Unity 2D oyunda skor sistemi kurarken skorun başlangıç değerini, puanı artıran olayı ve değerin ekrana yazılmasını ayrı ayrı planlaman gerekir. Skor sistemi üç soruya cevap vermelidir: Puan ne zaman artıyor, nerede tutuluyor, kullanıcıya ne zaman gösteriliyor? Yıldız toplama örneğinde cevaplar şöyledir: puan yıldıza dokunulduğunda artar, değer tek bir yönetici sınıfta tutulur, arayüz de değer değiştiğinde güncellenir. Üçünü tek script'e doldurmak başta hızlı görünür, ama oyun büyüdükçe sorun çıkarır.
Skor sistemi üç sorunun cevabıdır: Olay, yönetici ve arayüz
Skor sistemi üç ayrı sorumluluktan oluşur ve her birinin tek bir sahibi olmalıdır:
- Olay: Puan kazanma anıdır. Örnekte yıldızın toplanması. Olay yalnızca "bir şey oldu" der, skorun nasıl tutulduğunu bilmez.
- Yönetici: Skor değerinin tutulduğu ve değiştirildiği tek yerdir. Başlangıç değeri de burada belirlenir.
- Arayüz: Değerin gösterildiği yerdir. Skoru hesaplamaz, yalnızca yöneticiden gelen değeri yazar.
Akış her zaman aynı sırayla ilerler:
- Oyuncu yıldıza temas eder ve yıldız toplanır.
- Yıldız, skor yöneticisine puan eklenmesini bildirir.
- Yönetici skoru artırır.
- Arayüz yeni değeri ekrana yazar.
Yıldızı, skor verisini ve ekran yazısını aynı sınıfa bağlarsan sorunlar somutlaşır. Düşman öldürünce ya da madalya alınca da puan vermek istediğinde bu sınıfı kopyalaman veya içine yeni koşullar eklemen gerekir. Skor yazısının yerini ya da biçimini değiştirmek, puan hesabını bozma riski taşıyan bir düzenleme olur. Her yıldız kendi skorunu tutarsa da sahnede birden fazla yıldız birbirinden bağımsız sayılara sahip olur. Yönetici ayrı olduğunda yeni bir puan kaynağı yalnızca "puan ekle" çağrısı yapar, arayüz hiç değişmez.
Bu ayrımı koda geçmeden önce kâğıt üzerinde çizmek, hataları editör açılmadan yakalamanı sağlar. Akışı dilden bağımsız kurabildiğini ölçmek istersen ücretsiz Algoritmik Düşünme Testini deneyebilirsin.
Yıldız nesnesini hazırlama: Is Trigger, Rigidbody2D ve etiket

Yıldızın oyuncuya dokunduğunu fark edebilmesi için üç editör ayarı birlikte doğru olmalıdır. Hiç kod yazmadan önce bunları kur:
- Yıldız sprite'ına bir 2D collider ekle ve Inspector'da Is Trigger seçeneğini işaretle.
- Oyuncuda bir Rigidbody2D bileşeni bulunduğundan emin ol.
- Oyuncu nesnesine Inspector'daki Tag alanından "Player" etiketini ver.
Her ayarın gerekçesi farklıdır:
- Is Trigger: Yıldızdan oyuncunun içinden geçmesini ve çarpışma fiziği uygulanmamasını istiyorsun; yalnızca "temas oldu" bildirimini istiyorsun. Is Trigger kapalı kalırsa yıldız katı bir engel gibi davranır ve oyuncu ona çarpar.
- Rigidbody2D: Unity'nin 2D trigger olaylarını üretebilmesi için temas eden nesnelerden en az birinde Rigidbody2D bulunmalıdır. Oyuncuda olması bu yüzden yeterlidir; her yıldıza ayrıca eklemen gerekmez.
- Player etiketi: Sahnedeki her temas yıldızı toplamamalıdır. Koddaki etiket kontrolü, yalnızca oyuncunun temasında puan verilmesini sağlar. Böylece zemin, düşman ya da başka bir yıldız dokunsa bile skor artmaz.
Bu ayarlardan biri eksik ya da yanlışsa tipik belirti, yıldıza değdiğinde olayın hiç çağrılmamasıdır: ne hata alırsın ne de skor değişir. Böyle bir durumda neyi, hangi sırayla kontrol edeceğini yazının hata ayıklama bölümünde adım adım göreceksin.
Skor yöneticisi ve OnTriggerEnter2D ile yıldız toplama kodu
İki script yeterli: biri skoru tutar ve değişince haber verir, diğeri yıldızın çarpışma anını yönetir. Yönetici arayüzü hiç tanımaz.
using System;
using UnityEngine;
public class ScoreManager : MonoBehaviour
{
public static ScoreManager Instance { get; private set; }
public event Action<int> ScoreChanged;
private int score = 0;
public int Score => score;
private void Awake()
{
Instance = this;
}
public void AddScore(int amount)
{
score += amount;
Debug.Log("Skor: " + score);
ScoreChanged?.Invoke(score);
}
}
Skor private olduğu için dışarıdan yalnızca AddScore ile değişir. ScoreChanged olayı her artışta yeni değeri yayınlar; dinleyen yoksa ?.Invoke hata vermez. Instance sahnedeki tek ScoreManager nesnesine giden basit bir erişim yoludur. Bu yol, sahnede tek örnek olduğu varsayımına dayanır; iki yönetici olursa son atanan kazanır.
using UnityEngine;
public class Star : MonoBehaviour
{
[SerializeField] private int points = 10;
private void OnTriggerEnter2D(Collider2D other)
{
if (!other.CompareTag("Player")) return;
ScoreManager.Instance.AddScore(points);
Destroy(gameObject);
}
}
Yıldız skoru tutmaz, yalnızca "ben toplandım" der. CompareTag kontrolü başka nesnelerin değerli yıldızı yanlışlıkla toplamasını engeller. Puan Destroy öncesinde eklenir; nesne aynı karede yok edilir ve ikinci tetiklemeye açık kalmaz.
Oyuncu üç yıldız topladığında Console çıktısı şöyle olur:
Skor: 10
Skor: 20
Skor: 30
Skoru ekranda göstermek: Text mi TextMeshPro mu?

Eski UI Text bileşeni basit metinler için yeterlidir, ancak ölçeklendiğinde bulanıklaşabilir ve biçimlendirme seçenekleri sınırlıdır. TextMeshPro ise daha keskin metin çizer, zengin biçimlendirme olanakları sunar. Yeni projelerde genellikle TextMeshPro tercih edilir. Sahneye eklemek için UI menüsünden TextMeshPro metin nesnesi eklemen yeterli.
Üçüncü sorumluluk olan arayüz, yöneticinin olayına abone olur:
using TMPro;
using UnityEngine;
public class ScoreUI : MonoBehaviour
{
[SerializeField] private TMP_Text scoreText;
private void OnEnable()
{
ScoreManager.Instance.ScoreChanged += UpdateText;
UpdateText(ScoreManager.Instance.Score);
}
private void OnDisable()
{
if (ScoreManager.Instance != null)
ScoreManager.Instance.ScoreChanged -= UpdateText;
}
private void UpdateText(int value)
{
scoreText.text = "Skor: " + value;
}
}
Inspector'daki scoreText alanına metin nesnesini sürükle. İkinci yıldızdan sonra ekranda Skor: 20 görünür.
İki dikkat noktası var:
OnEnableiçindekiUpdateTextçağrısı, oyun başında metnin boş kalmasını önler ve "Skor: 0" gösterir.- Bu çağrı
ScoreManager.Awakeçalışmadan önce yapılırsaInstanceboş kalır. Böyle bir durumda yöneticinin önce çalışmasını sağla, örneğin Unity'nin script çalışma sırası ayarıyla.
Eski Text kullanıyorsan yalnızca üç satır değişir: using TMPro; yerine using UnityEngine.UI; yazılır, alan türü TMP_Text yerine Text olur ve metin atama satırı aynı kalır.
Sahne değiştiğinde skor nasıl korunur?
Skoru tutan yönetici nesnesi sahnenin içindeyse, yeni sahne yüklendiğinde sahnenin diğer nesneleriyle birlikte yok olur. Yeni sahnede yönetici yeniden oluşur ve skor başlangıç değerine döner. Skorun sahneler arasında yaşaması için iki yaygın yol var.
Seçenek 1: Yöneticiyi DontDestroyOnLoad ile yaşatmak
DontDestroyOnLoad, verdiğin nesnenin yeni sahne yüklenirken yok edilmemesini sağlar. Nesnenin hiyerarşide başka bir nesnenin çocuğu değil, kök nesne olması gerekir. Tek dikkat noktası şudur: ilk sahneye geri dönersen sahnede duran yönetici ile yaşatılan yönetici aynı anda var olur. Bu yüzden Awake içinde kısa bir kopya kontrolü yapılır.
public class ScoreManager : MonoBehaviour
{
public static ScoreManager Instance { get; private set; }
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject); // ikinci kopya kendini yok eder
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
}
}
İlk yönetici kalır, sonradan gelen kopya kendini siler. Skor değeri ilk yöneticide durduğu için sahneler arasında korunur.
Seçenek 2: Skoru ayrı bir veri sınıfında tutmak
İkinci yaklaşımda skor, sahneden bağımsız bir sınıfta durur. Yönetici sahneyle birlikte yok olsa da değer kaybolmaz:
public static class GameSession
{
public static int Score;
}
Bu veri oyun açık kaldığı sürece bellekte durur. Yönetici yalnızca GameSession.Score değerini okur ve değiştirir. Yaklaşım basittir, ancak olay tetikleme ve arayüz bildirimi gibi işleri yine yöneticiye bırakman gerekir.
Küçük projelerde ilk yol, yönetici zaten tek bir noktada toplanmışsa pratiktir. Skor dışında da oturum verisi tutacaksan ikinci yol düzeni daha temiz tutar.
Yeni sahnedeki ScoreUI'ı yeniden bağlamak
Hangi yolu seçersen seç, yeni sahnedeki ScoreUI yeni bir nesnedir. Olaya yeniden abone olmalı ve açıldığında mevcut skoru okuyup yazıya yansıtmalıdır. Yoksa skor doğru olduğu hâlde ekranda eski ya da boş metin görünür.
private void OnEnable()
{
ScoreManager.Instance.OnScoreChanged += UpdateText;
UpdateText(ScoreManager.Instance.Score); // mevcut değeri hemen oku
}
private void OnDisable()
{
ScoreManager.Instance.OnScoreChanged -= UpdateText;
}
Oyunu kapatıp açınca da kalacak bir en yüksek skor ayrı bir ihtiyaçtır; bunun için PlayerPrefs gibi kalıcı bir depolama gerekir.
Hata ayıklama kontrol listesi: skor artmıyor, yazı güncellenmiyor, çift puan veriyor
Skor sorunlarının çoğu üç yerden çıkar: temas algılanmıyor, olay arayüze ulaşmıyor ya da olay birden fazla kez tetikleniyor. Aşağıdaki listeyi yukarıdan aşağıya takip edebilirsin.
Skor artmıyor
- Yıldızın collider bileşeninde Is Trigger işaretli mi?
- Çarpışan iki nesneden en az birinde
Rigidbody2Dvar mı? - Oyuncunun etiketi kodda yazdığınla birebir aynı mı? Büyük küçük harf ve boşluk farkı koşulu bozar.
- 2D yerine 3D collider mı eklendi?
OnTriggerEnter2D3D collider ile çalışmaz; iki nesnede de 2D collider kullan. - Script nesneye eklenmiş ve etkin mi?
- Console'da kırmızı bir hata var mı? Bir hata, kodun geri kalanını çalıştırmadan keser.
Yazı güncellenmiyor
ScoreUIolaya abone mi?OnEnableiçindeki satırın çalıştığından emin ol.- Inspector'da metin referansı atanmış mı?
- Yönetici referansı
nullmı? Sahnede yönetici yoksa ya daInstancehenüz kurulmadıysa abonelik hata verir. - Olay, puan eklendiğinde yönetici tarafından gerçekten çağrılıyor mu?
Çift puan veriyor
- Yıldızda ya da oyuncuda birden fazla collider olabilir; her biri ayrı temas olayı üretir.
Starscript'i aynı nesnede iki kez bulunabilir.Destroynesneyi hemen değil kare sonunda yok eder; bu arada olay yeniden tetiklenebilir.
Son durum için yıldıza bir collected bayrağı eklemek yeterince basit bir önlemdir:
public class Star : MonoBehaviour
{
private bool collected;
private void OnTriggerEnter2D(Collider2D other)
{
if (collected) return;
if (!other.CompareTag("Player")) return;
collected = true;
Debug.Log("Yıldız toplandı");
ScoreManager.Instance.AddScore(10);
Destroy(gameObject);
}
}
Her yıldız için Console'da tek bir "Yıldız toplandı" satırı görmelisin. Aynı yıldız için iki satır çıkıyorsa temas olayı gerçekten tekrar ediyor demektir. Hiç satır çıkmıyorsa sorun trigger ayarlarındadır.
Teşhiste kodu tahminle değiştirmek yerine Debug.Log ile zinciri adım adım doğrula: temas, AddScore çağrısı, olay, yazı güncellemesi. Log'un kesildiği yer sorunun bulunduğu yerdir.
Sık Sorulan Sorular
Unity'de skoru static değişkende mi yoksa ayrı bir yöneticide mi tutmalıyım?
Çok küçük bir denemede static değişken işini görür. Ancak puan değişince arayüzü bilgilendirmek, kuralları tek yerde toplamak ve ileride başka sistemleri (can, süre) eklemek için ayrı bir yönetici daha düzenli bir temel sunar. İstersen ikisini birleştirip değeri static veri sınıfında, mantığı yöneticide tutabilirsin.
Oyunu kapatıp açınca en yüksek skoru nasıl saklarım?
Basit sayısal değerler için PlayerPrefs kullanabilirsin. Oyun bittiğinde mevcut skoru kayıtlı en yüksek skorla karşılaştırır, yalnızca büyükse PlayerPrefs.SetInt ile yazarsın. Oyun açılırken PlayerPrefs.GetInt ile okursun. Daha karmaşık veriler için dosyaya kaydetme gibi başka yöntemler gerekebilir.
Yıldız toplanınca puan yerine can ya da süre eklemek için sistemde ne değişir?
Temas algılama kısmı aynı kalır. Değişen yer, yıldızın çağırdığı yöneticidir: AddScore yerine AddLife ya da AddTime gibi bir metot çağrılır. Bu işler için ayrı yönetici ve ayrı arayüz bileşeni kurmak, her değerin kendi olayını yayınlamasını sağlar ve sistemleri birbirinden bağımsız tutar.
Skor sistemi küçük görünse de olay, yönetici ve arayüz ayrımını doğru kurduğunda hem hata ayıklamak hem genişletmek kolaylaşır. Yıldız toplama örneğindeki bu ayrımı can, süre ya da başka bir toplanabilir nesne için de aynı şekilde kullanabilirsin.