Unity 3D Editör Scripti Yazmak – 2 – PropertyDrawer

Yayınlandı: 03 Kasım 2019 yasirkula tarafından Oyun Tasarımı, UNITY 3D içinde

Merhabalar,

Bu derste, Unity‘nin Inspector’undaki değişkenlerin görünümlerini, PropertyDrawer vasıtasıyla nasıl değiştirebileceğimize bakacağız. Örneğin Color türündeki değişkenlerin Inspector’a RGB değerleriyle birlikte çizilmelerini sağlayacağız:

Hazırsanız başlayalım!

Öncelikle Project panelinde Editor isminde bir klasör oluşturun. Editör script’leri daima Editor klasörü içerisine eklenmelidir. Klasörü oluşturduktan sonra, klasörün içinde ColorDrawer adında bir C# script’i oluşturun ve içeriğini şöyle değiştirin:

using UnityEditor;
using UnityEngine;

[CustomPropertyDrawer( typeof( Color ) )]
[CustomPropertyDrawer( typeof( Color32 ) )]
public class ColorDrawer : PropertyDrawer
{
	public override void OnGUI( Rect konum, SerializedProperty property, GUIContent degiskenIsmi )
	{
		EditorGUI.BeginProperty( konum, degiskenIsmi, property );
		konum = EditorGUI.PrefixLabel( konum, degiskenIsmi );

		float ceyrekGenislik = konum.width / 4f;
		Rect rKonum = new Rect( konum.x, konum.y, ceyrekGenislik, konum.height );
		Rect gKonum = new Rect( konum.x + ceyrekGenislik, konum.y, ceyrekGenislik, konum.height );
		Rect bKonum = new Rect( konum.x + 2f * ceyrekGenislik, konum.y, ceyrekGenislik, konum.height );
		Rect renkKonum = new Rect( konum.x + 3f * ceyrekGenislik, konum.y, ceyrekGenislik, konum.height );
		Color renk = property.colorValue;

		EditorGUI.BeginChangeCheck();
		int r = EditorGUI.IntField( rKonum, (int) ( renk.r * 255 ) );
		if( EditorGUI.EndChangeCheck() )
		{
			renk.r = r / 255f;
			property.colorValue = renk;
		}

		EditorGUI.BeginChangeCheck();
		int g = EditorGUI.IntField( gKonum, (int) ( renk.g * 255 ) );
		if( EditorGUI.EndChangeCheck() )
		{
			renk.g = g / 255f;
			property.colorValue = renk;
		}

		EditorGUI.BeginChangeCheck();
		int b = EditorGUI.IntField( bKonum, (int) ( renk.b * 255 ) );
		if( EditorGUI.EndChangeCheck() )
		{
			renk.b = b / 255f;
			property.colorValue = renk;
		}

		EditorGUI.BeginChangeCheck();
		renk = EditorGUI.ColorField( renkKonum, renk );
		if( EditorGUI.EndChangeCheck() )
			property.colorValue = renk;

		EditorGUI.EndProperty();
	}
}

Artık Color veya Color32 türündeki değişkenlere Inspector’dan bakarken, değişkenlerin RGB değerleriyle beraber ekrana çizildiğini göreceksiniz. Peki bu script nasıl çalışıyor?

  • Kullandığımız CustomPropertyDrawer attribute‘ları, bu class’ın Color ve Color32 türündeki değişkenleri Inspector’a çizdirmeye yaradığını Unity’e söylemekte.
  • CustomPropertyDrawer eklemeye ilaveten, class PropertyDrawer sınıfından türemek zorunda.
  • Arayüz elemanlarını çizdirme işlemini OnGUI fonksiyonunda yapıyoruz. Bu fonksiyon 3 parametre almakta:
    • konum: bu değişkeni Inspector’da hangi koordinatlara çizeceğimizi depolar
    • property: değişkene erişmeye yarar, SerializedProperty aslında ayrı bir ders yazacak kadar kapsamlı bir konu ama biz bu derste üzerinden basitçe geçeceğiz
    • degiskenIsmi: değişkenin ismini tutar
  • PropertyDrawer’lar GUILayout desteklememekte, o yüzden arayüz elemanlarını GUI ve EditorGUI fonksiyonları vasıtası ile, konum‘un olduğu koordinatlarda çizdirmek zorundayız.
  • Sistemin düzgün çalışması için, OnGUI’nin başında EditorGUI.BeginProperty fonksiyonunu çağırmak zorundayız, bu fonksiyon ne işe yarar derseniz ben de tam bilmiyorum.
  • EditorGUI.PrefixLabel fonksiyonu, konum koordinatlarının sol tarafına değişkenin ismini yazar ve sağ tarafta kalan boş alanın koordinatlarını döndürür:
  • Sonraki satırlarda, rengin RGB değerlerini ve rengin kendisini hangi koordinatlarda çizeceğimizi hesaplıyoruz. Bu 4 bileşenin her biri, konum koordinatlarının genişliğinin 1/4’ü genişlik kaplıyor. konum.x, konum’un sol kenarının pozisyonunu tutarken konum.y de konum’un üst kenarının pozisyonunu tutar. konum.width konum’un genişliğini, konum.height da konum’un yüksekliğini döndürür. new Rect constructor‘ı ise, yeni konum değişkeninin x, y, genişlik ve yükseklik değerlerini parametre olarak alır.
  • Color renk = property.colorValue; satırında, değişkenin değerine erişiyoruz. Unity’de her şey SerializedProperty‘ler vasıtasıyla diske kaydedilmekte, yani oyununuzda yer alan her değişkenin değeri arkaplanda pek çok SerializedProperty objesinde depolanmakta. Color veya Color32 değişken tutan bir SerializedProperty’deki rengin değerine colorValue ile erişilir.
  • EditorGUI.BeginChangeCheck ve EditorGUI.EndChangeCheck fonksiyonları beraber kullanılırlar ve bu iki fonksiyon arasında ekrana çizdirdiğiniz arayüz elamanlarında bir değişiklik meydana gelirse (mesela kullanıcı oradaki input’un değerini değiştirirse), EditorGUI.EndChangeCheck fonksiyonu true döndürür.
  • Rengin RGB değerlerini sırasıyla kendi koordinatlarında çizdiriyoruz. Bunun için EditorGUI.IntField kullanıyoruz çünkü RGB değerleri [0,255] aralığında bir int değer alırlar. Ancak Color32’nin aksine Color türü, RGB değerlerini [0,1] aralığında float olarak tutar. Biz de bu aralığı [0,255]’e genişletmek için rengin RGB bileşenlerini 255 ile çarpıp int’e çeviriyoruz.
  • Eğer kullanıcı RGB bileşenlerinden herhangi birisini değiştirirse, EditorGUI.EndChangeCheck if koşulunun içine giriyoruz ve burada o bileşeni 255’e bölerek tekrar [0,1] aralığına daraltıyoruz. Ardından da değişkenin değerini SerializedProperty’nin colorValue‘si vasıtasıyla değiştiriyoruz.
  • RGB bileşenlerinden sonra, EditorGUI.ColorField vasıtasıyla rengin kendisini de ekrana çizdiriyoruz.
  • EditorGUI.BeginProperty ile başlattığımız kodu, EditorGUI.EndProperty ile bitiriyoruz.

Kabaca sistemi anlatabilmişimdir diye ümit ediyorum. Kodda hiç Undo fonksiyonu kullanmadık çünkü PropertyDrawer sınıflarında BeginProperty ve EndProperty fonksiyonları arasında SerializedProperty’e yapılan değişiklikler, otomatik olarak undo-redo destekliyor.

Yazdığımız bu script vasıtasıyla Color/Color32 array’lerinin elemanları da yeni stilde ekrana çizdiriliyorlar:

Buradaki tek sıkıntı, RGB bileşenlerinin hepsinin başında biraz boşluk olması; çünkü Unity, array elemanlarını otomatik olarak biraz sağda çizdirir (indentation). Ama eğer dilerseniz bu sorunu çözmek çok kolay; EditorGUI.PrefixLabel fonksiyonundan hemen sonra EditorGUI.indentLevel = 0; satırını ekleyerek indentation’ı sıfırlayabilirsiniz:

Şimdi dilerseniz bir başka PropertyDrawer örneği daha görelim. Bu örnekte, kendi yazdığımız basit bir class’ın Inspector’daki görünümünü değiştireceğiz. Class’ımız şu:

[System.Serializable]
public class TestClass
{
	public Vector3 vektor;
	public string yazi;
}

İsmi TestClass ve içerisinde bir Vector3 ile bir string tutuyor. TestClass türündeki değişkenlerin Inspector’da gözükebilmesi için, class’ın System.Serializable attribute’una sahip olması lazım (Serializable ve SerializedProperty kelimeleri arasındaki benzerliğe dikkat ettiniz mi 😉 ). Şu anda TestClass türünde değişkenler, Inspector’da şu şekilde gözükmekte:

Değişkenlerimizin başında açılıp kapanabilir bir başlık bulunmakta (Test Class, Element 0, Element 1). Hem bu başlığı elle açmak zorunda olmak vakit kaybı hem de bu başlığın kapladığı 1 satırlık alan, Inspector’da yer israfı. Bu sorunları çözmek için, biz bu TestClass değişkenlerimizin şöyle görünmesini istiyoruz:

Bunun için, Editor klasöründe TestClassDrawer adında yeni bir C# script’i oluşturalım:

using UnityEditor;
using UnityEngine;

[CustomPropertyDrawer( typeof( TestClass ) )]
public class TestClassDrawer : PropertyDrawer
{
	public override void OnGUI( Rect konum, SerializedProperty property, GUIContent degiskenIsmi )
	{
		EditorGUI.BeginProperty( konum, degiskenIsmi, property );
		konum = EditorGUI.PrefixLabel( konum, degiskenIsmi );
		EditorGUI.indentLevel = 0;

		float yarimYukseklik = konum.height / 2f;
		Rect vektorKonum = new Rect( konum.x, konum.y, konum.width, yarimYukseklik );
		Rect yaziKonum = new Rect( konum.x, konum.y + yarimYukseklik, konum.width, yarimYukseklik );

		EditorGUI.PropertyField( vektorKonum, property.FindPropertyRelative( "vektor" ), GUIContent.none );
		EditorGUI.PropertyField( yaziKonum, property.FindPropertyRelative( "yazi" ), GUIContent.none );

		EditorGUI.EndProperty();
	}

	public override float GetPropertyHeight( SerializedProperty property, GUIContent label )
	{
		return EditorGUIUtility.singleLineHeight * 2f;
	}
}
  • Burada ekstradan GetPropertyHeight fonksiyonu bulunuyor. Değişken ekrana çizilirken birden çok satır kaplamasını istiyorsanız, bunu bu fonksiyon vasıtasıyla belirtiyorsunuz. EditorGUIUtility.singleLineHeight değişkeni, Unity’deki standart 1 satırın yüksekliğini döndürür. Biz değişkeni 2 satırlık bir alana çizmek istiyoruz. Böylece OnGUI‘ye aktarılan konum parametresi, 2 satır yüksekliğinde olacak.
  • OnGUI fonksiyonunda tek fark, değişkenleri ekrana EditorGUI.PropertyField ile çizdirmemiz. Bu fonksiyon parametre olarak bir SerializedProperty alır ve onu belirtilen koordinatlarda çizdirir. Normalde 3. parametre olarak değişkenin ismi girilir ve PropertyField fonksiyonu, değişkenin ismini de ekrana çizdirir ama biz zaten değişkenin ismini PrefixLabel fonksiyonu ile ekrana çizdiğimiz için, boş bir değişken ismi olan GUIContent.none‘ı 3. parametreye değer olarak veriyoruz.
  • PropertyField fonksiyonuna SerializedProperty parametresini verirken, property değişkenimizin FindPropertyRelative fonksiyonunu kullanıyoruz. Burada property bizim TestClass değişkenimizi tutmaktadır. Bu değişkenin alt değişkenlerine, yani TestClass’ın kendi değişkenlerine erişmek için FindPropertyRelative kullanıyoruz. Parametre olarak da değişkenin ismini giriyoruz. Bu fonksiyon ise bize, içerisinde o alt değişkeni tutan başka bir SerializedProperty döndürüyor.
  • PropertyField fonksiyonunu BeginChangeCheck ve EndChangeCheck ile çevrelemedik çünkü PropertyField fonksiyonunun bir avantajı, SerializedProperty’de yapılan değişiklikleri otomatik olarak algılayıp değişkeni gerektiğinde güncellemesi.

Böylece geldik bir dersin daha sonuna. O halde bir sonraki derste görüşmek üzere, esen kalın!

yorum
  1. emobey09 dedi ki:

    Yasir abi hani bişey vardı aklıma gelmiyor
    2 farklı şeyi bir isimle tanımlıyorduk
    mesela bunun gibi text ile inti ayrı değil “asd” diye kaydetmek
    public text[] asd;
    public int[] dsa;
    değilde
    public asd[] dsa;

  2. Barış dedi ki:

    Abi sen Bilkent Üniversitesinde okuyordun bildiğim kadarıyla sana çok önemli sorularım olacak yasirkula@gmail.com adresini about me bölümünden buldum. Bu adresi kullanıyorsun değil mi? Sana ulaşmam gerek.

  3. Yasin dedi ki:

    Merhaba. Konu ile alakalı olmayan bir sorum olucak. Unity 3d İle Bir Mobil Oyun Tasarladım. Program Üzerinden Test Ederken Herşey Sorunsuz Çalışıyor Fakat Telefon Ekranında Herşey Farklı Geliyorz. Bu Konu İle Alakalı Bir Bilginiz Varmıdır. Yardımcı Olursanız Sevinirim. Şimdiden Teşekkürler.

  4. Ömer Kulaoğlu dedi ki:

    Merhabalar sitenizde araştırma yaptım ve gerçekten çok beğendim emeğinize sağlık.Benim araştırdığım bir konu var mobil için parmakla kaydırılabilir bi menü yapmaya çalışıyorum içindeki bütün objelerle birlikte sürükleyip kaydırmak istiyorum.(bunlar UI yada GUI tuşlar değil sadece OnMouseDown kullanılmış 2d objeler.(yapmak istediğim basit bir instagram kaydırma sistemi sadece vertical olanı)) .Bunun için sitenizde ders varmı acaba ? Yeterince araştırmama rağmen bulamadım.Yada kaynak önerirmisiniz ? İyi günler 🙂

    • yasirkula dedi ki:

      Şu script işinize yarar diye düşünüyorum: https://stackoverflow.com/a/47105887/2373034. Bu kodda tek değiştirmeniz gereken şey, “if(Input.GetMouseButtonDown(0))” koşulunun içindeki kodu OnMouseDown fonksiyonuna taşımak.

      • Ömer Kulaoğlu dedi ki:

        Teşekkür ederim yönlendirdiğiniz sitedeki kod çalışıyor.Üzerinde ufak oynamalarla gayet de kullanilabilir.Parmağımı kaldırdıktan sonra direk durmaması için daha yavaş bir şekilde durması için hangi yöntemi izlemeliyim acaba ?Ne tür bir kod eklemek gerekir ?

      • yasirkula dedi ki:

        Objenin son hızını bir şekilde hesaplayıp, parmak ekranda değilse ve bu hız 0’dan büyükse objeyi bu hız kadar hareket ettirmeniz ve aynı zamanda hızı bir miktar azaltmanız lazım. Son hızı hesaplama işini, parmak ekranda iken parmağın objeyi hareket ettirdiği kodda yapmayı deneyebilirsiniz: objenin son konumu ile ilk konumu arasındaki farkı Time.deltaTime’a bölünce kabaca bir hız elde edebilirsiniz.

  5. Ahmet Yıldız dedi ki:

    Merhaba Yasir Bey,
    Unity3d ‘de yaşadığım bir sorun var ve internette aradığım halde bulamadım belki sizin başınıza gelmiştir diye sormak istiyorum. Umarım zaman ayırıp beni cevapsız bırakmazsınız çünkü bu konu benim için çok ciddi bir konu.

    Elimde çok yoğun 3d bir sahne var ve gittikçe yoğunlaşıyor. Elimdeki makine yani bilgisayar çok kuvvetli. Fakat unity3d bunu kullanamıyor. Tam performanslı çalışmıyor. Normal çalıştığım editörde çalışırken zorlanıyor fakat build ettiğimde .exe olarak bir bakıyorsunuz coşuyor. Unity3d, geliştirme yaptığım alanda bilgisayarın tek bir çekirdeğini kullanıyor fakat build erttikten sonra 100’ünüde bir şekilde kullanıyor. Bu neden olabilir bunun önüne nasıl geçebilirim. İstediğim bir vertical sync yi açıp kapatın target fps yapın gibi bir şey değil. Ben gerçekten unity3d’nin hem ekran kartını hemde cpu’yu tam anlamıyla kullanmasını sağlayan bir ayardan bahsediyorum.

    Umarım derdimi anlatabillmişimdir. Cevabınız olması dileğiyle, kolay gelsin.

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.