Oyun geliştirme sürecinde, birden fazla öğeyi bir arada tutma ihtiyacı sürekli karşımıza çıkar. Envanter sistemleri, düşman listeleri, seviye verileri veya oyuncu istatistikleri… Tüm bunlar için verileri düzenli bir şekilde saklamanız gerekir. İşte tam bu noktada C# diziler (Arrays) ve List<T> gibi C# koleksiyonları devreye girer. Bu makalede, bu iki temel veri yapısını derinlemesine inceleyecek, aralarındaki farkları, ne zaman hangisini kullanmanız gerektiğini ve Unity projelerinizde nasıl verimli bir şekilde uygulayabileceğinizi öğreneceksiniz.
Kısa Özet: Diziler ve List<T> Nedir?
Diziler (Arrays), aynı türden sabit sayıda öğeyi depolamak için kullanılan, bellekte ardışık olarak yerleşen veri yapılarıdır. Boyutları bir kez belirlendiğinde değiştirilemezler. Performans açısından oldukça hızlıdırlar çünkü elemanlara doğrudan indeks numaralarıyla erişilir.
Öte yandan, List<T> (generik liste), System.Collections.Generic namespace’inde bulunan, dinamik boyutlu bir koleksiyondur. Dizilerin aksine, List<T> öğe ekleyip çıkarmanıza olanak tanır ve boyutu otomatik olarak ayarlar. Bu esneklik, özellikle öğe sayısının çalışma zamanında değişebileceği senaryolarda onu vazgeçilmez kılar. Her iki yapı da Unity oyunlarında veri yönetimi için kritik öneme sahiptir.
Dizilerin Temelleri: Sabit Boyutlu Veri Depolama
Diziler, C# programlamanın en temel koleksiyon tiplerinden biridir. Bir dizi tanımladığınızda, bellekte belirli bir türden belirli sayıda yer ayırmış olursunuz. Bu boyut daha sonra değiştirilemez.
Dizi Tanımlama ve Başlatma
Bir dizi tanımlamak için türünü, ardından köşeli parantezleri [] ve değişken adını belirtirsiniz. Başlatma sırasında ise new anahtar kelimesiyle boyutunu belirtmeniz gerekir:
// 5 elemanlı bir int dizisi tanımlama
int[] skorlar = new int[5];
// Başlatma ve değer atama
string[] oyuncuIsimleri = new string[] { "Ahmet", "Ayşe", "Mehmet" };
// Kısayol başlatma
float[] pozisyonlar = { 1.5f, 2.3f, 0.7f };
Dizi Elemanlarına Erişim
Dizi elemanlarına sıfırdan başlayan indeks numaralarıyla erişilir:
skorlar[0] = 100; // İlk elemana değer atama
int ilkSkor = skorlar[0]; // İlk elemana erişim
// Bir döngü ile tüm elemanlara erişim
for (int i = 0; i < oyuncuIsimleri.Length; i++)
{
Debug.Log("Oyuncu: " + oyuncuIsimleri[i]);
}
Dizinin boyutunu Length özelliği ile alabilirsiniz. Diziler, performans açısından avantajlıdır ancak esneklik konusunda kısıtlıdır. Boyutunu değiştiremeyeceğiniz için, eleman ekleme veya çıkarma işlemleri maliyetli olabilir (yeni bir dizi oluşturup elemanları kopyalamak gerekir).
List<T> ile Dinamik Koleksiyon Yönetimi
List<T>, dinamik boyutlu olması sayesinde dizilerin en büyük eksikliğini giderir. T, listenin tutacağı veri türünü (örneğin, int, string, GameObject) temsil eden bir genetik parametredir. Bu, List<T>‘nin herhangi bir türü tutabilmesini sağlar.
List<T> Tanımlama ve Başlatma
List<T> kullanmak için System.Collections.Generic namespace’ini dahil etmeniz gerekir:
using System.Collections.Generic;
// int türünde bir liste tanımlama
List<int> dusmanCanlari = new List<int>();
// string türünde bir liste ve başlangıç değerleri
List<string> envanter = new List<string> { "Kılıç", "Kalkan", "İksir" };
List<T> Metotları: Ekleme, Silme ve Erişim
List<T>, elemanlarla çalışmak için birçok kullanışlı metoda sahiptir:
dusmanCanlari.Add(100); // Listenin sonuna eleman ekle
dusmanCanlari.Add(50);
dusmanCanlari.Insert(0, 150); // Belirli bir indekse eleman ekle
Debug.Log("İlk düşman canı: " + dusmanCanlari[0]); // İndeks ile erişim
int listeBoyutu = dusmanCanlari.Count; // Liste boyutu (eleman sayısı)
Debug.Log("Düşman sayısı: " + listeBoyutu);
// Eleman silme
dusmanCanlari.Remove(50); // Değere göre sil (ilk bulunanı)
dusmanCanlari.RemoveAt(0); // İndekse göre sil
envanter.Clear(); // Tüm elemanları sil
bool kalkanVarMi = envanter.Contains("Kalkan"); // Eleman var mı kontrolü
List<T>‘nin esnekliği, özellikle oyun içi envanter, görev listeleri veya dinamik olarak oluşan düşman dalgaları gibi senaryolarda büyük kolaylık sağlar. Bu C# koleksiyonları, geliştirme sürecinizi çok daha akıcı hale getirir.
Diziler ve List<T> Arasındaki Farklar ve Ne Zaman Hangisini Kullanmalı?
Her iki yapı da aynı türden birden fazla öğeyi depolamak için kullanılırken, temel farkları onları farklı senaryolar için uygun hale getirir:
- Boyut: Diziler sabittir,
List<T>dinamiktir. - Performans: Diziler genellikle daha hızlıdır (doğrudan bellek erişimi).
List<T>, eleman ekleme/çıkarma sırasında bellekte yeniden boyutlandırma yapabilir, bu da performans maliyeti yaratabilir. - Bellek Kullanımı: Diziler daha öngörülebilir bellek kullanımına sahiptir.
List<T>, kapasitesini artırdığında mevcut kapasitesinin iki katına kadar bellek ayırabilir. - Metotlar: Dizilerin sınırlı metotları varken,
List<T>ekleme, silme, arama gibi birçok kullanışlı metot sunar.
Ne Zaman Dizi Kullanmalı?
- Eleman sayısı başlangıçta biliniyor ve değişmeyecekse.
- Yüksek performans kritikse ve sık sık eleman ekleme/çıkarma yapılmayacaksa.
- Küçük, sabit boyutlu veri kümeleri için.
Ne Zaman List<T> Kullanmalı?
- Eleman sayısı çalışma zamanında değişecekse (ekleme/çıkarma olacaksa).
- Esneklik ve kolay kullanım ön plandaysa.
- Dinamik veri yapılarına ihtiyaç duyulan çoğu oyun içi sistem için (envanter, görevler, düşman listeleri vb.).
Pratik İpuçları
1. Unity Editöründe Koleksiyonları Görselleştirme
public olarak tanımladığınız diziler ve List<T>‘ler, Unity Inspector’ında otomatik olarak görünür. Bu, oyun nesneleri, materyaller veya diğer verileri kolayca atamanızı sağlar. Eğer private bir koleksiyonu Inspector’da görmek isterseniz, [SerializeField] niteliğini kullanabilirsiniz:
public GameObject[] dusmanPrefableri;
[SerializeField] private List<string> seviyeIsimleri;
2. List<T> Kapasitesini Önceden Ayarlama
List<T>‘nin sık sık yeniden boyutlandırılmasının performans maliyeti olduğunu belirtmiştik. Eğer listenizin yaklaşık olarak kaç eleman içereceğini biliyorsanız, başlangıç kapasitesini belirterek bu maliyeti azaltabilirsiniz:
// 100 eleman için başlangıç kapasitesi ayarla
List<Enemy> aktifDusmanlar = new List<Enemy>(100);
Bu, listenin ilk 100 eleman için yeniden boyutlandırma yapmasını engeller.
3. LINQ ile Koleksiyonları Sorgulama
System.Linq namespace’i, C# koleksiyonları üzerinde güçlü sorgulama yetenekleri sunar. Where, Select, OrderBy gibi metotlar, veri filtreleme, dönüştürme ve sıralama işlemlerini çok daha okunabilir ve kısa hale getirir:
using System.Linq;
List<int> skorlar = new List<int> { 85, 92, 78, 95, 88 };
// 90'dan yüksek skorları filtrele
List<int> yuksekSkorlar = skorlar.Where(s => s > 90).ToList();
// Skorları azalan sıraya göre sırala
List<int> siraliSkorlar = skorlar.OrderByDescending(s => s).ToList();
LINQ, özellikle büyük koleksiyonlarla çalışırken veya karmaşık veri manipülasyonları yaparken hayat kurtarıcı olabilir.
Yaygın Hatalar ve Çözümleri
1. IndexOutOfRangeException
Bu, dizilerin ve listelerin en yaygın hatalarından biridir. Bir koleksiyonun tanımlı sınırlarının dışındaki bir indekse erişmeye çalıştığınızda oluşur. Örneğin, 5 elemanlı bir dizinin [5] indeksine erişmek (indeksler 0’dan 4’e kadardır).
int[] sayilar = new int[3];
sayilar[3] = 10; // HATA! IndexOutOfRangeException
Çözüm: Koleksiyonun Length (dizi için) veya Count (liste için) özelliğini kullanarak döngü sınırlarını veya erişim indekslerini her zaman kontrol edin.
for (int i = 0; i < sayilar.Length; i++)
{
// Güvenli erişim
}
2. Null Referans Hatası (NullReferenceException)
Bir koleksiyonu kullanmaya çalışmadan önce onu başlatmayı unutursanız bu hatayı alırsınız.
List<string> esyalar; // Sadece tanımlandı, başlatılmadı
esyalar.Add("Kılıç"); // HATA! esyalar null olduğu için NullReferenceException
Çözüm: Koleksiyonları her zaman new anahtar kelimesiyle başlatın:
List<string> esyalar = new List<string>(); // Başlatıldı
esyalar.Add("Kılıç"); // Şimdi çalışır
3. foreach Döngüsü İçinde Eleman Silme
foreach döngüsü, koleksiyonlar üzerinde iterasyon yaparken koleksiyonun yapısının değiştirilmesine izin vermez. Döngü içinde eleman silmeye çalışmak hataya yol açar.
List<string> isimler = new List<string> { "Ali", "Veli", "Ayşe" };
foreach (string isim in isimler)
{
if (isim == "Veli")
{
isimler.Remove(isim); // HATA! Koleksiyon değiştirilemez
}
}
Çözüm: Geriye doğru döngü kullanın veya silinecek elemanları ayrı bir listede toplayıp döngüden sonra silin ya da yeni bir liste oluşturun.
// Çözüm 1: Geriye doğru döngü
for (int i = isimler.Count - 1; i >= 0; i--)
{
if (isimler[i] == "Veli")
{
isimler.RemoveAt(i);
}
}
// Çözüm 2: Yeni liste oluşturma
List<string> yeniIsimler = new List<string>();
foreach (string isim in isimler)
{
if (isim != "Veli")
{
yeniIsimler.Add(isim);
}
}
isimler = yeniIsimler;
Performans ve Optimizasyon Notları
- Diziler: Sabit boyutlu veriler için en iyi performansı sunar. Özellikle döngü içinde elemanlara rastgele erişim gerektiğinde çok hızlıdır. Bellek üzerinde ardışık oldukları için önbellek (cache) dostudurlar.
- List<T>: Dinamik yapısı nedeniyle eleman ekleme/çıkarma işlemleri, listenin yeniden boyutlandırılmasına neden olabilir. Bu yeniden boyutlandırma, mevcut tüm elemanların yeni, daha büyük bir bellek alanına kopyalanması anlamına gelir ve bu da performans düşüşüne yol açar. Eğer sık sık eleman ekleyip çıkarıyorsanız ve performans kritikse,
LinkedList<T>gibi farklı C# koleksiyonları düşünebilirsiniz (ancakLinkedList<T>rastgele erişim için daha yavaştır). - Kapasite Ön Ayarı: Yukarıda belirtildiği gibi,
List<T>‘nin başlangıç kapasitesini bilinen maksimum boyuta yakın bir değere ayarlamak, yeniden boyutlandırma maliyetlerini önemli ölçüde azaltır. - LINQ Performansı: LINQ sorguları, okunabilirlik ve kolaylık sağlarken, bazen manuel döngülere göre daha yavaş çalışabilir. Özellikle oyunun ana döngüsünde (
Update,FixedUpdate) sıkça çağrılan kritik performans bölgelerinde LINQ kullanımını dikkatli değerlendirmek gerekir.
Sonuç
C# diziler ve List<T>, Unity projelerinizde veri yönetimi için vazgeçilmez temel C# koleksiyonlarıdır. Diziler, sabit boyutlu ve performans kritik veriler için idealdir; List<T> ise dinamik ve esnek veri ihtiyaçlarınızı karşılar. Her birinin avantajlarını ve dezavantajlarını anlamak, doğru aracı doğru senaryoda kullanmanızı sağlayacaktır. Bu bilgileri Unity projelerinizde uygulayarak daha düzenli, verimli ve hata toleranslı kodlar yazabilirsiniz. Unutmayın, iyi bir oyunun temelinde her zaman iyi bir veri yönetimi yatar!



