UNITY Android – Ekranda Parmakla Çevrilebilir Direksiyon Örneği

Yayınlandı: 16 Eylül 2013 yasirkula tarafından Oyun Tasarımı, UNITY 3D içinde

GÜNCELLEME – 10.09.2017: yazıda bahsi geçen UI direksiyon scriptini artık SimpleInput plugin’ime ekledim. SimpleInput plugin’i, multi-platform Input işlemlerini oldukça kolaylaştırdığı için bu direksiyon yerine SimpleInput’taki direksiyonu kullanmanızı öneririm: https://yasirkula.com/2017/07/19/unity-gelismis-input-sistemi-mobil-destekli/

Hepinize merhaba,

Bu dersimizde bir anlatım yapmayacağım ama yazdığım bir scripti sizinle paylaşacağım. Bu script sayesinde ekranda bir direksiyonu mouse ile ya da mobil cihazlarda parmak ile döndürebilir, scriptin döndürdüğü değeri kullanarak arabanıza yön verebilir ya da bir uçağı döndürebilirsiniz. Scripti olabildiğince comment’lemeye çalıştım.

Direksiyon scriptinin iki farklı versiyonu mevcut: eski GUI sistemini (OnGUI) destekleyen kod ve Unity 4.6 ile gelen yeni UI sistemini destekleyen kod. İkisini de ayrı ayrı paylaşıyorum:

OnGUI Üzerinde Çalışan Kod

Alttaki Direksiyon isimli C# scriptini projenize ekleyin. Ne zaman ki direksiyonun döndürdüğü değeri kullanmak istiyorsunuz, o zaman Direksiyon scriptinin EgimiAl() fonksiyonunu çağırın. Bu fonksiyon [-1,1] aralığında bir float döndürür; tıpkı Input.GetAxis(“Horizontal”) gibi.

using UnityEngine;

public class Direksiyon : MonoBehaviour
{
	public float maksimumDonusAcisi = 500f; // Direksiyonun dönebileceği maksimum açı
	public float direksiyonEbat = 256f; // Direksiyonun ekranda pixel cinsinden boyutu
	public Vector2 deltaPivot = Vector2.zero; // Eğer direksiyon tam merkezinden dönmüyorsa pivotuyla oynayarak durumu düzeltebilirsin
	public float direksiyonDuzelmeHizi = 200f; // Direksiyon bırakıldığında eski haline dönene kadar saniyede döneceği açı
	public Texture2D direksiyonTexture; // Direksiyonun texture'si

	private float direksiyonEgim; // Direksiyonun eğimi

	private bool direksiyonTutuluyor; // Direksiyonun elle tutulup tutulmamakta olduğunu depolayan değişken
	private Rect direksiyonKonum; // Direksiyonun ekrandaki konumu
	private Vector2 direksiyonMerkez; // Direksiyonun ekran koordinatlarında (Rect değil) merkezi
	private float direksiyonEskiEgim; // Gerekli bir değişken

	// Bölüm başlayınca tek seferlik çalıştırılır
	void Start()
	{
		// Değişkenlere değer ata ve direksiyonun ekrandaki konumunu hesapla
		direksiyonTutuluyor = false;
		direksiyonKonum = new Rect( 25, Screen.height - direksiyonEbat - 25, direksiyonEbat, direksiyonEbat );
		direksiyonMerkez = new Vector2( direksiyonKonum.x + direksiyonKonum.width * 0.5f, Screen.height - direksiyonKonum.y - direksiyonKonum.height * 0.5f );
		direksiyonEgim = 0f;
	}

	// Direksiyonun eğimini döndürür. Bu değeri kullanarak arabayı döndürmek gibi işlemler yapılabilir
	// Döndürülen değer -1 ile 1 arasındadır ve direksiyonu sola kırınca negatif değer,
	// sağa kırınca pozitif değer döndürülür.
	public float EgimiAl()
	{
		return direksiyonEgim / maksimumDonusAcisi;
	}

	// Ekrana direksiyonu çizdir
	void OnGUI()
	{
		// Alttaki satırın başındaki comment'i silerek direksiyonu tutabileceğin alanı görebilirsin
		// GUI.Box( direksiyonKonum, "" );

		Matrix4x4 eskiRotasyon = GUI.matrix;
		GUIUtility.RotateAroundPivot( direksiyonEgim, direksiyonKonum.center + deltaPivot );
		GUI.Box( direksiyonKonum, direksiyonTexture, GUI.skin.label );
		GUI.matrix = eskiRotasyon;
	}

#if UNITY_EDITOR || UNITY_STANDALONE || UNITY_WEBPLAYER
	/*
	** Bu kod parçası sadece Unity editöründe, PC, MAC, Linux ve Web Player'da çalışır
	*/
	// Her frame'de (kare) bir kez çalıştırılır
	void Update()
	{
		// Eğer direksiyon mouse tarafından tutuluyorsa
		if( direksiyonTutuluyor )
		{
			Vector2 mouseKonum;

			// Mousenin ekrandaki konumunu bul
			mouseKonum = Input.mousePosition;

			float direksiyonYeniEgim = Vector2.Angle( Vector2.up, mouseKonum - direksiyonMerkez );

			// Eğer mouse direksiyonun merkezine çok yakınsa işlem yapma
			if( Vector2.Distance( mouseKonum, direksiyonMerkez ) > 20f )
			{
				if( mouseKonum.x > direksiyonMerkez.x )
					direksiyonEgim += direksiyonYeniEgim - direksiyonEskiEgim;
				else
					direksiyonEgim -= direksiyonYeniEgim - direksiyonEskiEgim;
			}

			// Direksiyonun eğiminin maksimum dönme açısını aşmadığından emin ol
			if( direksiyonEgim > maksimumDonusAcisi )
				direksiyonEgim = maksimumDonusAcisi;
			else if( direksiyonEgim < -maksimumDonusAcisi ) // >
				direksiyonEgim = -maksimumDonusAcisi;

			direksiyonEskiEgim = direksiyonYeniEgim;

			// Eğer mouse kaldırılırsa direksiyonu bırak
			if( Input.GetMouseButtonUp( 0 ) )
				direksiyonTutuluyor = false;
		}
		else // Eğer direksiyon tutulmuyorsa
		{
			// Eğer mouse direksiyonu tuttuysa durumu güncelle
			if( Input.GetMouseButtonDown( 0 ) && direksiyonKonum.Contains( new Vector2( Input.mousePosition.x, Screen.height - Input.mousePosition.y ) ) )
			{
				direksiyonTutuluyor = true;
				direksiyonEskiEgim = Vector2.Angle( Vector2.up, (Vector2) Input.mousePosition - direksiyonMerkez );
			}

			// Eğer direksiyon serbestse ve dönmüş durumdaysa onu direksiyonDuzelmeHizi ile eski haline döndür
			if( !Mathf.Approximately( 0f, direksiyonEgim ) )
			{
				float deltaEgim = direksiyonDuzelmeHizi * Time.deltaTime;

				if( Mathf.Abs( deltaEgim ) > Mathf.Abs( direksiyonEgim ) )
				{
					direksiyonEgim = 0f;
					return;
				}

				if( direksiyonEgim > 0f )
					direksiyonEgim -= deltaEgim;
				else
					direksiyonEgim += deltaEgim;
			}
		}
	}
#else
	/*
	** Bu kod parçası sadece mobil cihazlarda çalışır
	*/
	private int parmakId = -1; // Dokunmatik ekranlarda kullanacağımız bir değişken

	// Her frame'de (kare) bir kez çalıştırılır
	void Update()
	{
		// Eğer direksiyon parmak tarafından tutuluyorsa
		if( direksiyonTutuluyor )
		{
			Vector2 parmakKonum = new Vector2( 0f, 0f );

			// Parmağın ekrandaki konumunu bul
			for( int i = Input.touchCount - 1; i >= 0; i-- )
			{
				Touch parmak = Input.GetTouch( i );

				if( parmak.fingerId == parmakId )
				{
					parmakKonum = parmak.position;

					// Eğer parmak ekrandan kaldırılıyorsa bir sonraki frame'de direksiyonu serbest bırak
					if( parmak.phase == TouchPhase.Ended || parmak.phase == TouchPhase.Canceled )
						direksiyonTutuluyor = false;
				}
			}

			float direksiyonYeniEgim = Vector2.Angle( Vector2.up, parmakKonum - direksiyonMerkez );

			// Eğer parmak direksiyonun merkezine çok yakınsa işlem yapma
			if( Vector2.Distance( parmakKonum, direksiyonMerkez ) > 20f )
			{
				if( parmakKonum.x > direksiyonMerkez.x )
					direksiyonEgim += direksiyonYeniEgim - direksiyonEskiEgim;
				else
					direksiyonEgim -= direksiyonYeniEgim - direksiyonEskiEgim;
			}

			// Direksiyonun eğiminin maksimum dönme açısını aşmadığından emin ol
			if( direksiyonEgim > maksimumDonusAcisi )
				direksiyonEgim = maksimumDonusAcisi;
			else if( direksiyonEgim < -maksimumDonusAcisi ) // >
				direksiyonEgim = -maksimumDonusAcisi;

			direksiyonEskiEgim = direksiyonYeniEgim;
		}
		else // Eğer direksiyon tutulmuyorsa
		{
			// Eğer bir parmak direksiyonu yeni tuttuysa durumu güncelle
			for( int i = Input.touchCount - 1; i >= 0; i-- )
			{
				Touch parmak = Input.GetTouch( i );

				if( parmak.phase == TouchPhase.Began )
				{
					if( direksiyonKonum.Contains( new Vector2( parmak.position.x, Screen.height - parmak.position.y ) ) )
					{
						direksiyonTutuluyor = true;
						direksiyonEskiEgim = Vector2.Angle( Vector2.up, parmak.position - direksiyonMerkez );
						parmakId = parmak.fingerId;
					}
				}
			}

			// Eğer direksiyon serbestse ve dönmüş durumdaysa onu direksiyonDuzelmeHizi ile eski haline döndür
			if( !Mathf.Approximately( 0f, direksiyonEgim ) )
			{
				float deltaEgim = direksiyonDuzelmeHizi * Time.deltaTime;

				if( Mathf.Abs( deltaEgim ) > Mathf.Abs( direksiyonEgim ) )
				{
					direksiyonEgim = 0f;
					return;
				}

				if( direksiyonEgim > 0f )
					direksiyonEgim -= deltaEgim;
				else
					direksiyonEgim += deltaEgim;
			}
		}
	}
#endif
}

Yeni UI Sistemi Üzerinde Çalışan Kod

Alttaki SteeringWheel isimli C# scriptini projenize ekleyin ve istediğiniz bir objeye component olarak ekleyin. Ardından UI canvas‘ınızda bir Image oluşturun, Image’i istediğiniz gibi konumlandırın ve Image’e direksiyon sprite‘sini atayın. Image’a Event Trigger component‘i eklemeyin yoksa script düzgün çalışmayabilir. Son olarak, scripti verdiğiniz objeyi seçin ve UI_Element değişkenine değer olarak direksiyon Image’ini verin.

Ne zaman ki direksiyonun döndürdüğü değeri kullanmak istiyorsunuz, o zaman SteeringWheel scriptinin GetClampedValue() fonksiyonunu çağırın. Bu fonksiyon [-1,1] aralığında bir float döndürür; tıpkı Input.GetAxis(“Horizontal”) gibi.

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using System.Collections;
 
public class SteeringWheel : MonoBehaviour
{
	public Graphic UI_Element;

	RectTransform rectT;
	Vector2 centerPoint;

	public float maximumSteeringAngle = 200f;
	public float wheelReleasedSpeed = 200f;

	float wheelAngle = 0f;
	float wheelPrevAngle = 0f;

	bool wheelBeingHeld = false;

	public float GetClampedValue()
	{
		// returns a value in range [-1,1] similar to GetAxis("Horizontal")
		return wheelAngle / maximumSteeringAngle;
	}

	public float GetAngle()
	{
		// returns the wheel angle itself without clamp operation
		return wheelAngle;
	}

	void Start()
	{
		rectT = UI_Element.rectTransform;
		InitEventsSystem();
	}

	void Update()
	{
		// If the wheel is released, reset the rotation
		// to initial (zero) rotation by wheelReleasedSpeed degrees per second
		if( !wheelBeingHeld && !Mathf.Approximately( 0f, wheelAngle ) )
		{
			float deltaAngle = wheelReleasedSpeed * Time.deltaTime;
			if( Mathf.Abs( deltaAngle ) > Mathf.Abs( wheelAngle ) )
				wheelAngle = 0f;
			else if( wheelAngle > 0f )
				wheelAngle -= deltaAngle;
			else
				wheelAngle += deltaAngle;
		}

		// Rotate the wheel image
		rectT.localEulerAngles = Vector3.back * wheelAngle;
	}

	void InitEventsSystem()
	{
		// Warning: Be ready to see some extremely boring code here :-/
		// You are warned!
		EventTrigger events = UI_Element.gameObject.GetComponent<EventTrigger>();

		if( events == null )
			events = UI_Element.gameObject.AddComponent<EventTrigger>();

		if( events.triggers == null )
			events.triggers = new System.Collections.Generic.List<EventTrigger.Entry>();

		EventTrigger.Entry entry = new EventTrigger.Entry();
		EventTrigger.TriggerEvent callback = new EventTrigger.TriggerEvent();
		UnityAction<BaseEventData> functionCall = new UnityAction<BaseEventData>( PressEvent );
		callback.AddListener( functionCall );
		entry.eventID = EventTriggerType.PointerDown;
		entry.callback = callback;

		events.triggers.Add( entry );

		entry = new EventTrigger.Entry();
		callback = new EventTrigger.TriggerEvent();
		functionCall = new UnityAction<BaseEventData>( DragEvent );
		callback.AddListener( functionCall );
		entry.eventID = EventTriggerType.Drag;
		entry.callback = callback;

		events.triggers.Add( entry );

		entry = new EventTrigger.Entry();
		callback = new EventTrigger.TriggerEvent();
		functionCall = new UnityAction<BaseEventData>( ReleaseEvent );//
		callback.AddListener( functionCall );
		entry.eventID = EventTriggerType.PointerUp;
		entry.callback = callback;

		events.triggers.Add( entry );
	}

	public void PressEvent( BaseEventData eventData )
	{
		// Executed when mouse/finger starts touching the steering wheel
		Vector2 pointerPos = ( (PointerEventData) eventData ).position;

		wheelBeingHeld = true;
		centerPoint = RectTransformUtility.WorldToScreenPoint( ( (PointerEventData) eventData ).pressEventCamera, rectT.position );
		wheelPrevAngle = Vector2.Angle( Vector2.up, pointerPos - centerPoint );
	}

	public void DragEvent( BaseEventData eventData )
	{
		// Executed when mouse/finger is dragged over the steering wheel
		Vector2 pointerPos = ( (PointerEventData) eventData ).position;

		float wheelNewAngle = Vector2.Angle( Vector2.up, pointerPos - centerPoint );
		// Do nothing if the pointer is too close to the center of the wheel
		if( Vector2.Distance( pointerPos, centerPoint ) > 20f )
		{
			if( pointerPos.x > centerPoint.x )
				wheelAngle += wheelNewAngle - wheelPrevAngle;
			else
				wheelAngle -= wheelNewAngle - wheelPrevAngle;
		}
		// Make sure wheel angle never exceeds maximumSteeringAngle
		wheelAngle = Mathf.Clamp( wheelAngle, -maximumSteeringAngle, maximumSteeringAngle );
		wheelPrevAngle = wheelNewAngle;
	}

	public void ReleaseEvent( BaseEventData eventData )
	{
		// Executed when mouse/finger stops touching the steering wheel
		// Performs one last DragEvent, just in case
		DragEvent( eventData );

		wheelBeingHeld = false;
	}
}

Scripti projelerinizde credit vermeden kullanabilirsiniz ama eğer credit verirseniz elbette ki memnun olurum.

Scripti kullanarak oluşturduğum örnek projeyi Web Player üzerinden test etmek isterseniz (OnGUI versiyonunu kullandım): http://yasirkula.freeiz.com/Projects/SimpleCarProject.html

Örnek projeyi indirmek isterseniz (Unity 5 desteklemez): https://www.dropbox.com/s/ex1yagax4k236p7/SimpleCarProject.rar?dl=0

Başka derslerde görüşmek dileğiyle!

yorum
  1. Kenan dedi ki:

    Selam. Hocam bir nesneyi, cihazın ekran boyutuna kadar hareket ettirmek istiyorum.Yani nesnenin hareketi edişi, ekranın boyutunu aşmamalı.Bununla ilgili c# dilinde bir kod önerebilir misiniz?

    • yasirkula dedi ki:

      Camera sınıfının ViewportToWorldPoint veya ScreenToWorldPoint fonksiyonlarına bakabilirsiniz.

      • Kenan dedi ki:

        Teşekkür ederim hocam. Bu kodların ne işe yaradığını açiklayabilir misiniz? Yani amacı nedir ona göre mantık yürütmeye çalışacağım.Sitesinden çeviri yaptım ama hala amaçlarını tam kavrayamadım.3d nesneyi ekranın dışına çıkmadan durdurmaya çalışıyorum.Canvas kullanmama rağmen, 3d nesneye velocity uyguladığım için dışarıya fırlıyor,engel olamadım.Bunu çözmeye çalışıyorum.Tekrar teşekkür ederim.Allah razı olsun.

      • yasirkula dedi ki:

        ScreenToWorldPoint fonksiyonu, ekrandaki bir pikselin 3D uzayda hangi koordinata denk geldiğini döndürür. Örneğin “Camera.main.ScreenToWorldPoint(new Vector3(Screen.width, Screen.height, 10f))” fonksiyonu, ekranın en sağ üst noktasının 3D uzayda hangi nokta olduğunu döndürür. Döndürülen noktanın kameraya uzaklığı ise 10f’tir. ViewportToWorldPoint’te ise [0,Screen.width] olan aralığı [0,1] olarak düşünebilirsiniz, başka bir farkı yok. Bu fonksiyonlar ile ekranın kenarlarının 3D koordinatlarını bulup objenin bu koordinatlardan dışarı çıkmamasını sağlayabilirsiniz.

Cevap Yazın

Aşağıya bilgilerinizi girin veya oturum açmak için bir simgeye tıklayın:

WordPress.com Logosu

WordPress.com hesabınızı kullanarak yorum yapıyorsunuz. Çıkış  Yap /  Değiştir )

Google fotoğrafı

Google hesabınızı kullanarak yorum yapıyorsunuz. Çıkış  Yap /  Değiştir )

Twitter resmi

Twitter hesabınızı kullanarak yorum yapıyorsunuz. Çıkış  Yap /  Değiştir )

Facebook fotoğrafı

Facebook hesabınızı kullanarak yorum yapıyorsunuz. Çıkış  Yap /  Değiştir )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.