Adding achievements and leaderboards is one of the easiest ways to make a finished prototype feel like a real game. Done well, they give players short-term goals, long-term mastery targets, and a social reason to keep playing without forcing you to redesign your entire core loop.
In this guide you will:
- Design a simple achievement model that fits almost any Unity project.
- Implement achievement tracking that is decoupled from your gameplay code.
- Wire a basic leaderboard using score events and a pluggable backend.
- Learn how to integrate with platform services later (Steam, Google Play, Apple Game Center) without rewriting everything.
This tutorial assumes you are comfortable with C# scripts, Scenes, and prefabs in Unity.
Step 1: Decide what achievements are actually for
Before you add popups and badges, decide what behavior you want to encourage.
- Onboarding: “Complete the tutorial”, “Finish your first run”, “Beat your first boss”.
- Skill growth: “Clear a level without taking damage”, “Win three games in a row”.
- Exploration: “Find a hidden area”, “Use every weapon at least once”.
- Retention: “Play on 3 different days”, “Reach level 20”, “Defeat 500 enemies”.
Write down 5–10 achievements that:
- Connect directly to your core loop (not random side activities).
- Scale from easy wins to serious mastery.
- Are observable in code (you can tell when they happen).
If you struggle to define them, your game’s goals may be unclear—fixing that comes before any UI work.
Step 2: Create a reusable achievement data model
You do not want achievements scattered across random scripts. Instead, use a small data model that your systems can refer to in a consistent way.
In Unity, one clean option is a ScriptableObject:
using UnityEngine;
[CreateAssetMenu(menuName = "Progress/Achievement")]
public class AchievementDefinition : ScriptableObject
{
public string id; // e.g. "complete_tutorial"
public string displayName; // e.g. "First Steps"
public string description; // e.g. "Finish the tutorial."
public int points; // optional, for meta score
public Sprite icon; // UI icon
}
Then create a collection to hold all achievements:
using UnityEngine;
using System.Collections.Generic;
[CreateAssetMenu(menuName = "Progress/Achievement Collection")]
public class AchievementCollection : ScriptableObject
{
public List<AchievementDefinition> achievements;
public AchievementDefinition GetById(string id)
{
return achievements.Find(a => a.id == id);
}
}
Now your game can look up achievements by id rather than hard-coding text in scripts.
Step 3: Implement an achievement manager with events
Instead of making every script know about achievements, create a central AchievementManager that listens for events like “tutorial completed” or “enemy defeated”.
A simple pattern:
using UnityEngine;
using System;
using System.Collections.Generic;
public class AchievementManager : MonoBehaviour
{
public static AchievementManager Instance { get; private set; }
[SerializeField] private AchievementCollection collection;
private readonly HashSet<string> unlockedIds = new HashSet<string>();
public event Action<AchievementDefinition> OnAchievementUnlocked;
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
LoadProgress();
}
public bool IsUnlocked(string id) => unlockedIds.Contains(id);
public void Unlock(string id)
{
if (unlockedIds.Contains(id)) return;
var definition = collection.GetById(id);
if (definition == null)
{
Debug.LogWarning($"Missing achievement definition for id: {id}");
return;
}
unlockedIds.Add(id);
SaveProgress();
OnAchievementUnlocked?.Invoke(definition);
Debug.Log($"Unlocked achievement: {definition.displayName}");
}
private void LoadProgress()
{
// For a real project, use JSON or a save system.
// For now, use PlayerPrefs as a simple demo.
var saved = PlayerPrefs.GetString("achievements", string.Empty);
if (string.IsNullOrEmpty(saved)) return;
foreach (var id in saved.Split(','))
{
if (!string.IsNullOrWhiteSpace(id))
unlockedIds.Add(id);
}
}
private void SaveProgress()
{
var joined = string.Join(",", unlockedIds);
PlayerPrefs.SetString("achievements", joined);
PlayerPrefs.Save();
}
}
Now gameplay code can simply say AchievementManager.Instance.Unlock("complete_tutorial");
The manager handles persistence and UI notifications.
Step 4: Hook achievements into real gameplay events
To avoid tight coupling, send semantic events from your systems. Here are a few examples:
- Tutorial scene: when the last step is done, call
Unlock("complete_tutorial"). - Kill counter: when total enemies killed hits 100, unlock
kill_100_enemies. - Session tracking: when player finishes a daily run, update a streak and unlock streak-based achievements.
Prefer small, focused scripts that bridge gameplay to the manager, such as:
public class TutorialCompletionTracker : MonoBehaviour
{
public void OnTutorialFinished()
{
AchievementManager.Instance.Unlock("complete_tutorial");
}
}
This keeps your main systems readable while still feeding progress into achievements.
Step 5: Design and implement a simple leaderboard
Leaderboards answer a different question: “How do I compare to other players?”
Start with a minimal model:
[Serializable]
public class LeaderboardEntry
{
public string playerName;
public int score;
}
In a production game you will likely use:
- A platform API (Steam, PlayFab, Epic Online Services, Apple, Google).
- A custom backend (serverless functions plus a database).
For a prototype or offline game, you can start with local leaderboards stored via JSON.
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
public class LocalLeaderboard : MonoBehaviour
{
[SerializeField] private int maxEntries = 10;
public List<LeaderboardEntry> Entries { get; private set; } = new List<LeaderboardEntry>();
private void Awake()
{
Load();
}
public void SubmitScore(string playerName, int score)
{
Entries.Add(new LeaderboardEntry { playerName = playerName, score = score });
Entries = Entries
.OrderByDescending(e => e.score)
.Take(maxEntries)
.ToList();
Save();
}
private void Load()
{
var json = PlayerPrefs.GetString("leaderboard", string.Empty);
if (string.IsNullOrEmpty(json)) return;
var wrapper = JsonUtility.FromJson<LeaderboardWrapper>(json);
if (wrapper != null && wrapper.entries != null)
Entries = wrapper.entries;
}
private void Save()
{
var wrapper = new LeaderboardWrapper { entries = Entries };
var json = JsonUtility.ToJson(wrapper);
PlayerPrefs.SetString("leaderboard", json);
PlayerPrefs.Save();
}
[System.Serializable]
private class LeaderboardWrapper
{
public List<LeaderboardEntry> entries;
}
}
In your game over or victory screen:
- Ask for the player name (or use a profile).
- Submit the score to
LocalLeaderboard. - Refresh a simple UI list bound to
Entries.
Later, you can swap the internals of SubmitScore and Load/Save to call a real API without changing your UI.
Step 6: Integrate with platform services (optional)
If you plan to ship on Steam, mobile, or consoles, achievements and leaderboards often come from:
- Steamworks on PC.
- Google Play Games Services on Android.
- Apple Game Center on iOS and macOS.
The pattern is:
- Keep your local achievement and score model as your source of truth.
- Add thin adapters that:
- Map local
ids to platform IDs. - Mirror unlock and score events to the platform SDK.
- Map local
This way:
- Your game logic remains testable without network access.
- You can still provide offline achievements/leaderboards for players who never sign in.
Step 7: UX and player feedback
Well-designed achievements and leaderboards are as much UX as they are code.
- Show a small, non-blocking toast when an achievement unlocks.
- Provide a dedicated achievements screen where players can browse what they have and what is left.
- On leaderboards, allow filters like “friends only” or “daily / weekly / all-time” once you move to an online service.
- Avoid cluttering the screen with notifications during critical gameplay moments (boss fights, cutscenes).
Ask in playtests:
- Do players understand why they got each achievement?
- Do leaderboards motivate them, or make them feel discouraged?
- Are there hidden achievements that feel unfair rather than fun to discover?
Mini implementation checklist
- [ ] Define 5–10 achievements that reinforce your core loop.
- [ ] Create
AchievementDefinitionandAchievementCollectionassets. - [ ] Implement
AchievementManagerwith save/load and anOnAchievementUnlockedevent. - [ ] Wire at least 3 achievements into real gameplay events.
- [ ] Implement a basic local leaderboard and hook it into your game over flow.
- [ ] Add a minimal UI for viewing achievements and scores.
Once this backbone is in place, adding new achievements or alternative leaderboard backends becomes a content task instead of a refactor.
Where to go next
- If you want deeper monetization and live-ops systems, pair this post with a business-focused guide or course on live service design.
- If you are shipping on Steam or consoles, explore their official SDK docs for platform achievements and cloud-backed leaderboards.
- For now, get a small set of achievements and a simple leaderboard working in one scene—then reuse the same architecture across the rest of your project.