Unity UI Dokunmatik Ekran Joystick Kullanımı (Multi-touch Destekli)

Yayınlandı: 17 Haziran 2016 yasirkula tarafından Oyun Tasarımı, UNITY 3D içinde

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

Hepinize merhabalar,

Belki biliyorsunuzdur, bundan yaklaşık 2 sene önce sitemde bir Joystick scripti paylaşmıştım (https://yasirkula.com/2014/05/27/unity-3d-android-dokunmatik-ekran-joystick-kullanimi/). Bunun üzerinden uzunca bir zaman geçti. Bazen bu scripti Unity’nin 4.6 versiyonu ile gelen yeni UI sistemini kullanarak güncellemeyi düşündüm ama bu fikir nedense hep havada kaldı. Ancak bugün ufak bir azimle scripti yeni sisteme geçirdim, ne yalan söyleyeyim hoşuma da gitti yeni script 🙂

Belki reklam olarak göreceksiniz ama beni bu Joystick scriptini güncellemeye iten önemli bir faktör de TAGDA Game kanalının joystick hakkındaki bir video dersiydi (https://www.youtube.com/watch?v=k0DrDK5ixlQ), değinmeden geçmek istemedim. Hepinize destekleriniz için teşekkür ederim.

Yeni joystick plugini (unitypackage): https://www.dropbox.com/s/pwm3yf1a72r41h0/JoystickUI.unitypackage?dl=0 (Alternatif link)

Derste işlediğimiz örnek projenin bitmiş hali: https://www.dropbox.com/s/jm8m83qybw3tmes/JoystickUIOrnekProje.zip?dl=0 (Alternatif link)

0

Detayları görmek ve örnek projeye joystick’i adım adım implement etmek için yazının devamını okuyabilirsiniz…

Hakkında

Yeni scripti C# ile yazdım. Joystick’in konumlandırmasını direkt UI üzerinden yaptığımız için script oldukça hafifledi. Bir de UI Button’larda gördüğümüz “On Click” olayını (event sistemi) kendi joystick’ime de uyarladım. Böylece artık joystick’in değeri değiştiği vakit (joystick’i hareket ettirdiğinizde) kendi scriptlerinizdeki bazı özel fonksiyonları otomatik olarak çağırabiliyorsunuz. Bu fonksiyonları özel kılan kısıtlamalar ise public olmaları ve Vector2 türünde bir parametre almak zorunda olmaları (tahmin edebileceğiniz üzere bu Vector2, joystick’in output’unu depolamakta).

Üstteki değişikliklere ilaveten, artık yeni plugin ile birlikte CC0 lisansına sahip birkaç joystick görseli de hazır olarak geliyor. Bu görselleri herhangi bir yere credit vermeden istediğiniz gibi kullanabilirsiniz (veya tabi ki kendi görsellerinizi kullanmaya da devam edebilirsiniz).

Kullanım

Yazının başında paylaştığım linkten joystick unitypackage‘ını indirip bu package’i Assets-Import Package-Custom Package… yoluyla projenize import edin. Ardından Plugins-JoystickUI klasörü içerisindeki JoystickUI.cs scriptini istediğiniz bir objeye component olarak verin. Şimdi Inspector‘dan “Joystick Image” değişkenine değer olarak, canvas’ınızdaki joystick UI objesini verin ve diğer ayarları da istediğiniz gibi düzenleyin. Artık Joystick’in döndürdüğü değere erişmek için iki farklı seçeneğiniz var (bu değer bir Vector2‘dir ve x bileşeni joystick sağa kaydıkça 1’e, sola yaklaştıkça -1’e yakınlaşır. Benzer şekilde, y bileşeni de joystick yukarı yaklaştıkça 1’e, aşağı yaklaştıkça -1’e yakınlaşır):

  1. a) joystick’in değerinden faydalanmak istediğiniz scriptte, Vector2 türünde bir parametre alan public bir fonksiyon tanımlayıp bu fonksiyonu joystick’in Inspector’undaki “Joystick Hareket Event” event’ine tanıtmak. Artık joystick’in değeri değiştikçe otomatik olarak bu fonksiyon çağrılacak.
  1. b) (eski usül) joystick’e GetComponent(JoystickUI) ile veya public bir değişken vasıtasıyla erişip oradan da joystick’in sonuc (Vector2) değişkenine erişmek.

Her iki durumu da örnek projede göreceğiz.

Detaylar

Joystick’in Inspector’una ufaktan bir bakış atalım:

1

Hakkında: joystick’in kullanımıyla ilgili ufak bir özet geçtiğim yardımcı metni göstermeye/gizlemeye yarar

Joystick Image: UI sistemini kullanarak ekranda oluşturduğunuz joystick objesi buraya değer olarak verilmeli

Joystick Arkaplan Image: eski scriptin üzerine eklediğim bir yenilik, joystick arkaplanı desteği. Genelde bu arkaplanların dört bir yanında ok işaretleri olur ve joystick başlangıçta arkaplanın ortasında yer alır. Joystick parmakla hareket edebilirken arkaplan daima sabit kalır. Buradaki olay da aynen öyle. Ancak arkaplan kullanmak tamamen opsiyonel, değerini boş bırakabilirsiniz

Hareket Alani Yaricap Deger: joystick’in pixel cinsinden hareket alanı yarıçapı. Eğer dilerseniz değerini kod vasıtasıyla (GetComponent(JoystickUI).hareketAlaniYaricap) istediğiniz gibi değiştirebilirsiniz (örneğin ekranın yüksekliğinin %20’sini kaplayan bir yarıçap için)

Hareket Ekseni: joystick’in hareket edebileceği eksenler

Dinamik Konumlandir: dinamik joystick dediğimiz şey, ekrana dokununca dokunulan noktada beliren ve ekrandan parmağı çekince otomatik olarak kaybolan joystick versiyonu oluyor. Bu seçeneği işaretlerseniz joystick dinamik oluyor

Dinamik Joystick Hareket Alani: eski scriptin üzerine eklediğim bir başka yenilik. Bu değişken sadece dinamik joystick’ler için geçerli. Bazen dinamik joystick’in ekranın her yerine tıklayınca çıkmasını değil de örneğin sadece sağ yarısına dokununca çıkmasını isteyebilirsiniz. Bu durumda tek yapmanız gereken ekranın sağ yarısını kaplayan, RectTransform‘a sahip boş bir obje oluşturup onu Dinamik Joystick Hareket Alani değişkenine değer olarak vermek. Eğer bu değişkenin değerini boş bırakırsanız joystick ekranın herhangi bir yerine dokununca çıkabilir, yani bir kısıtlama söz konusu olmaz

Örnek Bir Kullanım

Bir önceki dersimde direkt bir örnek proje paylaşmıştım. Bu sefer öyle yapmayacağım. Onun yerine Asset Store‘daki ücretsiz bir proje üzerinden adım adım gideceğim. Bunun başlıca sebebi, zaten var olan ve klavye veya mouse ile çalışan bir projeye joystick’in nasıl yedirileceği ile ilgili oldukça fazla soru almam. Umuyorum ki bu örnek, bu konuda yaşadığınız sıkıntıları bir nebze azaltır.

Kullanacağımız hazır proje şu: https://www.assetstore.unity3d.com/en/#!/content/54604

Bu asset’i seçme sebeplerim ücretsiz oluşu, boyutunun ufak oluşu, Unity’nin nispeten güncel bir sürümüyle yapılmış olması ve de joystick’e çok elverişli olması (birazdan göreceksiniz zaten).

Yeni bir proje açıp bu asset’i projenize import edin. Ardından “Base_01” isimli scene‘i açıp oyunu test edin (Kawaii_Tanks_Assets klasörü içerisinde). Şu anda oyunda çok fazla draw call var. Hedefimiz görsellikten ziyade akıcı bir gameplay olduğu için sahnede biraz temizlik yapalım. Bunun için Hierarchy panelinden “SD_Tiger-I (2)“, “Scene_Objects (1)” ve “Scene_Objects (2)” objelerini seçip silin. Sonrasında “Terrain” objesini seçip Inspector‘da en sağdaki dişli ikona tıklayın ve “Pixel Error” değerini 80 yapın. Terrain artık biraz garip gözükecek ancak draw call sayısı oldukça düşecek (eğer daha da düşsün isterseniz “Terrain Height” değerini 0 yapabilirsiniz ancak artık tümseklerin yerinde yeller eser).

Bu projede ne bir ne iki, tamı tamına üç tane joystick kullanacağız. İlki tankı hareket ettirmek için, ikincisi kamerayı hareket ettirmek için, üçüncüsü de ekrandaki nişangahı hareket ettirmek için (bir başka deyişle tankın kafasını hareket ettirmek için). Bonus olarak bir de basit bir ateş etme butonu koyacağız, çünkü; neden olmasın?!

Joystick’i kullanmak istediğiniz bir oyunda, hangi scriptlerde değişiklik yapmanız gerektiğini bulmak için en basit yöntem şu kriterlere dikkat etmek:

  • oyun ok tuşları veya WSAD ile oynanıyor mu? Eğer oynanıyorsa kodlarınızda Input.GetAxis(“Horizontal”) ve Input.GetAxis(“Vertical”) komutlarını arayın. Eğer Visual Studio kullanıyorsanız bunu yapmanın en kolay yolu projedeki rastgele bir scripti açmak ve ardından CTRL+Shift+F kombinasyonu ile arama kutucuğunu çıkartmak. Oradan “Look in:” kısmını “Entire Solution” yaptığınız vakit projede yer alan tüm scriptler otomatik olarak aranır (arama işlemini yapmak için “Find All” butonuna basın).

NOT: Kimileri parantezler arasına boşluk koyarken kimileri parantezden önce boşluk koyar, kimileri ise hiç bir yere boşluk koymaz. Her bir durumu tek bir seferde aramak için regular expression kullanabiliriz. Bunun için arama kutucuğundaki “Find options“tan “Use Regular Expressions“ı seçin ve arama kısmına GetAxis\s*\(\s*”Vertical”\s*\) yazın. Burada kullandığımız “\s*” kısmı, sıfır veya daha fazla sayıda boşluk karakteri (daha doğrusu whitespace karakteri) anlamına gelir. Parantezleri “\(” ve “\)” şeklinde açıp kapatmamızın sebebi de, bu parantezlerin regular expression’ın bir parçası olmaktan ziyade normal birer parantez karakteri olduklarını göstermek.

  • oyun klavyeden özel bir tuş kombinasyonu (örneğin TGFH) ile mi oynanıyor? İlk iş Edit-Project Settings-Input‘ta bu tuşlara göre ayarlanmış axes‘ler var mı diye bakın. Eğer varsa onları GetAxis(“Axes’in Adı”) şeklinde kodlarınızda aratın. Aksi taktirde kodlarınızda Input.GetKey(KeyCode.T) veya Input.GetKeyDown(KeyCode.T) şeklinde aramalar yapın (son iki örnekte T tuşunun kullanımlarını arıyoruz).
  • oyun mouse ile mi oynanıyor? O zaman kodlarınızda Input.GetAxis(“Mouse X”), Input.GetAxis(“Mouse Y”) ve Input.mousePosition komutlarının kullanımlarını aratın.
  • kodları elle mi arayacaksınız? O halde “control“, “controller” veya “movement” kelimelerini içeren scriptlere öncelik verebilirsiniz.

Kodlarımızda düzenleme yapmadan önce dilerseniz joystick’lerimizi bir oluşturalım. Bunun için JoystickUI unitypackage‘ını projeye import edin. Artık joystick’leri oluşturmaya başlayabiliriz.

  • Tankı Hareket Ettiren Joystick

Bu joystick dinamik olmayacak ve bir arkaplana sahip olacak. Konum olarak ise ekranın sol altını düşündüm.

GameObject-UI-Image ile yeni bir Image objesi oluşturup ismini TankHareketJoystick yapın. Ben sprite olarak “JoystickGorseller” klasöründeki Joystick5‘i verdim. Son olarak joystick’i kabaca ekranın sol altına yerleştirdim:

2

Arkaplan için TankHareketJoystickArkaplan adında yeni bir Image oluşturup sprite olarak JoystickArkaplan4‘ü verdim. Kendisini TankHareketJoystick ile aynı yere yerleştirdim, sadece Width ve Height değerlerini 150 olarak artırdım.

NOT: Önce arkaplanın, sonra onun üzerine joystick’in çizilmesini sağlamak için Hierarchy‘de arkaplan objesini sürükleyerek joystick’in yukarısında bir yere taşıyın. Çünkü UI elemanları, Hierarchy’deki sıralarına göre (yukarıdan aşağıya doğru) çizilirler.

Görselliği hallettiğimize göre TankHareketJoystick objesine JoystickUI scriptini component olarak ekleyebiliriz:

3

Şimdi oyunu çalıştırırsanız joystick’in çalışmadığını göreceksiniz. Bunun sebebi joystick’in Unity’nin event sistemini kullanıyor olması. Bu yüzden de çalışması için sahnede bir EventSystem objesi olmak zorunda. Normalde bir UI elemanı oluşturunca yeni bir canvas ve EventSystem objesi otomatik olarak oluşturulur ancak bizim örneğimizde sahnede zaten bir canvas vardı (ama EventSystem yoktu). Bu durumda Unity, joystick’leri sahnede zaten var olan canvas’a ekledi. Yeni bir canvas oluşturmadığı için de otomatik olarak yeni bir EventSystem objesi oluşmadı. Uzun lafın kısası, eğer sahnenizde EventSystem objesi yoksa “GameObject-UI-Event System” yoluyla yeni bir tane oluşturun.

  • Kamerayı Hareket Ettiren Joystick

Bu joystick ekranın sağ alt kısmında yer alacak, dinamik olmayacak ve yine bir arkaplana sahip olacak. Yalnız bu sefer joystick’in boyutlandırılmasını daha farklı bir şekilde yapacağız. “Joystick 100 pixel olsun” demek yerine “joystick ekranın yüksekliğinin %15’i kadar olsun” diyeceğiz. Ben kendi oyunlarımda da genelde bu şekilde joystick kullanırım çünkü yeni mobil cihazların devasa ekran çözünürlükleri var ve 100 pixel’lik bir joystick o ekranlarda minnacık kalabiliyor. Joystick’in ekran çözünürlüğüne göre kendini otomatik olarak ayarlaması oldukça önem arz edebiliyor (önceki joystick’i neden böyle yapmadık derseniz, sadece göstermek amaçlıydı). Benzer şekilde, joystick’in hareket yarıçapı da ekranın yüksekliğinin %10’u kadar olacak. Bunu ise kod vasıtasıyla yapacağız.

KameraHareketJoystick adında yeni bir Image oluşturun. Ben sprite olarak Joystick1‘i verdim. Ardından joystick’e genişlik/yükseklik oranının 1 olması için (genişliğinin de ekranın yüksekliğinin %15’i kadar olması için) bir Aspect Ratio Fitter component’i verip joystick’i şekildeki gibi konumlandırdım:

4

UI sisteminde %’lik hesaplamaları anchor‘lar vasıtasıyla yapıyoruz. Anchor Ymin değeri 0.2 olduğu vakit joystick ile ekranın alt kenarı arasında, ekranın yüksekliğinin %20’si kadar bir boşluk oluyor. Ymax değeri 0.35 olduğu için de joystick ekranın yüksekliğinin %15’ini kaplıyor. Aspect Ratio Fitter “Height Controls Width” (yükseklik genişliği kontrol eder) modunda olup “Aspect Ratio” değeri de 1 olduğu için joystick’in genişlik ve yüksekliği eşit oluyor. Anchor X değerlerinin ikisi de 0.8 olduğu için de joystick ile ekranın sağ kenarı arasındaki uzaklık, ekranın genişliğinin %20’si kadar oluyor.

KameraHareketJoystickArkaplan için JoystickArkaplan1 sprite’ını kullandım ve onu şekildeki gibi konumlandırdım:

5

Arkaplan, joystick’in kendisinden %10 daha büyük. Başka bir farklılık yok. Artık bu joystick’e de JoystickUI component’i verebiliriz:

6

Yarıçap değerini daha sonradan script vasıtasıyla değiştireceğiz.

  • Ekrandaki İmleci Hareket Ettiren Joystick

Bu joystick dinamik olacak, bir arkaplanı olmayacak ve sadece ekranın orta yarısına dokununca ortaya çıkacak (çünkü bu dersin amacı, joystick scriptinin tüm özelliklerini keşfetmek).

CrosshairHareketJoystick adında yeni bir Image oluşturun. Benim seçtiğim sprite Joystick2. Bu joystick dinamik olacağı için pozisyonu önemli değil, sadece Width ve Height değerlerini 100 yapmanız yeterli (ilk joystick gibi pixel cinsinden boyutlandırma yapıyorum).

Bu joystick’in ekranın sadece orta yarısında çalışması için, bu orta yarıyı bir RectTransform vasıtasıyla scripte tanıtmalıyız. Bunun için sahnedeki Canvas objesine sağ tıklayıp “Create Empty” yolunu izleyerek canvas’ta yeni bir RectTransform objesi oluşturun. Yeni objeye CrosshairJoystickHareketAlani adını verin ve RectTransform’unu, ekranın orta yarısını kaplayacak şekilde ayarlayın:

7

Son olarak da bu objeyi Hierarchy‘de canvas’ın hemen altına taşıyın:

8

Bunu yapmamızın sebebi şu: UI sisteminde elemanlar yukarıdan aşağıya doğru çizildiği için, ekrana dokunulduğu vakit bu dokunmayı hangi UI elemanının kabul edeceği kontrol edilirken aşağıdan yukarıya doğru bir sıra izleniyor. Biz hareket alanını en yukarıya koyduğumuz için, eğer bu alanın üzerine çizilen başka bir UI elemanı varsa (mesela başka bir joystick) ve oyuncu oraya dokunursa, o zaman o raycast’i dinamik joystick’in hareket alanı değil de o noktadaki diğer UI elemanı kabul ediyor (ve haliyle orada dinamik joystick’i oluşturmuyoruz).

Son olarak CrosshairHareketJoystick‘e bir JoystickUI component’i vererek işlemi sonlandırın:

9

NOT: şu anda oyundayken ekrana sol tıklayınca ekranda Reticle isimli UI elemanı çizdirildiği için (kodlayan kişi öyle uygun görmüş) bu bizim dinamik joystick’imize engel oluyor. O olmasa bile ekrandaki crosshair hep mouse’u takip ettiği için, ekrana tıkladığımız zaman ilgili input hep bu objelere gidiyor. Oysa bu objelerin raycast’i kabul etmelerini gerektirecek bir sebep yok. Bunu çözmek için tek yapmanız gereken Marker ve Reticle objelerinin Image component’lerindeki “Raycast Target” seçeneğini kapatmak.

Joystick’in Output’unu Kod(lar)da Kullanmak

Artık (en nihayetinde) joystick’leri oluşturmayı tamamladık. Gelelim bu joystick’leri kodlarımızda kullanmaya.

  • Tankı Hareket Ettirmek

Bunun için joystick’in yeni “Joystick Hareket Event” değişkeninden faydalanacağız (tamamen tercih meselesi). Ufak bir arama sonucu, tankın “MainBody” objesinde yer alan “Wheel_Controller_CS” scriptindeki şu kodun tankın hareketinden sorumlu olduğunu buldum:

void Update () {
	if ( isCurrentID ) {
		#if UNITY_ANDROID || UNITY_IPHONE
		float vertical = CrossPlatformInputManager.GetAxis ( "Vertical" ) ;
		float horizontal = Mathf.Clamp ( CrossPlatformInputManager.GetAxis ( "Horizontal" ) , -turnClamp , turnClamp ) ;
		#else
		float vertical = Input.GetAxis ( "Vertical" ) ;
		float horizontal = Mathf.Clamp ( Input.GetAxis ( "Horizontal" ) , -turnClamp , turnClamp ) ;
		#endif
		leftRate = Mathf.Clamp ( -vertical - horizontal , -1.0f , 1.0f ) ;
		rightRate = Mathf.Clamp ( vertical - horizontal , -1.0f , 1.0f ) ;
	}
}

Görüldüğü üzere, #if kullanarak mobil platformlar için ayrı, diğer platformlar için ayrı bir kod yazılmış. Ancak her iki durumda da alınan input’un sonucu leftRate ve rightRate değişkenlerine gidiyor. O halde, bu iki değişkenin değerlerini joystick’in output’undan almalarını sağlarsak artık tankı joystick ile hareket ettirebileceğiz.

Joystick’in “Joystick Hareket Event” değişkeninden faydalanmak için scriptimizde Vector2 türünde parametre alan public bir fonksiyon oluşturmalıyız. Öyleyse üstteki Update fonksiyonunu silip yerine şunu yazalım:

public void TankiJoystickIleHareketEttir( Vector2 joystickOutput )
{
	float vertical = joystickOutput.y;
	float horizontal = Mathf.Clamp ( joystickOutput.x, -turnClamp , turnClamp );
	leftRate = Mathf.Clamp ( -vertical - horizontal , -1.0f , 1.0f );
	rightRate = Mathf.Clamp ( vertical - horizontal , -1.0f , 1.0f );
}

Farklı platformlar için farklı kod kullanımından kurtulduk (joystick her platformu destekliyor) ve koddaki Input.GetAxis(“Horizontal”)‘ı joystickOutput.x ile, Input.GetAxis(“Vertical”)‘ı da joystickOutput.y ile değiştirdik. Geriye sadece bu fonksiyonu “Joystick Hareket Event“e tanıtmak kaldı. O halde TankHareketJoystick‘teki JoystickUI component’inde yer alan ilgili event’e bu fonksiyonu ekleyelim:

10

Oyunu test ederseniz artık sol alttaki joystick’in istendiği gibi çalıştığını göreceksiniz.

NOT: Editor’deyken ekrana her dokunduğumuzda kameranın değişmesi sizi de rahatsız ediyorsa GunCamera_Control_CS scriptindeki GunCam_On ve GunCam_Off fonksiyonlarını comment’leyebilir veya direkt silebilirsiniz.

  • Kamerayı Hareket Ettirmek

Kamerayı döndürme işlemini, “Camera_Pivot” objesindeki “Camera_Rotate_CS” scriptinde bulunan şu kod sağlamakta:

void Update () {
	if ( isCurrentID ) {
		#if UNITY_ANDROID || UNITY_IPHONE
		angY += CrossPlatformInputManager.GetAxis ( "Camera_X" ) * 2.0f ;
		angZ -= CrossPlatformInputManager.GetAxis ( "Camera_Y" ) ;
		#else
		if ( Input.GetMouseButtonDown ( 1 ) ) {
			previousMousePos = Input.mousePosition ;
		}
		if ( Input.GetMouseButton ( 1 ) ) {
			float horizontal = ( Input.mousePosition.x - previousMousePos.x ) * 0.1f ;
			float vertical = ( Input.mousePosition.y - previousMousePos.y ) * 0.1f ;
			previousMousePos = Input.mousePosition ;
			angY += horizontal * 3.0f ;
			angZ -= vertical * 2.0f ;
		}
		#endif
		thisTransform.rotation = Quaternion.Euler ( 0.0f , angY , angZ ) ;
	}
}

Görüldüğü üzere yine ve yeniden farklı platformlar için farklı bir kod yazılmış ancak en neticede angY ve angZ değişkenleri vasıtasıyla kamera döndürülmekte. angY’nin değerini “Camera_X” isimli axis’ten aldığını göz önünde bulundurursak, joystick’in output’unun x değeri ile angY’yi, joystick’in output’unun y değeri ile de angZ’yi değiştireceğimizi tahmin edebiliriz.

Bu sefer de “Joystick Hareket Event“i kullanacağız. Ancak burada şöyle bir durum var: angY ve angZ değişkenlerinin değerleri Update içerisinde her frame’de güncelleniyor. Ancak “Joystick Hareket Event” sadece joystick hareket edince çalışmakta, yani joystick’i bir miktar kımıldattıktan sonra hiç hareket ettirmezsek herhangi bir fonksiyon çağrılmamakta. Oysa biz joystick’i sağa dayayıp o şekilde hiç kımıldamadan beklesek bile angY’nin değeri sürekli olarak artmalı. Önceki örnekte böyle bir durum yoktu çünkü orada herhangi bir değişkenin değerinin üzerine ekleme yapmıyorduk, direkt bir değişkenin değerini değiştiriyorduk. Bu değer değiştirme işlemini de sadece joystick hareket edince yapmak optimizasyon açısından gayet makul bir şey.

Kısacası, bu durumda eğer “Joystick Hareket Event”ten faydalanacaksak joystick’in output’u her değiştiğinde onu bir değişkene kaydetmeli ve Update fonksiyonunda bu değişkenden faydalanmalıyız. Kodun o kısmının son hali şöyle olacak:

private float joystickXDegeri;
private float joystickYDegeri;

public void JoystickinDegeriDegisti( Vector2 joystickOutput )
{
	joystickXDegeri = joystickOutput.x;
	joystickYDegeri = joystickOutput.y;
}

void Update()
{
	angY += joystickXDegeri * 2.0f;
	angZ -= joystickYDegeri;
	thisTransform.rotation = Quaternion.Euler ( 0.0f , angY , angZ );
}

Joystick hareket ettikçe output’unun son halini joystickXDegeri ve joystickYDegeri değişkenlerinde tutuyoruz. Update fonksiyonunda da bu değişkenlerden faydalanıyoruz. “JoystickinDegeriDegisti” fonksiyonunu “KameraHareketJoystick“teki JoystickUI‘a tanıtın ve oyunu test edin:

11

NOT: Eğer kamera sadece sola-sağa hareket etsin isterseniz “Hareket Ekseni“ni “Sadece X” yapabilirsiniz.

  • İmleci Hareket Ettirmek

Bu kodun kaynağını bulmak diğerlerine göre nispeten daha zordu ama en neticede Input.mousePosition üzerinden bir arama yapınca (imleç şu anda fareyi takip ediyor çünkü) bu işlemin, “Turret_Base” objesindeki “Turret_Control_CS” scriptinde yer alan şu kod ile gerçekleştiğini gördüm:

void LateUpdate () {
	if ( isCurrentID ) {
		#if UNITY_ANDROID || UNITY_IPHONE
		Mobile_Control () ;
		#else
		Mouse_Control () ;
		#endif
		if ( markerImage ) {
			markerImage.transform.position = Camera.main.WorldToScreenPoint ( targetPos ) ;
			if ( markerImage.transform.position.z < 0 ) {
				markerImage.enabled = false ;
			} else {
				markerImage.enabled = true ;
			}
		}
	}
}

#if !UNITY_ANDROID && !UNITY_IPHONE
void Mouse_Control () {
	...
	Cast_Ray ( Input.mousePosition , true ) ;
	...
}
#endif

#if UNITY_ANDROID || UNITY_IPHONE
void Mobile_Control () {
	...
	Cast_Ray_Mobile ( new Vector3 ( CrossPlatformInputManager.GetAxis ( "Target_X" ) , CrossPlatformInputManager.GetAxis ( "Target_Y" ) , 0.0f ) ) ;
	...
}
#endif

void Cast_Ray ( Vector3 screenPos , bool isLockOn ) {
	Ray ray = Camera.main.ScreenPointToRay ( screenPos ) ;
	RaycastHit raycastHit ;
	if ( Physics.Raycast ( ray , out raycastHit , 1000.0f ) ) {
		Transform colliderTransform = raycastHit.collider.transform ;
		if ( colliderTransform.root != rootTransform ) {
			if ( isLockOn ) {
				if ( raycastHit.transform.GetComponent < Rigidbody > () ) { //When 'raycastHit.collider.transform' is Turret, 'raycastHit.transform' is the MainBody.
					targetTransform = colliderTransform ;
					targetOffset = targetTransform.InverseTransformPoint ( raycastHit.point ) ;
					return ;
				} else {
					targetTransform = null ;
				}
			}
			targetPos = raycastHit.point ;
		} else { // Ray hits itsel
			screenPos.z = 50.0f ;
			targetPos = Camera.main.ScreenToWorldPoint ( screenPos ) ;
		}
	} else { // Ray does not hit anythig.
		screenPos.z = 500.0f ;
		targetPos = Camera.main.ScreenToWorldPoint ( screenPos ) ;
	}
}

#if UNITY_ANDROID || UNITY_IPHONE
void Cast_Ray_Mobile ( Vector3 screenPos ) {
	Ray ray = Camera.main.ScreenPointToRay ( screenPos ) ;
	RaycastHit [] raycastHits ;
	raycastHits = Physics.SphereCastAll ( ray , spherecastRasius , 1000.0f ) ;
	foreach ( RaycastHit raycastHit in raycastHits ) {
		Transform colliderTransform = raycastHit.collider.transform ;
		if ( colliderTransform.root != rootTransform ) {
			if ( raycastHit.transform.GetComponent < Rigidbody > () ) { //When 'raycastHit.collider.transform' is Turret, 'raycastHit.transform' is the MainBody.
				targetTransform = colliderTransform ;
				targetOffset = targetTransform.InverseTransformPoint ( raycastHit.point ) ;
				return ;
			}
		}
	}
	Cast_Ray ( screenPos , true ) ;
}
#endif

LateUpdate‘te, mevcut platforma bağlı olarak, Mobile_Control() veya MouseControl() fonksiyonlarından birisi çağrılıyor. Bu fonksiyonların içerisinden ise Cast_Ray ve Cast_Ray_Mobile fonksiyonları çağrılıyor (mouse’un/parmağın o anki mevcut konumu ile). Ve en nihayetinde bu fonksiyonların içinde de bir takım Raycast operasyonu ile tankın namlusunun nereye doğru bakması gerektiği hesaplanıyor. Velhasılıkelam, bizim yapmamız gereken şey Cast_Ray’e gönderilen screenPos parametresinin değerini joystick ile güncellemek. Cast_Ray_Mobile fonksiyonunu kullanmayacağım çünkü imleç Editor’de (Cast_Ray) zaten oldukça güzel çalışıyor.

O halde ihtiyacımız olan şey, screenPos yerine kullanacağımız yeni bir değişken oluşturup bunun değerini joystick hareket ettikçe güncellemek (kamera hareket scriptine benzer şekilde). Buna ilaveten ben mevcut koddaki fazlalıkları da sileceğim (Cast_Ray_Mobile ve Mobile_Control fonksiyonları).

Bu sefer joystick’in değerini direkt olarak joystick component’inden çekeceğiz (nasıl yapıldığını görme amaçlı) o yüzden bu scripte bir de JoystickUI türünde public bir değişken eklememiz lazım. Yukarıdaki kodun son hali şöyle olmalı:

public JoystickUI joystickScript;
private Vector3 imlecKonum = new Vector3( Screen.width / 2, Screen.height / 2, 0f );

void LateUpdate()
{
	Mouse_Control();

	if( markerImage )
	{
		markerImage.transform.position = Camera.main.WorldToScreenPoint( targetPos );
		if( markerImage.transform.position.z < 0 )
		{
			markerImage.enabled = false;
		}
		else
		{
			markerImage.enabled = true;
		}
	}
}

void Mouse_Control()
{
	imlecKonum.x += joystickScript.sonuc.x * Screen.width * 0.5f * Time.deltaTime;
	imlecKonum.y += joystickScript.sonuc.y * Screen.width * 0.5f * Time.deltaTime;

	imlecKonum.x = Mathf.Clamp( imlecKonum.x, 0f, Screen.width );
	imlecKonum.y = Mathf.Clamp( imlecKonum.y, 0f, Screen.height );

	Cast_Ray( imlecKonum, false );
}

void Cast_Ray( Vector3 screenPos, bool isLockOn )
{
	// Bir değişiklik yok
	...
}

Artık LateUpdate‘te direkt Mouse_Control() fonksiyonunu çağırıyoruz. Bu fonksiyonun içinde ise, yeni tanımladığımız imlecKonum vektörünün x ve y değerlerini, joystick’in output’unun (sonuc) x ve değerlerinden faydalanarak güncelliyoruz. Şu anki formül ile, joystick en sağa dayalıyken imlecin ekranın en sol kenarından en sağ kenarına gelmesi toplam 2 saniye alıyor. İmlecin konumunun ekranın dışına çıkamamasını da Clamp kullanarak garanti altına aldıktan sonra Cast_Ray fonksiyonuna parametre olarak imlecKonum’u veriyoruz. İkinci parametre olarak ben false girdim ancak true girince de bir şey değişmiyor.

Scripti güncelledikten sonra Turret_Base objesindeki Turret_Control_CS component’inde yer alan “Joystick Script“e değer olarak “CrosshairHareketJoystick“i verin ve oyunu test edin:

12

  • KameraHareketJoystick’in Yarıçapını Dinamik Olarak Güncellemek

Yapmak istediğimiz şey, kamerayı hareket ettiren joystick’in hareket alanı yarıçapını, ekranın yüksekliğinin %10’una ayarlamak. Böylece her ekran çözünürlüğünde bu joystick benzer davranış sergileyecek.

Bu iş için var olan bir scripti kullanabileceğimiz gibi yeni bir script de yazabiliriz. Ben, projenin temiz olması açısından ikinci yolu tercih edeceğim ve “JoystickYaricapScript” adında yeni bir C# script oluşturacağım:

using UnityEngine;

public class JoystickYaricapScript : MonoBehaviour
{
    void Start()
	{
		GetComponent<JoystickUI>().hareketAlaniYaricap = Screen.height * 0.1f;
	}
}

Artık bu scripti KameraHareketJoystick objesine verebilirsiniz.

  • Ateş Etmek

Artık geriye sadece bonus part, yani tankın ateş etmesi kaldı. Şu anda tank Space tuşu ile ateş ediyor. Bu işlemi ise “Cannon_Base” objesindeki “Fire_Control_CS” scriptinin Update fonksiyonu kontrol ediyor. Bizim yapmak istediğimiz şey, ekrandaki bir butona basılı tuttuğumuz sürece tankın ateş edebilmesi. Bunun için scriptin Update fonksiyonunu güncelleyip üzerine iki tane de yeni fonksiyon ekleyelim:

private bool atesEt = false;

public void AtesEtmeyeBasla()
{
	atesEt = true;
}

public void AtesEtmeyiKes()
{
	atesEt = false;
}

void Update()
{
	if( atesEt && isReady )
	{
		// Send message to this and 'Barrel_Control' and 'Fire_Spawn'.
		BroadcastMessage( "Fire", SendMessageOptions.DontRequireReceiver );
	}
}

Ekrana koyacağımız butona dokununca AtesEtmeyeBasla, butondan parmağı çekince AtesEtmeyiKes fonksiyonları çağrılacak ve atesEt değişkeni true olduğu sürece tank ateş edebilecek.

GameObject-UI-Button ile yeni bir buton oluşturup bunu ekranda istediğiniz bir yere yerleştirin. Unity’nin Button component’i sadece butona basınca çalışan bir event (On Click) destekliyor, biz ise butona dokununca ve butondan parmağı çekince iki ayrı event çağrılmasını istiyoruz. Bunun için yapmamız gereken şey, butona “Component-Event-Event Trigger” ile bir Event Trigger component’i eklemek ve bu component’e şu eklemeleri yapmak:

13

The End

Bu yazının da burada sonuna gelmiş bulunmaktayız. Son noktayı koymadan önce ufak bir optimizasyon önerisinde bulunmak istiyorum. Eğer bu projedeki gibi birden çok joystick sprite‘ı kullandıysanız, her bir sprite ayrı bir draw call yer ve performansa olumsuz etki eder. Bunu çözmek için, kullandığınız sprite’ları seçip bunlara Inspector‘dan, aynı değere sahip bir “Packing Tag” verebilirsiniz (mesela JoystickAtlas). İşin özü, bu işlemi sadece joystick için değil ama oyununuzdaki diğer sprite’lar için de uygulayabilirsiniz. Sprite’larınız devasa olmadığı sürece ne kadar çok sprite’ı birlikte pack ederseniz o kadar az draw call’a ihtiyaç olur.

NOT: Oyunu Android‘de test etmek için Build Settings‘ten Android‘e geçiş yapın, Player Settings‘teki “Bundle Identifier“ı güncelleyin (mesela com.joystick.tankdemo) ve tercihen “Default Orientation“ı Landscape Left yapın. Oyunu açtıktan sonra multi-touch’ın gücüne tanık olun: üç parmağınız üç joystick’i aynı anda hareket ettirirken boş bulduğunuz bir başka parmakla ateş et butonuna basılı tutun ve tüm bu UI elemanlarının bir harmoni içerisinde çalışmasını hayretler içinde izleyin!

Sağlıcakla kalın, hayırlı ramazanlar.

yorum
  1. hakanince0 dedi ki:

    Selamun aleyküm hocam öncelikle iyi akşamlar. SimpleInputSystem assetinizi kullanıyorum. Yapmış olduğum 3D uçak oyununu kontrol edebilmek için joystick’i, horizontal ve vertical değerlere atadım. Horizontal ve vertical değerler -1 ve 1 arası değişmekte. Joystick’i de buna göre ayarladım. Ancak uçak çapraz yattığında veya çok hızlandığında joystick kontrolden çıkıyor, thumb deli gibi titriyor ve kendi kendine sağa sola döndürüyor. Tabi joystick’e dokunmazsam problem olmuyor ama dokunduğum anda benim sürüklediğim yere gitmiyor kendi kendine titriyor.

    Kontrol etmek için kullandığım kod:
    Pitch = SimpleInput.GetAxis(“Vertical”);
    Roll = SimpleInput.GetAxis(“Horizontal”);

    Pitch ve Roll, -1 ve 1 arasında float değerlerdir. Uçağı kontrol etmemi sağlıyor.

    • yasirkula dedi ki:

      Joystickin UI arayüzünün uçakla bu tarz bir bağlantısı olmaması lazım, şaşırdım. Özel bir kod ile, parmakla dokunmadan da joystick’in thumb’ının konumunu değiştiriyor musunuz?

      • hakanince0 dedi ki:

        Hocam olayı az önce çözmüş bulunmaktayım çözümünü yazayım belki başka arkadaşlarında işine yarar.

        Benim oyunumda 4 ayrı uçak bulunduğundan ve uçak değiştirdikçe arayüz tasarımlarıda değiştiğinden UI arayüzlerini ayrı ayrı yapmam gerekti ve bu UI canvasları uçakların içine ekledim child object olarak. UI arayüzünü uçağın içerisine attığımdan sanıyorum ki uçak hızlandıkça ve seri pozisyon/rotasyon değişikliği yaptığından UI elementlerinde sorun çıkarıyordu.

        UI Canvas’ı uçak objesinden çıkarınca düzeldi.

      • yasirkula dedi ki:

        Çözülmesine sevindim, elinize sağlık 😀

  2. Barış dedi ki:

    Hocam ben bunu tank yerine bir topa nasıl uyarlayabilirim?

    • yasirkula dedi ki:

      Öncelikle Input.GetAxis kullanarak klavye input’u ile topu hareket ettiren kodunuzu yazın. Akabinde dersten faydalanarak bu kodu joystick’e uyarlayabilirsiniz.

  3. Barış Samurkaş dedi ki:

    Merhaba Yasir Bey. Bu paket son unity sürümleriyle çalışmaya uygun mu?

  4. Barış dedi ki:

    Yasir Hocam lütfen yardım edin ilk defa bu kadar çok zorlandım. Benim tek yapmak istediğim şey kamera kontrolü ama mobil için yürüme, eğilme, koşma vs vs. hiçbirşey yok. Sadece ekrana dokunca kamera rotation ayarlayacak bu kadar (Ama UI ile panel oluşturup o panele dokunursak kamera hareket edecek diğer yerlere dokununca hareket etmeyecek )

  5. Sibel dedi ki:

    Merhaba ,
    Ben 2D yılan oyunu yapmaya çalışıyorum.Aslında çoğu şeyi yaptım ve ekranın alt kısmına sağ,sol,yukarı ve aşağı buton yaptım..ben telefonda oynamak isteyen birisinin ekranın alt kısmındaki bu yön tuşlarını kullanarak hareket etmesini istiyorum.Şu an “w,a,s,d” ile hareket ettiriyorum.Ama ben yaptığım yön butonlarına basıldığında da hareket etmesini istiyorum.
    Bunun için bir sürü yol denedim ancak olmadı..Yardımcı olabilir misiniz?

    Teşekkürler.

  6. Ali Emre dedi ki:

    Merhaba öncelikle elinize, emeğinize sağlık çok güzel iş çıkarmışsınız

    Bir sorunla karşılaştım ve sebebini bulamadım. Yardımcı olursanız sevinirim.

    JoystickUI script’indeki hareket edecek nesne atamasına sahnede bulunan player’ı atadığımda player hareket edebiliyor ancak sahneden değil de prefab klasorunden prefab olarak atarsam sahnedeki player hareket etmiyor. Bunu nasıl çözebilirim. Şimdiden teşekkürler

  7. mfrkndgnsoftware dedi ki:

    Merhaba Bu joystick FPS Controller Hareket Ettirebilirmi Ettirebilirse nasıl Lütfen Yardım

    • yasirkula dedi ki:

      Joystick’in sonuc değişkeni, tıpkı Input.GetAxis gibi [-1,1] aralığında değerler döndürür. Yani joystick’i public bir değişkende tutup sonuc’unun değerine göre FPS Controller’ı hareket ettirebilirsiniz. Biraz daha detaylı bilgi için lütfen derste paylaştığım örneği inceleyin.

Cevap Yazın

Bu site, istenmeyenleri azaltmak için Akismet kullanıyor. Yorum verilerinizin nasıl işlendiği hakkında daha fazla bilgi edinin.