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
Etiketler:, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,

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. ahmet dedi ki:

    Merhaba hocam başarılı bi ders olmuş, ama ilk paylaştığınız küp projesine uyarlarlanmış halini atarmısınız bu UI dersinin.

  2. unity game dedi ki:

    dostum ellerine saglık yine çok yararlı bir ders olmus 😀
    bu hareket joystigi benim koduma uyarlanabilirmi acaba yardım edebilirmisin ?

    • yasirkula dedi ki:

      Her türlü koda uyarlanabilir. En neticede durum, joystick’in döndürdüğü Vector2’den nerede ve nasıl faydalanılacağına bakıyor. Dersteki örneği inceleyince nasıl olacağını anlarsınız hemen.

  3. unity game dedi ki:

    dostum joystick uı scriptindeki joystick image e joystick atmaya calısıyorum ama olmuyor neden ?

  4. Taner Apps dedi ki:

    Merhaba hocam. Derslerinizi takip ediyorum. Dersten Faydalanarak şu oyunu yaptım ama bi mahsuru varmıdır? Sormak isterim. . https://play.google.com/store/apps/details?id=com.TanerApps.Maze3D

  5. unity game dedi ki:

    Dostum android için 2 bir oyun yapıyorum bir butona dokununca karakterin zıplamasını nasıl yapabilirim?

    • yasirkula dedi ki:

      Karakterin zıplama kodunu “public function Zipla()” isminde bir fonksiyonun içerisine yazın. Ardından butona Event Trigger component’i verip bir “Pointer Down” event’i ekleyin. Bu event’e de kodunuzdaki Zipla fonksiyonunu ekleyin. Button’un kendi “On Click”i yerine Event Trigger’ın “Pointer Down”ını kullanıyoruz çünkü butona dokunur dokunmaz karakterin zıplaması lazım, butona dokunup parmağı çekince değil.

      • unity game dedi ki:

        çok saol hallettim dostum ancak karakter butona her tıkladıgımda sürekli zıplıyor ben bunu sadece 2 kere zıplatmak istiyorumnasıl yapabilirim kodum şöyle :

        Vector3 vec;
        bool ziplamaKontrol = true;
        void Update ()
        {
        if (Input.GetMouseButtonDown (0))
        {
        if (ziplamaKontrol == true) {
        vec = new Vector3 (0, 350, 0);
        GetComponent ().AddForce (vec);
        ziplamaKontrol = false;
        }
        }
        }
        void onCollisionEnter(Collision col)
        {
        ziplamaKontrol = true;
        }
        }

      • unity game dedi ki:

        https://www.youtube.com/watch?v=JVw6Yg2Np1I bu linkteki videodan izleyerek şu kodu denedım hata vermiyor ancak hiç zıplamıyorda karakter neden olabilir acaba çift zıplamayı bi yapamadım 😦 kod şöyle :
        Vector3 vec;
        int counter = 0;
        bool ziplamaKontrol = false;

        public void ziplaa ()
        {
        if (Input.GetMouseButtonDown (0) && ziplamaKontrol) {
        vec = new Vector3 (0, 350, 0);
        GetComponent ().AddForce (vec);
        counter++;
        if (counter == 2) {
        ziplamaKontrol = false;
        counter = 0;
        }
        }
        }
        void onCollisionEnter(Collision col)
        {
        Debug.Log(“ds”);
        ziplamaKontrol = true;
        counter = 0;
        }

        }

      • yasirkula dedi ki:

        onCollisionEnter’ın ilk harfini büyük yazın (OnCollisionEnter) ve ziplaa fonksiyonunu Update ile değiştirmeyi deneyin. Ayrıca ziplamaKontrol’un ilk değerini true vermenizi tavsiye ederim.

  6. faik vural dedi ki:

    Hocam bu kadar uğraşmak yerine direk unitynin sağladığı cross platform(zaten kullanmışsın ama) uı kısmınıda onunkini kullanıp ınput namelerini (horizontal ve verticalle) kullanıp kısa zamanda ve hatasız kullanabilirsiniz Arkadaşlarda bu kadar uğraşmaz ve kendilerini hızlı bir şekilde geliştirebilirler Saygılarımla Vesselam 🙂

    • yasirkula dedi ki:

      Cross platform input’u kullanmadım ancak gelişmiş bir sistem olduğuna eminim. Yanlış bilmiyorsam o sistem joystick dışında başka input türlerini de destekliyor; her input’u desteklemek için de kendi içerisinde daha karmaşık bir kod kullanılıyor. Ben sadece joystick kullanacaksam, daha hafif olması ve direkt Joystick’e odaklanmış olması dolayısıyla kendi kodumu tercih ederim ama farklı input türlerini bir arada kullanacaksam dediğiniz gibi cross platform daha iyi bir çözüm olabilir.

  7. Multi touch dedi ki:

    Merhaba benim android için yaptıgım 2d bi oyunum var oyunda joystickle karakteri sagsol yaptırıyorum bide butonum var uı button bunalda karakteri zıplatıyorum ancak ikisinide aynı anda kullanamıyorum birini kullanırken diğeri çalısmıyor nasıl yapabilirim

    • yasirkula dedi ki:

      Joystick multi-touch destekliyor. Öyle ki örnek projede aynı anda 3 joystick ve bir UI button kullanabiliyoruz. Sizde aynı anda çalışmamaları garip geldi.

  8. assassinfurkan dedi ki:

    resmi sürüklüyorum ancak kabul etmiyor yukarıda değer olarak vericeksin demişsin ama nasıl olucak yardımcı olur musun

  9. Yunus dedi ki:

    Merhaba Hocam Joyistici hareket ettirdiğimde karakter hareket etmiyor ve” Hareket alanı yarıçapının değeri oyun sırasında inspectordan değiştirilmemelidir
    ” hatası veriyor nasıl düzeltebilirim. Teşekkürler

    • yasirkula dedi ki:

      O uyarı mesajı önemsiz bir şey, ilgili değerin oyun esnasında değiştirilmemesi gerektiğini bildiriyor. Joystick’i karakterin hareket scripti ile entegre etmek için örnek anlatımı baştan sona okuyunuz.

  10. yunus dedi ki:

    Merhaba hocam yine rahatsız ediyorum ama hareket kodu bu şekilde mi olucak:

    using UnityEngine;
    using System.Collections;

    public class NewBehaviourScript : MonoBehaviour {

    }

    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 );
    }
    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 ) ;
    }

    Bu şekilde yaptığımda void kelimesinin altında:
    “expected class delegate enum interface or struct” hatası veriyor nasıl düzeltebilirim?
    Teşekkürler.

    • yasirkula dedi ki:

      Fonksiyon NewBehaviourScript class’ının içerisinde olmalı. Şu anda class’ın dışında. Kod böyle olmayacaktır herhalde çünkü bu kod tank oyunu için yazılmış, sizin kendi kodunuzu kendinizin yazması lazım.

  11. ilkcan dedi ki:

    Hocam ben scripti Joystick Hareket Event ın içine atıyorum fakat fonksiyonları gözükmüyor no function yazıyor haliyle çalışmıyor ne yapmak gerek?

    • yasirkula dedi ki:

      Hareket Event’e scripti değil ama scripte sahip GameObject’i verdiğinizi varsayıyorum. Bu durumda fonksiyon public void olduğu ve Vector2 türünde bir değişken aldığı sürece listede gözükmesi lazım. Takılırsanız örnek anlatımı inceleyebilirsiniz, orada Hareket Event’i kullandık.

      • ilkcan dedi ki:

        Teşekkürler hocam o sorunu hallettim fakat yine çalışmıyor joystick hareket ediyor ama nesne hareket etmiyor dün geceden beri uğraşıyorum 2 boyutlu oyunda hareket kodları farklı olabilir mi acaba?

      • yasirkula dedi ki:

        Olabilir elbet. Normalde 2D bir oyunda x ve y eksenlerinde hareket edilir ama kameranın açısına göre bu eksenler farklı da olabilir.

  12. ilkcan dedi ki:

    Yeni bir kod yazdım oldu hocam Teşekkür ederim.

  13. ilkcan dedi ki:

    Merhaba hocam
    Ben bu joystick i oyunumda kullanıyorum kendi telefonumda sorunsuz çalışıyor fakar farklı çözünürlükteki telefonlarda joystickin yarıçapı büyüyor ve joystick arkaplanından çıkıyor bu sorunu düzeltmenin bir yolu var mı?

    • yasirkula dedi ki:

      Joystick hareket ederken arkaplanın çok uzağına mı gidebiliyor yoksa bahsettiğiniz sıkıntı başka bir şey mi?

      • ilkcan dedi ki:

        Arkaplanının çok uzağına gidiyor. Kendi telefonumda arkaplana uygun olarak çalışıyor fakat küçük çözünürlüklü telefonlarda joystick yarıçapı ekranın yarısına kadar gelebiliyor arkaplanında baya bi uzağına gidiyor dolayısıyla.

      • yasirkula dedi ki:

        Örnek projedeki JoystickYaricapScript gibi bir kod işinize yarar diye düşünüyorum. Yapmanız gereken şey, joystick’in hareketAlaniYaricap değişkeninin değerini oyunun başında kod vasıtasıyla değiştirmek.

  14. Yekta dedi ki:

    Hocam merhaba, kolay gelsin.

    Yuvarlanan bir sphere var, kuvvet uyguluyorum buna (sizin küp hareket scriptinizi kullandım), hızlanıyor fakat hızlandığı ile ölçüde dönmüyor. Yani Cisim hızlanıyor ama kendi etrafında az tur atıyor. Bunu nasıl çözebiliriz ?

  15. Yekta dedi ki:

    Hocam yeri değil belki ama bir sorum olacak. Yukarı aşağı inip çıkan bir zemin var, ben kübümü bu zeminin üstüne getirdiğimde zeminle küp beraber çıkarken titreme yapıyor. Sürekli titriyor normalde bir sorun olmuyor fakat üzerine geldiğimizde böyle oluyor. Nasıl çözebilirim ?

    • yasirkula dedi ki:

      Küp yüzeyle temas edince bir boolean değişkeni true yapın (mesela zemininUzerinde isimli bir değişken), zeminle temas kesilince bu boolean’ı false yapın. FixedUpdate fonksiyonunda da zemininUzerinde true ise küp objesine aşağı yönde bir miktar güç uygulayın. Hareketli zeminlerde sizin yaşadığınız gibi sıkıntı çıkabiliyor, ama bu tarz ufak ayarlarla bu sıkıntılar aşılabilir.

Bir Yanıt Bırakı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. Log Out / Değiştir )

Twitter resmi

Twitter hesabınızı kullanarak yorum yapıyorsunuz. Log Out / Değiştir )

Facebook fotoğrafı

Facebook hesabınızı kullanarak yorum yapıyorsunuz. Log Out / Değiştir )

Google+ fotoğrafı

Google+ hesabınızı kullanarak yorum yapıyorsunuz. Log Out / Değiştir )

Connecting to %s