Unity ile Basit Bir Kaçış Oyunu Geliştirme: Kare İçinde Hayatta Kal
Giriş: Kare İçinde Hayatta Kalma Sanatı
Oyun geliştirme dünyasına adım atmak isteyenler için Unity Basit Oyun projeleri harika bir başlangıç noktasıdır. Bu makalede, “Kare İçinde Kaçış” adını verebileceğimiz, basit ama bağımlılık yapıcı bir kaçış oyununu Unity ve C# kullanarak nasıl geliştireceğinizi adım adım öğreneceğiz. Amacımız, oyuncunun bir kareyi kontrol ederek yukarıdan gelen engellerden kaçmasını ve mümkün olduğunca uzun süre hayatta kalmasını sağlamak. Bu proje, Unity'nin temel bileşenlerini, fizik motorunu, kodlama prensiplerini ve UI entegrasyonunu anlamanıza yardımcı olacak.
Oyunun Temellerini Atmak
Her Unity Basit Oyun projesinde olduğu gibi, öncelikle yeni bir 2D proje oluşturarak başlıyoruz.
1. Oyuncu Karakteri (Player)
Oyuncumuz basit bir kare olacak. Hiyerarşi penceresinde sağ tıklayarak `2D Object > Sprites > Square` seçeneğini seçin. Adını "Player" olarak değiştirin. Ardından, ona fiziksel özellikler kazandırmak için `Add Component` butonuna tıklayarak `Rigidbody2D` ve `BoxCollider2D` bileşenlerini ekleyin. `Rigidbody2D`'nin `Gravity Scale` değerini `0` yapın, böylece yer çekiminden etkilenmez. `BoxCollider2D` bileşeninde `Is Trigger` seçeneğini işaretlemeyi unutmayın, bu sayede çarpışmaları daha esnek bir şekilde algılayabiliriz.
Oyuncu Hareketi (Player Movement) Script'i:
using UnityEngine;
public class PlayerController : MonoBehaviour
{
public float moveSpeed = 5f;
private Rigidbody2D rb;
void Start()
{
rb = GetComponent<Rigidbody2D>();
}
void FixedUpdate()
{
float horizontalInput = Input.GetAxis("Horizontal");
float verticalInput = Input.GetAxis("Vertical");
Vector2 movement = new Vector2(horizontalInput, verticalInput);
rb.velocity = movement * moveSpeed;
}
void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Obstacle"))
{
Debug.Log("Oyun Bitti!");
Time.timeScale = 0; // Oyunu durdur
// Burada oyun bitiş ekranını çağırabilirsiniz.
}
}
}
Bu script'i "Player" nesnesine atayın. `FixedUpdate` kullanmamızın nedeni, fizik motoruyla etkileşime giren hareketleri bu fonksiyonda yapmanın daha tutarlı sonuçlar vermesidir.
2. Engel Karakteri (Obstacle)
Engeller de oyuncu gibi kareler olacak. Bir "Square" `GameObject` oluşturun, adını "Obstacle" olarak değiştirin. Ona da `Rigidbody2D` ve `BoxCollider2D` ekleyin. `Rigidbody2D`'nin `Gravity Scale` değerini yine `0` yapın, ancak bu sefer bir `Tag` (Etiket) eklemeyi unutmayın. Inspector penceresinde `Tag` açılır menüsünden `Add Tag...` seçeneğini seçin ve "Obstacle" adında yeni bir etiket oluşturun, sonra bu etiketi "Obstacle" nesnesine atayın. `BoxCollider2D` bileşeninde `Is Trigger` seçeneğini işaretleyin.
Engel Hareketi (Obstacle Movement) Script'i:
using UnityEngine;
public class ObstacleMover : MonoBehaviour
{
public float moveSpeed = 3f;
void Update()
{
transform.Translate(Vector2.down * moveSpeed * Time.deltaTime);
// Ekranın altından çıkan engelleri yok et
if (transform.position.y < -6f) // Örnek bir eşik değeri
{
Destroy(gameObject);
}
}
}
Bu script'i "Obstacle" nesnesine atayın. `Update` içinde `transform.Translate` kullanabiliriz çünkü bu nesnelerin çarpışma algılaması `Is Trigger` ile yapılıyor ve fizik motorunu doğrudan manipüle etmiyoruz (sadece hareket ettiriyoruz). Engel prefab'ını oluşturmayı unutmayın!
3. Engel Üretimi (Obstacle Spawner)
Engelleri belirli aralıklarla ve rastgele konumlarda üretmemiz gerekiyor. Bunun için boş bir `GameObject` oluşturun, adını "ObstacleSpawner" olarak değiştirin ve sıfır konumuna getirin.
Engel Üretim (Spawner) Script'i:
using UnityEngine;
using System.Collections;
public class ObstacleSpawner : MonoBehaviour
{
public GameObject obstaclePrefab;
public float spawnInterval = 1.5f;
public float spawnRangeX = 4f;
void Start()
{
StartCoroutine(SpawnObstacles());
}
IEnumerator SpawnObstacles()
{
while (true)
{
float randomX = Random.Range(-spawnRangeX, spawnRangeX);
Vector2 spawnPosition = new Vector2(randomX, transform.position.y);
Instantiate(obstaclePrefab, spawnPosition, Quaternion.identity);
yield return new WaitForSeconds(spawnInterval);
}
}
}
Bu script'i "ObstacleSpawner" nesnesine atayın. `obstaclePrefab` değişkenine "Obstacle" prefab'ınızı sürükleyip bırakın.
4. Puanlama ve UI
Oyuncunun hayatta kalma süresini puan olarak göstermek için basit bir `UI Text` (TextMeshPro kullanmak daha iyidir) kullanabiliriz. `GameObject > UI > Text` ile bir metin nesnesi oluşturun ve adını "ScoreText" olarak değiştirin. Bir `GameManager` script'i oluşturarak puanı yönetin.
using UnityEngine;
using TMPro; // TextMeshPro kullanıyorsanız
public class GameManager : MonoBehaviour
{
public static GameManager Instance; // Singleton deseni
public TextMeshProUGUI scoreText; // veya UnityEngine.UI.Text
private float score = 0f;
private bool isGameOver = false;
void Awake()
{
if (Instance == null)
{
Instance = this;
}
else
{
Destroy(gameObject);
}
}
void Update()
{
if (!isGameOver)
{
score += Time.deltaTime;
scoreText.text = "Puan: " + Mathf.Floor(score).ToString();
}
}
public void GameOver()
{
isGameOver = true;
Debug.Log("Oyun Bitti! Son Puan: " + Mathf.Floor(score));
// Oyun bitiş panelini aktif et vb.
Time.timeScale = 0; // Oyunu durdur
}
}
Bu `GameManager` script'ini boş bir `GameObject`'e atayın ve `scoreText` referansını Inspector'dan bağlayın. PlayerController'daki `OnTriggerEnter2D` metodunu `GameManager.Instance.GameOver();` şeklinde güncelleyin ve `Time.timeScale = 0;` satırını oradan kaldırın.
Pratik İpuçları ve Orta Seviye Detaylar
1. Katmanlar (Layers) ve Fizik Ayarları
Çarpışma algılamalarını optimize etmek için `Layer`'ları kullanabilirsiniz. Örneğin, "Player" ve "Obstacle" için ayrı katmanlar oluşturup `Project Settings > Physics 2D` altındaki `Layer Collision Matrix`'ten sadece bu iki katmanın birbiriyle çarpışmasını sağlayabilirsiniz. Bu, gereksiz çarpışma kontrollerini ortadan kaldırarak performans artışı sağlar ve Unity Basit Oyun projelerinde bile iyi bir alışkanlıktır.
2. Nesne Havuzu (Object Pooling) Kullanımı
Engelleri sürekli `Instantiate` edip `Destroy` etmek, özellikle yoğun oyunlarda performans sorunlarına yol açabilir (`Garbage Collection` tetiklenmesi). Bunun yerine, bir Nesne Havuzu (Object Pooling) tekniği kullanarak engelleri önceden oluşturup ihtiyaç duyulduğunda havuzdan alıp kullanmak, işiniz bittiğinde ise havuza geri göndermek çok daha verimlidir. Bu konu başlı başına bir makale konusu olsa da, temel olarak bir `List` veya `Queue` kullanarak aktif ve pasif engelleri yönetebilirsiniz.
3. Zorluk Ayarı
Oyunun zamanla zorlaşmasını sağlamak için `ObstacleSpawner`'daki `spawnInterval`'ı zamanla azaltabilir veya `ObstacleMover`'daki `moveSpeed`'i artırabilirsiniz. Bunu `GameManager`'dan yönetmek en iyisidir:
// GameManager içinde
public float difficultyIncreaseRate = 0.1f;
public float minSpawnInterval = 0.5f;
public float maxObstacleSpeed = 10f;
void Update()
{
if (!isGameOver)
{
score += Time.deltaTime;
scoreText.text = "Puan: " + Mathf.Floor(score).ToString();
// Zorluk artırma
float currentDifficultyFactor = 1 + (score * difficultyIncreaseRate);
ObstacleSpawner.Instance.spawnInterval = Mathf.Max(minSpawnInterval, spawnInterval / currentDifficultyFactor);
// ObstacleMover'ların hızını da burada ayarlayabilirsiniz.
}
}
Yaygın Hatalar ve Çözümleri
- Rigidbody Olmadan Fiziksel Hareket: `Rigidbody` bileşeni olmayan bir `GameObject`'i `transform.Translate` ile hareket ettirip `OnCollisionEnter2D` beklemek yaygın bir hatadır. Fiziksel etkileşimler için mutlaka `Rigidbody` gereklidir. Çözüm: `Rigidbody` kullanın ve fizik hareketlerini `FixedUpdate` içinde yapın.
- `Is Trigger` Seçeneğini Unutmak: `OnTriggerEnter2D` fonksiyonunun çalışması için en az bir `Collider2D`'nin `Is Trigger` olarak işaretlenmesi ve her iki `GameObject`'in de `Rigidbody2D`'ye sahip olması gerekir. Çözüm: Gerekli ayarları kontrol edin.
- `FindObjectOfType`'ı `Update` İçinde Kullanmak: Bu fonksiyon, hiyerarşideki tüm nesneleri taradığı için oldukça pahalıdır. `Update` gibi sık çağrılan fonksiyonlarda kullanmak performansı düşürür. Çözüm: Referansları `Start` veya `Awake` içinde cache'leyin (örneğin `GameManager` için Singleton deseni kullanın).
- `Time.deltaTime` Kullanımını Atlamak: Hareket hızını veya zamanla değişen diğer değerleri `Update` içinde `Time.deltaTime` ile çarpmamak, farklı kare hızlarına sahip bilgisayarlarda oyunun farklı hızlarda çalışmasına neden olur. Çözüm: Hız hesaplamalarında `Time.deltaTime` kullanın.
Performans ve Optimizasyon Notları
Bir Unity Basit Oyun bile olsa, iyi alışkanlıklar edinmek önemlidir. Yukarıda bahsedilen `Object Pooling` tekniği, çöp toplama (Garbage Collection) işlemlerini azaltarak performansı önemli ölçüde artırır. Ayrıca:
- Fizik Ayarları: `Project Settings > Physics 2D` altındaki `Solver Iterations` gibi değerleri çok yükseltmek yerine makul seviyelerde tutun.
- Gereksiz Bileşenler: Kullanmadığınız `Collider` veya `Rigidbody` bileşenlerini `GameObject`'lerden kaldırın.
- Statik Batching: Hareket etmeyen arka plan nesneleri için statik batching'den faydalanmak çizim çağrılarını azaltabilir.
Sonuç
Bu makalede, Unity ile "Kare İçinde Kaçış" adında basit ama işlevsel bir kaçış oyunu geliştirmenin temel adımlarını öğrendik. Oyuncu hareketinden engel üretimine, çarpışma algılamadan puanlamaya kadar birçok temel oyun mekaniğini ele aldık. Edindiğiniz bu bilgilerle kendi yaratıcılığınızı kullanarak oyununuzu daha da geliştirebilirsiniz. Unutmayın, her büyük oyun, basit fikirlerle başlar. Bu Unity Basit Oyun projesi, oyun geliştirme yolculuğunuzda sağlam bir temel oluşturacaktır!
🧠 Ders Sonu Değerlendirme Testi
Dersi tamamladıktan sonra bilgilerinizi test edin ve ekstra puanlar kazanın.
Yorumlar (0)
İlk yorumu siz yapın!