Score Manager
Complete score management system with high score persistence, combo multipliers with decay, and score change events. Uses singleton pattern for easy global access.
How to Use
Create an empty GameObject named ScoreManager in your scene
Attach the ScoreManager script to it
Set comboDecayTime and maxMultiplier in the inspector
Call ScoreManager.Instance.AddScore(points) from gameplay scripts
Connect onScoreChanged and onHighScoreBeaten events to your UI text elements
Features
- Score tracking with events
- High score persistence (PlayerPrefs)
- Score multiplier support
- Combo system with decay timer
- Animated score display helper
When to Use This
Essential for hyper-casual games, mobile arcade games, and any game that needs score tracking with combo multipliers. Use this when you need persistent high scores, formatted score display (1.5K, 2.3M), and a combo system that rewards consecutive scoring.
Common Mistakes
The singleton uses DontDestroyOnLoad, so don't place it inside a scene that gets additively loaded or you'll get duplicates — the Awake check destroys extras but you'll see a brief frame flicker. The combo multiplier formula (1 + ComboCount / 5) uses integer division, so combos 1-4 all give 1x — adjust the divisor if you want faster multiplier scaling. PlayerPrefs persistence is not tamper-proof; for competitive games, use a server-side leaderboard instead.
Source Code
using UnityEngine;
using UnityEngine.Events;
public class ScoreManager : MonoBehaviour
{
public static ScoreManager Instance { get; private set; }
[Header("Combo Settings")]
[SerializeField] private float comboDecayTime = 3f;
[SerializeField] private int maxMultiplier = 10;
[Header("High Score")]
[SerializeField] private string highScoreKey = "HighScore";
[Header("Events")]
public UnityEvent<int> onScoreChanged;
public UnityEvent<int> onHighScoreBeaten;
public UnityEvent<int> onComboChanged;
public int CurrentScore { get; private set; }
public int HighScore { get; private set; }
public int ComboMultiplier { get; private set; } = 1;
public int ComboCount { get; private set; }
public float ComboTimer { get; private set; }
private float comboTimerMax;
private bool highScoreBeaten;
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
HighScore = PlayerPrefs.GetInt(highScoreKey, 0);
comboTimerMax = comboDecayTime;
}
private void Update()
{
if (ComboCount > 0)
{
ComboTimer -= Time.deltaTime;
if (ComboTimer <= 0f)
{
ResetCombo();
}
}
}
public void AddScore(int points)
{
int earnedPoints = points * ComboMultiplier;
CurrentScore += earnedPoints;
// Combo
ComboCount++;
ComboTimer = comboTimerMax;
ComboMultiplier = Mathf.Min(1 + ComboCount / 5, maxMultiplier);
onComboChanged?.Invoke(ComboMultiplier);
onScoreChanged?.Invoke(CurrentScore);
// High score check
if (CurrentScore > HighScore)
{
HighScore = CurrentScore;
SaveHighScore();
if (!highScoreBeaten)
{
highScoreBeaten = true;
onHighScoreBeaten?.Invoke(HighScore);
}
}
}
public int GetScore()
{
return CurrentScore;
}
public int GetHighScore()
{
return HighScore;
}
public void ResetScore()
{
CurrentScore = 0;
highScoreBeaten = false;
ResetCombo();
onScoreChanged?.Invoke(CurrentScore);
}
public void ResetCombo()
{
ComboCount = 0;
ComboMultiplier = 1;
ComboTimer = 0f;
onComboChanged?.Invoke(ComboMultiplier);
}
public void SetComboDecayTime(float time)
{
comboDecayTime = Mathf.Max(0.5f, time);
comboTimerMax = comboDecayTime;
}
public float GetComboTimerNormalized()
{
if (comboTimerMax <= 0f) return 0f;
return Mathf.Clamp01(ComboTimer / comboTimerMax);
}
private void SaveHighScore()
{
PlayerPrefs.SetInt(highScoreKey, HighScore);
PlayerPrefs.Save();
}
public void ClearHighScore()
{
HighScore = 0;
PlayerPrefs.DeleteKey(highScoreKey);
PlayerPrefs.Save();
}
public string GetFormattedScore()
{
return FormatNumber(CurrentScore);
}
public string GetFormattedHighScore()
{
return FormatNumber(HighScore);
}
public static string FormatNumber(int number)
{
if (number >= 1000000) return (number / 1000000f).ToString("F1") + "M";
if (number >= 1000) return (number / 1000f).ToString("F1") + "K";
return number.ToString();
}
private void OnDestroy()
{
if (Instance == this)
Instance = null;
}
}