Giriş: Flappy Bird Mekanikleri
Flappy Bird, basit ama bağımlılık yaratan oynanışıyla mobil oyun dünyasında bir fenomen haline geldi. Kuşun engellerden kaçınması, skor kazanması ve en iyi skoru kaydetmesi gibi temel Flappy Bird mekanikleri, birçok geliştirici için başlangıç projelerine ilham kaynağı olmuştur. Bu makalede, Unity motorunu ve C# programlama dilini kullanarak bir Flappy Bird benzeri oyunda çarpışma algılamayı, skor sistemini oluşturmayı ve oyuncunun en iyi skorunu kalıcı olarak nasıl kaydedeceğimizi adım adım inceleyeceğiz. Bu temel mekanikler, sadece Flappy Bird benzeri oyunlar için değil, birçok farklı oyun türü için de temel oluşturacaktır.
Bir oyunun temelini oluşturan bu dinamikleri doğru bir şekilde anlamak ve uygulamak, oyunculara akıcı ve keyifli bir deneyim sunmanın anahtarıdır. Özellikle çarpışma algılama, oyunun ‘ölüm’ anını belirlerken, skor sistemi oyuncuyu motive eder. En iyi skor kaydı ise rekabetçi ruhu canlı tutar ve oyuncunun oyuna geri dönmesini sağlar. Bu üç temel Flappy Bird mekanikleri, oyunun döngüsünü tamamlayan kritik unsurlardır.
Çarpışma Algılama: Kuşun Sonu
Flappy Bird’de kuşun borulara veya zemine çarpması oyunun sonu anlamına gelir. Bu çarpışmaları Unity’de doğru bir şekilde algılamak için `Collider2D` bileşenlerini ve `Rigidbody2D` fizik motorunu kullanırız. Çarpışma algılamanın çalışabilmesi için çarpışacak nesnelerden en az birinde bir `Rigidbody2D` bileşeni bulunması ve her ikisinde de `Collider2D` bileşeni olması gerekmektedir.
`OnTriggerEnter2D` ve `OnCollisionEnter2D` Farkı
- `OnCollisionEnter2D`: Bu metot, iki `Collider2D` bileşeni fiziksel olarak birbirine çarptığında çağrılır. Çarpışan nesneler birbirini iter ve fiziksel bir tepki verir. Genellikle ölümcül çarpışmalar (kuşun boruya çarpması) için kullanılır.
- `OnTriggerEnter2D`: Bu metot, bir `Collider2D` bileşeninin `isTrigger` özelliği açık olduğunda ve başka bir `Collider2D` onun içinden geçtiğinde çağrılır. Fiziksel bir itme veya tepki olmaz, sadece bir geçiş algılanır. Skor alanları gibi durumlar için idealdir.
Katmanlar ve Tag’ler ile Hedefleme
Çarpışma algılarken, hangi nesnelerle çarpıştığımızı belirlemek önemlidir. Bunu `Tag`’ler (etiketler) veya `Layer`’lar (katmanlar) kullanarak yapabiliriz. Örneğin, borulara ‘Pipe’ etiketi, zemine ‘Ground’ etiketi verebiliriz. Böylece, kuşun çarpıştığı nesnenin ne olduğunu kontrol edebiliriz.
// BirdController.cs
using UnityEngine;
public class BirdController : MonoBehaviour
{
private bool isDead = false;
void OnCollisionEnter2D(Collision2D collision)
{
if (isDead) return; // Zaten ölmüşse tekrar işlem yapma
if (collision.gameObject.CompareTag("Pipe") || collision.gameObject.CompareTag("Ground"))
{
Die();
}
}
void Die()
{
isDead = true;
// Kuşun hareketini durdur
GetComponent().velocity = Vector2.zero;
GetComponent().gravityScale = 0; // Yerçekimini kapat
// Oyun yöneticisine ölüm bilgisini gönder
GameManager.Instance.GameOver();
Debug.Log("Kuş öldü!");
// Ölüm animasyonu veya sesi çalabiliriz
}
}
Bu kod bloğu, kuşun ‘Pipe’ veya ‘Ground’ etiketli bir nesneye çarpması durumunda `Die()` metodunu çağırır. `GameManager.Instance.GameOver()` ile oyunun genel durumunu yöneten bir `GameManager`’a bilgi gönderilir. Bu, Flappy Bird mekanikleri için temel bir ölüm sistemidir.
Skor Sistemi: Borulardan Geçiş
Skor sistemi, oyuncunun boru engellerini başarıyla geçtiğini ödüllendiren temel Flappy Bird mekanikleri‘nden biridir. Her geçilen boru için bir puan verilir.
Tetikleyici Alanlar (Trigger Zones) Kullanımı
Boruların arasından geçerken skor kazanmak için, boruların arasına bir `BoxCollider2D` ekleyip `isTrigger` özelliğini açarız. Bu tetikleyici alanın etiketini ‘ScoreZone’ olarak belirleyebiliriz. Kuş bu alandan geçtiğinde skor artırılır.
Skorun Güncellenmesi
Skor, bir `int` değişkeninde tutulur ve her boru geçişinde artırılır. Bu skorun kullanıcı arayüzünde (UI) gösterilmesi için Unity UI Text veya TextMeshPro bileşenleri kullanılır.
// ScoreZoneController.cs
using UnityEngine;
public class ScoreZoneController : MonoBehaviour
{
private bool scored = false; // Bir kere skor verildi mi?
void OnTriggerEnter2D(Collider2D other)
{
if (scored) return; // Zaten skor verilmişse tekrar verme
if (other.CompareTag("Player")) // Kuşun etiketi 'Player' ise
{
GameManager.Instance.AddScore(1);
scored = true; // Bu tetikleyiciden bir daha skor verilmemesini sağla
Debug.Log("Skor arttı!");
// Skor sesi çalabiliriz
}
}
}
Bu script, her boru takımının ortasında bulunan bir `Collider2D`’ye eklenir. `isTrigger` açık olmalıdır. Kuş (‘Player’ etiketli) bu alana girdiğinde, `GameManager` üzerinden skoru artırırız. `scored` değişkeni, aynı boru takımından birden fazla skor kazanılmasını engeller.
En İyi Skor Kaydı: Kalıcı Başarılar
Oyuncuların motivasyonunu yüksek tutmak için en iyi skorlarını kaydetmek önemlidir. Unity, bu tür basit veri depolama işlemleri için `PlayerPrefs` adında kullanışlı bir sınıf sunar. `PlayerPrefs`, küçük anahtar-değer çiftlerini (string-int, string-float, string-string) cihazda kalıcı olarak saklamanızı sağlar.
`PlayerPrefs` ile Veri Saklama
`PlayerPrefs` statik bir sınıftır ve doğrudan metotlarını çağırarak kullanılır:
- `PlayerPrefs.SetInt(string key, int value)`: Bir `int` değeri kaydeder.
- `PlayerPrefs.GetInt(string key, int defaultValue)`: Bir `int` değeri okur. Eğer anahtar yoksa `defaultValue` döner.
- `PlayerPrefs.SetString(string key, string value)`: Bir `string` değeri kaydeder.
- `PlayerPrefs.GetString(string key, string defaultValue)`: Bir `string` değeri okur.
- `PlayerPrefs.SetFloat(string key, float value)`: Bir `float` değeri kaydeder.
- `PlayerPrefs.GetFloat(string key, float defaultValue)`: Bir `float` değeri okur.
- `PlayerPrefs.HasKey(string key)`: Belirtilen anahtarın var olup olmadığını kontrol eder.
- `PlayerPrefs.DeleteKey(string key)`: Belirtilen anahtarı ve değerini siler.
- `PlayerPrefs.DeleteAll()`: Tüm `PlayerPrefs` verilerini siler (genellikle geliştirme aşamasında kullanılır).
Önemli Not: `PlayerPrefs` verileri şifrelenmez ve kolayca değiştirilebilir. Hassas veriler veya oyunun temel ekonomisi gibi önemli bilgiler için kullanılmamalıdır. Daha karmaşık ve güvenli kaydetme sistemleri için JSON, XML veya veritabanı çözümleri tercih edilmelidir.
Skoru Kaydetme ve Yükleme
Oyun bittiğinde mevcut skoru, kaydedilmiş en iyi skorla karşılaştırırız. Eğer mevcut skor daha yüksekse, yeni en iyi skor olarak kaydederiz.
// GameManager.cs (Örnek)
using UnityEngine;
using TMPro; // Eğer TextMeshPro kullanıyorsanız
public class GameManager : MonoBehaviour
{
public static GameManager Instance { get; private set; }
public int currentScore = 0;
public int highScore = 0;
[SerializeField] private TextMeshProUGUI scoreText;
[SerializeField] private TextMeshProUGUI highScoreText;
[SerializeField] private GameObject gameOverPanel;
private const string HIGH_SCORE_KEY = "HighScore";
void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
}
else
{
Instance = this;
LoadHighScore();
}
}
void Start()
{
UpdateScoreUI();
gameOverPanel.SetActive(false); // Oyun başlangıcında paneli gizle
}
public void AddScore(int amount)
{
currentScore += amount;
UpdateScoreUI();
}
void UpdateScoreUI()
{
if (scoreText != null)
{
scoreText.text = "Skor: " + currentScore.ToString();
}
if (highScoreText != null)
{
highScoreText.text = "En İyi: " + highScore.ToString();
}
}
public void GameOver()
{
Time.timeScale = 0; // Oyunu durdur
gameOverPanel.SetActive(true); // Oyun bitti panelini göster
if (currentScore > highScore)
{
highScore = currentScore;
SaveHighScore();
}
UpdateScoreUI(); // Yeni yüksek skoru göstermek için UI'ı güncelle
}
void SaveHighScore()
{
PlayerPrefs.SetInt(HIGH_SCORE_KEY, highScore);
PlayerPrefs.Save(); // Değişiklikleri hemen kaydetmek için
Debug.Log("En iyi skor kaydedildi: " + highScore);
}
void LoadHighScore()
{
highScore = PlayerPrefs.GetInt(HIGH_SCORE_KEY, 0); // Anahtar yoksa 0 döner
Debug.Log("En iyi skor yüklendi: " + highScore);
}
public void RestartGame()
{
Time.timeScale = 1; // Oyunu devam ettir
UnityEngine.SceneManagement.SceneManager.LoadScene(UnityEngine.SceneManagement.SceneManager.GetActiveScene().name);
}
}
Bu `GameManager` script’i, oyunun skorunu, en iyi skoru yönetir ve oyun bittiğinde `PlayerPrefs` kullanarak kaydeder. `Awake` metodunda `LoadHighScore()` çağrılarak oyun başladığında en iyi skor yüklenir. `GameOver()` metodunda mevcut skor, en iyi skorla karşılaştırılır ve gerekirse yeni en iyi skor kaydedilir. Bu, Flappy Bird mekanikleri için merkezi bir yönetim sağlar.
Pratik İpuçları ve En İyi Uygulamalar
1. Oyun Durumu Yönetimi
Oyunun farklı durumlarını (oyun başladı, oyun oynanıyor, oyun bitti, duraklatıldı) yönetmek için bir `GameManager` sınıfı kullanmak çok önemlidir. Bu sınıf, skor, oyunun hızını ayarlama (`Time.timeScale`), UI güncellemeleri ve oyunun yeniden başlatılması gibi tüm genel Flappy Bird mekanikleri‘ni merkezi bir yerden kontrol etmenizi sağlar. Singleton deseni (`public static GameManager Instance`) bu tür durumlar için sıklıkla tercih edilir.
2. Fizik Ayarları ve `Time.timeScale`
Unity’nin 2D fizik ayarları (Edit > Project Settings > Physics 2D) kuşun yerçekimi, hızı gibi davranışlarını etkiler. Özellikle oyun bittiğinde oyunu tamamen durdurmak için `Time.timeScale = 0;` kullanmak çok işlevseldir. Oyun yeniden başladığında `Time.timeScale = 1;` olarak ayarlanmalıdır.
3. Nesne Havuzlama (Object Pooling)
Flappy Bird’de borular sürekli olarak oluşturulup yok edilir. Bu durum, özellikle mobil cihazlarda performans sorunlarına yol açabilir. Nesne havuzlama (Object Pooling) tekniğini kullanarak, boruları önceden oluşturup ihtiyaç duyulduğunda tekrar kullanmak, performansı önemli ölçüde artıracaktır. Bu, sürekli nesne `Instantiate` ve `Destroy` işlemlerinden kaynaklanan çöp toplama (garbage collection) maliyetini azaltır.
Yaygın Hatalar ve Çözümleri
1. `Rigidbody2D` Eksikliği veya Yanlış Ayarlar
Hata: Çarpışmalar algılanmıyor veya fiziksel tepki olmuyor.
Çözüm: Çarpışma algılamanın çalışması için çarpışan nesnelerden en az birinde `Rigidbody2D` bileşeni olmalıdır. Eğer fiziksel itme istenmiyorsa, `Rigidbody2D`’nin `Body Type`’ını `Kinematic` olarak ayarlayabilir ve `isTrigger` kullanabilirsiniz. Kuşun fiziksel olarak hareket etmesi için `Rigidbody2D`’nin `Body Type`’ı `Dynamic` olmalıdır.
2. `isTrigger` ve `Collider` Uyumsuzluğu
Hata: Skor tetikleyicisi çalışmıyor veya kuş boruların içinden geçiyor.
Çözüm: `OnTriggerEnter2D` metodunun çalışması için `Collider2D` bileşeninin `isTrigger` özelliğinin açık olması gerekir. Eğer `isTrigger` kapalıysa, `OnCollisionEnter2D` metodu çağrılır ve nesneler fiziksel olarak çarpışır. Skor bölgeleri için `isTrigger` açık, boruların kendisi için kapalı olmalıdır.
3. `PlayerPrefs` Anahtar Çakışmaları
Hata: Kaydedilen skorlar beklenmedik şekilde değişiyor veya kayboluyor.
Çözüm: `PlayerPrefs` anahtarları (örneğin “HighScore”) uygulamanızdaki diğer `PlayerPrefs` çağrılarıyla çakışmamalıdır. Her anahtarın benzersiz olduğundan emin olun. Genellikle `const string` kullanarak anahtar isimlerini merkezi bir yerde tanımlamak bu tür hataları önler.
Performans ve Optimizasyon Notları
Flappy Bird gibi basit 2D oyunlar genellikle performans açısından büyük sorunlar çıkarmaz. Ancak yine de dikkat edilmesi gereken bazı noktalar vardır:
- `PlayerPrefs` Kullanımı: `PlayerPrefs` küçük veri kümeleri için oldukça hafiftir ve performansı etkilemez. Ancak her `Set` işleminden sonra `PlayerPrefs.Save()` çağırmak yerine, veriyi kaydetmek için uygun bir zaman (örneğin oyun bittiğinde veya uygulama kapanırken) belirlemek daha iyi olabilir. `PlayerPrefs.Save()` işlemi, verileri diske yazar ve sık sık çağrıldığında küçük bir gecikmeye neden olabilir.
- Fizik Hesaplamaları: Çok sayıda `Rigidbody2D` ve `Collider2D` içeren karmaşık sahnelerde fizik hesaplamaları maliyetli olabilir. Flappy Bird’de genellikle sadece kuşun bir `Rigidbody2D`’si ve birkaç `Collider2D`’si olduğundan bu bir sorun teşkil etmez. Ancak boru havuzlama gibi teknikler, sürekli nesne yaratıp yok etmenin getirdiği performans düşüşlerini engeller.
- UI Güncellemeleri: Skor gibi sık güncellenen UI elemanları için `TextMeshPro` kullanmak, standart `Text` bileşenlerine göre daha performanslıdır. Ayrıca, UI güncellemelerini sadece değer değiştiğinde yapmak, gereksiz işlem yükünü azaltır.
Sonuç
Bu makalede, Unity’de bir Flappy Bird benzeri oyunun temel Flappy Bird mekanikleri olan çarpışma algılama, skor sistemi oluşturma ve en iyi skoru `PlayerPrefs` kullanarak kalıcı olarak kaydetme süreçlerini detaylı bir şekilde inceledik. `OnCollisionEnter2D` ve `OnTriggerEnter2D` arasındaki farkları, `Rigidbody2D`’nin önemini ve `GameManager` gibi merkezi bir yapının faydalarını öğrendik. Bu bilgiler, sadece Flappy Bird benzeri oyunlar için değil, Unity ile geliştireceğiniz birçok 2D oyun projesi için sağlam bir temel oluşturacaktır. Kendi oyunlarınızı geliştirirken bu temel mekanikleri uygulayarak yaratıcılığınızı konuşturmaktan çekinmeyin!



