Selamlar dostlar, bu yazımızda Unity Abstract Class ve Interface Kavramları üzerine konuşup, örneklerle pekiştireceğiz. Bu iki yapıyı kullanarak hem daha okunaklı, performanslı kodlar yazabilir hem de daha organize halde çalışabiliriz. Abstract Class ve Interface kavramları aslında inheritance ( miras alma ) konusunu içerisindedir. Basitçe tanımlamak gerekirse, Abstact Class objenin en temel halini tanımladığımız, Interface ile ise objelerin yapabileceklerini belirttiğimiz durumlarda kullandığımız özelliklerdir.
Şimdi Abstract Class ile başlarsak, objenin en temel hali olarak bahsetmiştik. Örnek olması amacıyla, bir elma nesnesi yaratacağımızı düşünelim. Bu elma nesnesi aslında oyun içerisinde envanterimize alabileceğimiz bir objedir. Yani en genel kapsayıcı haliyle bir envanter itemidir. Bir kılıç da aynı şekilde envantere alınabilir. Elma isimli nesneyi “meyveler” adlı bir sınıftan, kılıç nesnesini de “ekipman” isimli bir sınıftan üretebiliriz. Ancak bu iki sınıf da “envanterItemi” adlı abstract class üzerinden miras alacak ve ağırlık, envanter boyutu gibi değşikenlere veya kullanmak amacıyla fonksiyonlara sahip olacaktır. Bu ortak noktalardan dolayı abstract class kullanmak işinize yarayacaktır. Ayrıca belirtmekte fayda var, bir sınıf ancak 1 tane abstract sınıftan miras alabilir.
Hangi durumlarda kullanacağınızdan emin olamamanız halinde, genel bir sorgulama yöntemi kullanabilirsiniz. İngilizce’de “is-a” şeklinde bir soru yapısı kullanılıyor. Örneğin “fruit is an inventory item” kelimesini ele alırsak, Türkçe olarak “elma envanter itemi midir?” diye bir soru soruyoruz aslında. Bu soruyu sorarak kullanma konusundaki kararsızlığınızı giderebilirsiniz.
Interface kavramı ise, Abstract Class ile benzer yönlere sahip olsa da, en önemli farklarından biri, fonksiyonlar içerisinde komutlar yazamayız veya değişkenlere değer atayamayız. Interface içerisinde sadece bunların tanımlamalarını yapabiliyoruz. Daha sonrasında oluşturduğumuz inteface’i miras alan sınıflarda bu fonksiyonları ve değişken atamalarını yapabiliyoruz. Bu arada belirtmekte fayda var, değişken diyorum ama aslında properties-özellikler dediğimiz yapıları tanımlayabiliyoruz. Bunlar aracılığıyla da alt sınıflarda değişkenleri değiştirebiliyoruz. Ayrıca, abstract class kullanımından farklı olarak, bir sınıf birden fazla Interface üzerinden miras alma işlemini gerçekleştirebilir.
Kullanım konusuna geldiğimizde ise daha fazla kararsız kalmanıza sebep olabilecek bir yapı aslında bu. “Bu nesne ne yapabilir?” diye düşünmek biraz bizi kurtarıyor bu aşamada. Örneğin geometrik şekillerin alanını hesaplatmak, elde etmek için bir method kullanmak istediğimizde üreteceğimiz her şeklinde “AlanHesapla()” ismiyle bir fonksiyona sahip olmasını isteyebiliriz. Ancak, her sekilin alan hesabı için farklı işlemler yapılmakta. Kare’nin kenarları çarpılır, üçgenin yüksekliği ile tabanı çarpılıp ikiye bölünür. Ancak alan hesaplaması bunların ortak özelliğidir. Ya da az sonra yapacağımız örneği düşünürsek, kapıları açabilmemiz veya envanter itemlerini toplayabilmemiz bunlarla etkileşime girebileceğimiz manasında geliyor. Yani etkileşim kurulabilirler, ancak kurulan etkileşimin içeriği farklı olacaktır.
Kavramsal olarak temel şeylerin oturduğunu düşünüyorum. Şimdi basit bir örnek ile bu kavramları pekiştirmek istiyorum. Görselde göreceğiniz gibi, nesnelere tıklandıkça etkileşime girebildiğimiz ve her nesnede farklı tepkiler alabildiğimiz bir yapı oluşturacağız. Bunun için bir adet ITiklanabilir adlı Interface bir tane de EnvanterItemi adlı abstract class oluşturacağız.Arayüz isminin başına I harfini ekleme sebebim Interface kelimesinin ilk harfi olmasından dolayı. Bu sayede dosyalarınız ya da miras aldığınz şeylerin bir arayüz olduğunu ayırt edebilirsiniz. Genel olarak bu kullanıma alışmanız faydanıza olacaktır.
Unity Abstract Class ve Interface Örneği
Yapacağım örnek için, boş bir GameObject oluşturup buna animator ekledim. Ardından alt obje olarak 2 tane küpten bir adet kapı oluşturdum ve bu küpleri animasyon penceresi üzerinden düzenleyip ile açılıp kapanma animasyonları yarattım. 4 adet animasyon var, açılırken, kapanırken, açılmış hali sabitken ve kapalı hali sabitken. Animator kısmında da bir adet “Acik” isimli bir adet bool değişken oluşturdum ve 4 animasyonu buna göre aralarında geçiş yapacak şekilde ayarladım. Şu an asıl odağımız animasyon ve animator olmadığı için, onları başka bir yazıya bırakıyorum.
Öncelikle ITiklanabilir isimli dosyamızı oluşturalım. İçerisinde tıkladığımız şeyin ismi olsun ve bir de “Etkilesim” isimli bir fonksiyon tanımlaması olsun istiyorum. İsim değişkeni sayesinde hangi nesneye tıkladığımız bilgisini de elde edebiliriz.
public interface ITiklanabilir {
//isim değişkenini elde edebilmek için bir özellik tanımlıyoruz.
string Isim {
get;
set;
}
//etkileşim için bir fonksiyon tanımlıyoruz
//komutları miras alan sınıflarda yazacağız.
void Etkilesim ();
}
Daha sonrasında da temel sınıf olarak kullanacağımız EnvanterItemi isimli abstract sınıfı oluşturacağız. Bunun içerisinde şu anlık sadece agirlik isimli bir değişken ve EnvantereEkle isimli bir fonksiyon oluşturacağız. Örneğin basitliği adına gerçekten bir envantere eklemek yerine objeyi yok edeceğiz.
public abstract class EnvanterItemi : MonoBehaviour {
public float agirlik;
public virtual void EnvantereEkle () {
Destroy (gameObject);
}
}
Artık objelerimizi temsil edecek sınıfları oluşturabiliriz. Şimdi sırasıyla Kapi, Meyve, Ekipman isimli sınıfları oluşturacağım. Detaylar için yorum satırlarını inceleyebilirsiniz.
public class Kapi : MonoBehaviour, ITiklanabilir {
[SerializeField]
string isim;
public string Isim {
get =>
isim;
set =>
isim = value;
}
Animator animator;
//ilk olarak kapalı halde tanımlıyoruz
bool acikMi = false;
void Start () {
//animator bileşenine erişiyoruz
animator = GetComponent<Animator> ();
}
public void Etkilesim () {
//! işareti kullanarak var olan değerinin tersini, yani
//true ise false, false ise true değerini almasını sağlıyoruz.
acikMi = !acikMi;
//kapı animasyonunu oynatmak için animatordeki
//bool değerini değiştiriyoruz.
animator.SetBool ("Acik", acikMi);
Debug.Log (Isim + " adlı kapı açıldı");
}
}
public class Meyve : EnvanterItemi, ITiklanabilir {
[SerializeField]
string meyveIsmi = "Pippin'in Kafasına Çarpan Elma";
public string Isim {
get =>
meyveIsmi;
set =>
meyveIsmi = value;
}
public void Etkilesim () {
Debug.Log (Isim + " adlı obje ile etkileşime girildi");
EnvantereEkle ();
}
public override void EnvantereEkle () {
//örnek olması amacıyla bu sefer base komutu kullamayacağım
// bu yüzden objemiz yok olmayacak
Debug.Log (Isim + " adlı meyve envantere eklendi");
}
}
Son olarak, tıklama kontrolü için “EtkilesimKontrol” isimli bir sınıf oluşturalım. Yorum satırlarında detaylarını bulabilirsini ama basitçe, farenin ekrandaki pozisyonunu kullanarak bir ray atıp herhangi bir objeye erişebiliyor muyuz bunu kontrol ediyoruz. Kodu oluşturduktan sonra kameraya atamayı unutmayın.
public class EtkilesimKontrol : MonoBehaviour {
Camera kamera;
void Start () {
kamera = GetComponent<Camera> ();
}
void Update () {
//eğer fare ile sol tık yapıldıysa
if (Input.GetMouseButtonDown (0)) {
//ScreenPointToRay fonksiyonu sayesinde
//verdiğimiz ekran konumunu kullanarak
//3 boyutlu sahnemiz içerisinde bir ray elde edebiliyoruz.
//
Ray etkilesimIsini = kamera.ScreenPointToRay (Input.mousePosition);
RaycastHit etkilesimBilgisi;
//Raycast fonksiyonu içerisine parametre olarak kullanmak istediğimiz ışını
//ve bu raycast işlemi sonrasında sonucu kaydetmesi için
//etkilesimBilgisi adlı değişkeni veriyoruz
if (Physics.Raycast (etkilesimIsini, out etkilesimBilgisi)) {
//eğer attığımız ışın bir objeye temas ettiyse
//temas edilen objenin ITiklanabilir isimli bileşenine
//erişmeye çalışıyoruz.
ITiklanabilir tiklananObje = etkilesimBilgisi.collider.GetComponentInParent<ITiklanabilir> ();
//Eğer ITiklanabilir bileşenine sahip ise etkileşim fonksiyonun çağırıyoruz.
if (tiklananObje != null) {
tiklananObje.Etkilesim ();
}
}
}
}
}
Böylece artık yazının önceki kısımlarında gördüğünüz gibi tıklamayla etkileşimleri gerçekleştirmiş olduk. Şayet hiçbir etki alamıyorsanız, objelerinizde Collider bileşeninin olduğundan emin olun. Raycast işleminin başarıyla çalışması için etkileşime girmek istediğiniz objelerde Collider olması gerekiyor. Ayrıca, scriptleri objelere atamayı unutmadığınızdan emin olun. Ufak şeyler gözden daha kolay kaçıyor.
Unity Abstract Class ve Interface Kavramları konusu da temel olarak tamamlanmış oldu. Elbette ki yapılabilecek bir sürü yapı, öğrenilecek bir sürü şey var. Umuyorum ki, basit ve doğru bir şekilde Unity üzerinden Abstract Class ve Interface kavramlarını size aktarabilmişimdir. Kullandığımı 3d modellerin linklerini yazının devamına ekliyorum. Bir dahaki yazımızda görüşmek dileğiyle.
Kullandığım Assetler:
- Kılıç Objesini Aldığım Asset: Seven Swords
- Elma Objesini Aldığım Asset : Survival Kit Lite