Get Your Personalized Game Dev Plan Tailored tips, tools, and next steps - just for you.

Building a Robust Achievement System in Unity with ScriptableObjects and PlayerPrefs

Posted by Gemma Ellison
./
July 16, 2025

Let’s face it: most achievement systems in games feel tacked on, an afterthought that doesn’t truly enhance the player experience. It’s time to ditch the generic “collect 100 coins” approach and build achievement systems that are meaningful, engaging, and tightly integrated with your game’s core mechanics. This isn’t just about slapping on a digital badge; it’s about rewarding players for mastering your game and exploring its depth. This article provides a practical guide to building a robust achievement system in Unity, leveraging the power of ScriptableObjects and PlayerPrefs, without relying on any external assets.

Defining Achievements with ScriptableObjects

Why ScriptableObjects? Because they offer a clean, efficient, and easily manageable way to define your achievements. Forget hardcoding; let’s create a flexible system.

  1. Create a ScriptableObject Script: Create a new C# script named Achievement.
using UnityEngine;

[CreateAssetMenu(fileName = "New Achievement", menuName = "Achievement System/Achievement")]
public class Achievement : ScriptableObject
{
    public string achievementName;
    [TextArea]
    public string description;
    public Sprite icon;
    public string id; // Unique identifier
    public int requiredAmount; // Amount needed to unlock
    public string playerPrefKey; //Key to store player progress

    public bool IsUnlocked()
    {
        return PlayerPrefs.GetInt(id, 0) == 1;
    }
}
  1. Understanding the Fields:

    • achievementName: The name displayed to the player.
    • description: A short explanation of the achievement.
    • icon: The image representing the achievement.
    • id: A unique identifier for the achievement (e.g., “ReachLevel5”). Crucial for persistence.
    • requiredAmount: The amount needed to unlock the achievement.
    • playerPrefKey: The key used to store the achievement’s progress. This should be unique per achievement to avoid conflicts.
    • IsUnlocked(): Checks if the achievement is unlocked based on the id.
  2. Creating Achievement Instances: In your Project window, right-click, select “Create” -> “Achievement System” -> “Achievement.” Create several achievement instances, filling in the details for each.

Challenge: Developers often fall into the trap of using generic achievement names and descriptions.

Solution: Tailor each achievement to your game’s specific mechanics and lore. Make them feel rewarding and meaningful. For example, instead of “Kill 10 Enemies,” try “Vanquish the Goblin Horde.”

Tracking Player Progress with PlayerPrefs

PlayerPrefs is your reliable, albeit basic, tool for storing player data locally. While not suitable for sensitive information or large datasets, it’s perfect for tracking achievement progress.

  1. Centralized Progress Tracking: Create a GameManager or similar class to handle achievement progress.
using UnityEngine;
using System.Collections.Generic;

public class GameManager : MonoBehaviour
{
    public List<Achievement> achievements; // Assign in the Inspector
    public static GameManager Instance;

    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }


    public void ReportProgress(string achievementID, int amount)
    {
        Achievement achievement = achievements.Find(a => a.id == achievementID);
        if (achievement != null && !achievement.IsUnlocked())
        {

            int currentProgress = PlayerPrefs.GetInt(achievement.playerPrefKey, 0);
            currentProgress += amount;
            PlayerPrefs.SetInt(achievement.playerPrefKey, currentProgress);
            PlayerPrefs.Save();

            if (currentProgress >= achievement.requiredAmount)
            {
                UnlockAchievement(achievement);
            }
        }
    }

    private void UnlockAchievement(Achievement achievement)
    {
        PlayerPrefs.SetInt(achievement.id, 1);
        PlayerPrefs.Save();
        Debug.Log("Achievement Unlocked: " + achievement.achievementName);
        // Trigger UI notification here
    }

    public int GetProgress(string playerPrefKey)
    {
      return PlayerPrefs.GetInt(playerPrefKey, 0);
    }
}
  1. Assign Achievements: Drag your created achievement ScriptableObject instances to the achievements List in the Inspector of your GameManager object.

  2. Reporting Progress: Call GameManager.Instance.ReportProgress(achievementID, amount) whenever the player makes progress towards an achievement. For example, when the player kills an enemy:

GameManager.Instance.ReportProgress("KillEnemy", 1);

Common Mistake: Forgetting to call PlayerPrefs.Save() after setting a value. Changes won’t persist if you don’t.

Solution: Always call PlayerPrefs.Save() after any PlayerPrefs.SetInt(), SetFloat(), or SetString() operation.

Displaying Achievement Notifications

Visual feedback is crucial. Let’s create a simple UI notification system to inform players when they unlock an achievement.

  1. Create a UI Panel: In your scene, create a UI Panel. Add a Text element for the achievement name and another for the description. Also add an Image element to show the achievement icon.

  2. Notification Script: Create a AchievementNotification script and attach it to the UI Panel.

using UnityEngine;
using UnityEngine.UI;
using System.Collections;

public class AchievementNotification : MonoBehaviour
{
    public Text achievementNameText;
    public Text achievementDescriptionText;
    public Image achievementIconImage;
    public float displayTime = 3f;

    private void Start()
    {
        gameObject.SetActive(false);
    }

    public void ShowAchievement(Achievement achievement)
    {
        achievementNameText.text = achievement.achievementName;
        achievementDescriptionText.text = achievement.description;
        achievementIconImage.sprite = achievement.icon;
        gameObject.SetActive(true);
        StartCoroutine(HideAfterDelay());
    }

    private IEnumerator HideAfterDelay()
    {
        yield return new WaitForSeconds(displayTime);
        gameObject.SetActive(false);
    }
}
  1. Integrate with GameManager: Modify the UnlockAchievement method in GameManager to trigger the UI notification. First, you must create a public variable to reference the AchievementNotification object.
public class GameManager : MonoBehaviour
{
    public List<Achievement> achievements; // Assign in the Inspector
    public AchievementNotification achievementNotification;
    public static GameManager Instance;

    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }

    public void ReportProgress(string achievementID, int amount)
    {
        Achievement achievement = achievements.Find(a => a.id == achievementID);
        if (achievement != null && !achievement.IsUnlocked())
        {

            int currentProgress = PlayerPrefs.GetInt(achievement.playerPrefKey, 0);
            currentProgress += amount;
            PlayerPrefs.SetInt(achievement.playerPrefKey, currentProgress);
            PlayerPrefs.Save();

            if (currentProgress >= achievement.requiredAmount)
            {
                UnlockAchievement(achievement);
            }
        }
    }

    private void UnlockAchievement(Achievement achievement)
    {
        PlayerPrefs.SetInt(achievement.id, 1);
        PlayerPrefs.Save();
        Debug.Log("Achievement Unlocked: " + achievement.achievementName);
        achievementNotification.ShowAchievement(achievement);
    }

    public int GetProgress(string playerPrefKey)
    {
      return PlayerPrefs.GetInt(playerPrefKey, 0);
    }
}

Pitfall: UI notifications that are too intrusive or disruptive.

Solution: Design your notifications to be subtle and informative, not distracting. Consider using animations or fading effects to make them less jarring.

Beyond the Basics: Elevating Your Achievement System

This foundation is just the beginning. Here’s how to take your achievement system to the next level:

  • Persistent Progress UI: Create a dedicated UI panel to display all achievements, their descriptions, icons, and current progress. The GameManager would need to expose getter functions for each achievement’s progress.
  • Tiered Achievements: Implement achievements with multiple tiers, offering increasing rewards for greater challenges.
  • Hidden Achievements: Add secret achievements that players must discover through exploration or experimentation.
  • Dynamic Descriptions: Craft descriptions that change as the player progresses towards an achievement, providing a sense of ongoing accomplishment.

Building a compelling achievement system is about more than just ticking boxes. It’s about rewarding player dedication, encouraging exploration, and enriching the overall game experience. By leveraging ScriptableObjects, PlayerPrefs, and creative design, you can create a system that truly resonates with your players.