Unity 3D ile Nesne Yönelimli Programlama serisinin üçüncüsünde “inheritance”ı konu alıyoruz.
Inheritance, en basit anlatımıyla bir nesnenin özelliklerini, türetildiği bir başka nesneden miras almasıdır. Günlük hayattan örnek vermek gerekirse hepimiz fiziksel ve/veya genetik özelliklerimizi anne ve babamızdan miras alırız. Bir insan, insan olma, özelliklerini biyolojik anlamda primatlardan, primatlar ise memeli hayvanlar sınıfından benzer özellikleri miras alırlar. Bu temel akışı istersek karbon atomlarına kadar takip edebiliriz. Peki bunu programlama esnasında nasıl kullanırız nasıl anlatırız?
Öncelikle kendimize bir senaryo oluşturalım.
Oyunumuzda yönettiğimiz bir karakterin olduğunu ve bu karakterin düşman karakterleri olduğunu. Kendi karakterimizin ve düşmanların birbirine ateş ettiğini ve canlarının azaldığını farzedelim.
Inheritance olmadan bu iki objenin sınıflarını aşağıdaki gibi yazardık.
public class Player { public string name; public int health; public int damage; public bool isDie; public void HitDamage(int enemyDamage) { health -= enemyDamage; if (health <= 0) { isDie = true; } else { isDie = false; } } } public class Enemy { public string name; public int health; public int damage; public bool isDie; public void HitDamage(int enemyDamage) { health -= enemyDamage; if (health <= 0) { isDie = true; } else { isDie = false; } } } public class Main { public Main() { Player player = new Player(); player.name = "MyHero"; player.health = 100; player.damage = 10; player.isDie = false; Enemy enemy = new Enemy(); enemy.name = "enemy1"; enemy.health = 80; enemy.damage = 5; enemy.isDie = false; player.HitDamage(enemy.damage); } }
Player ve Enemy hasar (damage) aldıklarında canları (health) sıfır veya sıfırdan küçük olduğunda karakter ölmüş oluyor.
Bu iki sınıfı (class) incelediğimizde aynı özelliklere sahip alanlar ve aynı işlemi yapan “HitDamage” metodumuz var. Peki bir sınıfımız olsa ve Player ve Enemy sınıflarının özelliklerini taşısaydı bu senaryoyu nasıl yazardık?
public class BaseCharacter { public string name; public int health; public int damage; public bool isDie; public void HitDamage(int enemyDamage) { health -= enemyDamage; if (health <= 0) { isDie = true; } else { isDie = false; } } } public class Main { public Main() { BaseCharacter player = new BaseCharacter(); player.name = "MyHero"; player.health = 100; player.damage = 10; player.isDie = false; BaseCharacter enemy = new BaseCharacter(); enemy.name = "enemy1"; enemy.health = 80; enemy.damage = 5; enemy.isDie = false; player.HitDamage(enemy.damage); } }
Burada BaseCharacter sınıfından iki nesne tanımladık. player ve enemy nesneleri BaseCharacter türünde. Bu son örnek ile bir önceki örnek bize aynı sonucu verecektir.
Adım adım inheritance’a geliyoruz. Şimdi, benim istediğim, oyuncunun (player) bir zırhı (armor) olsun ve hasar aldığında zırhın değeri sıfır olana kadar canınız azalmasın. Bu noktada oyuncu için istediğim özellikleri BaseCharacter sınıfım tam olarak karşılayamıyor. Fakat Enemy sınıfını da BaseCharacter üzerinden türettiğim için, Enemy’nin bu özelliklere sahip olmasını istemiyorum. O zaman Player sınıfımıza geri dönüp biraz değişiklikler yapalım. Player sınıfının BaseCharacter sınıfının özelliklerini taşımasını istediğim için BaseCharacter sınıfından türeteceğim.
public class Player : BaseCharacter { public int armor; public int HitArmor(int enemyDamage) { armor -= enemyDamage; return armor; } } public class Enemy : BaseCharacter { } public class Main { public Main() { Player player = new Player(); player.name = "MyHero"; player.health = 100; player.damage = 10; player.armor = 100; player.isDie = false; BaseCharacter enemy1 = new BaseCharacter(); enemy1.name = "enemy1"; enemy1.health = 80; enemy1.damage = 5; enemy1.isDie = false; Enemy enemy2 = new Enemy(); enemy2.name = "enemy2"; enemy2.health = 80; enemy2.damage = 5; enemy2.isDie = false; int playerArmor = player.HitArmor(enemy1.damage); playerArmor = player.HitArmor(enemy2.damage); if (playerArmor <= 0) { player.HitDamage(enemy.damage); player.HitDamage(enemy2.damage); } enemy1.HitDamage(player.damage); enemy2.HitDamage(player.damage); } }
Böylece Player sınıfım hem BaseCharacter sınıfının özelliklerini taşıyor hem de kendine özel olarak “armor” özelliğine sahip. Enemy sınıfı ise ek bir özelliğe sahip olmadan BaseCharacter sınıfından türetilmiş ve tüm özelliklerini taşıyor. Dikkat ettiyseniz, enemy1 BaseCharacter tipinde enemy2 ise Enemy tipinde bir nesne. Fakat birebir aynı özellikleri taşıyor. Enemy sınıfını oluşturmayabilirdim de. İhtiyaç duymuyor, gerek yoksa, anlamsız yere kod karmaşası oluşturmamaya çalışalım. Burada konuyu anlatabilmek için örnek olarak yarattım.
Enemy nesnesine ek olarak başka özellikler eklemek istediğimde bu Enemy sınıfına yazabilirim. Örnek, olarak Enemy tüpünde nesnelerin “boss” olup olmadığını aşağıdaki gibi eklersem enemy2 bu özelliği kullanamayacaktı. Bunun için enemy2’yi aşağıdaki gibi yazmam gerekecekti.
public class Enemy : BaseCharacter { public bool _isBoss; } public class Main { public Main() { Enemy enemy1 = new Enemy(); enemy1.name = "enemy1"; enemy1.health = 80; enemy1.damage = 5; enemy1.isDie = false; enemy1.isBoss = true; Enemy enemy2 = new Enemy(); enemy2.name = "enemy2"; enemy2.health = 80; enemy2.damage = 5; enemy2.isDie = false; enemy2.isBoss = false; BaseCharacter enemy3 = new BaseCharacter(); enemy3.name = "enemy3"; enemy3.health = 80; enemy3.damage = 5; enemy3.isDie = false; // isBoss BaseCharacter de bulunmuyor // bu satırda hata oluşur. enemy3.isBoss = false; } }
Şimdiye kadar Player ve Enemy sınıfları için yazdıklarımızla daha çok oynayıp bu sınıfları daha farklı yazabilirdik. İleride bahsedeceğim konularda örnek vermek üzere şimdilik burada kendimi durduruyorum.
Özet olarak inheritance, bir sınıfın özelliklerinin ve metotlarının başka sınıflara aktararak işlevlerinin arttırılmasını sağlar. Oluşturulan ve genel özellikleri içeren sınıfa “base class” (BaseCharacter), ondan miras alınarak özelleştirilen alt sınıfa “derived class” (Player ve Enemy) denir. Inheritance’da, birbirine benzeyen sınıfları ayrı ayrı yazmak yerine, sahip oldukları ortak özellikleri belirleyerek bir “base class” oluşturur ve ondan türeyen “derived class”ların hizmetine sunar. Geri kalan özellikleri base class’dan türetilen derived class’larda yazmamızı sağlar. Böylece temelde değiştirmek istediğimiz veya düzeltmek istediğimiz bir özelliği sadece base class’da (BaseCharacter) değiştirerek, diğerlerine de miras yoluyla bu değişikliği aktarmış oluruz.
Dikkat: Bir sınıfınbirden fazla base class’ı olamaz, sadece bir tane class’dan türetilebilir.
Unity içerisinde, Hierarchy’de bulunan, gameobject’lere bağladığımız sınıfında dikkat ettiyseniz MonoBehaviourclassı’nın derived class’larıdır. Böylece transform, gameObject, StartCoroutine, Invoke gibi özelliklerine erişebiliriz. Peki MonoBehaviour hangi class’ların özelliklerini miras alıyor? Merak ediyorsanız fare imlecinizi MonoBehaviour üzerine getirerek MacOS da Command + Left Click veya Windows’da Ctrl + Left Click tuş kombinasyonları ile görebilirsiniz.
Akış şu şekilde olacaktır:
MonoBehaviour → Behaviour → Component → Object
Dolayısıyla MonoBehaviour dan bir sınıf türettiğinizde Object sınıfına kadar (Object dahil) izin verilen özelliklere erişim sağlayabilecek, ve bütün özelliklerini taşıyacaksınız demektir. Bu bilgileri ileride kendi kişisel “component”lerimizi yazarken kullanacağız.