How to integrate Steam achievements into Unity

Achievements are great for giving your players something to work towards and rewarding them for playing your game. Now you have these cool ideas for Steam achievements in your game but don't know how to integrate them into your game? Read on. These tips come directly from my experience with my game Shopkeeper Simulator VR.

Overview

The steps we will be following are:

  1. Creating achievements in Steamworks account
  2. Download and install Steamworks.NET
  3. Creating the SteamManager
  4. Creating the AchievementManager
  5. Add code in your project where applicable

Creating achievements

Let's start with the creative part. Log into your Steamworks account and go into the App Admin for your game. Under "Community Presence", click "Achievements" which opens up die Achievement Configuration page.

Click on the New Achievement button which creates a new row. First you might want to think about a name and a description for the achievement. These will be presented to your players exactly how you type it in here so give it some thought.

Next, create an API name in the leftmost input field. These are the names we will be referring to in our code. I recommend using uppercase letters and underscores to separate words so they look like constants in the editor (what they are basically). Try to group your achievements. If you have multiple achievements regarding the game aspect, "thieves" for example, have the API names contain "_THIEF_". 

Finally, upload an image for your achievement in the locked and unlocked state respectively. Steam recommends taking the colorful image and the same image in grey-scale. They should be 64 x 64 pixels.

At the end, your achievement looks something like this:

Fully configured Steam achievement

Add as many achievements as you like and head to the next chapter.

Download and install Steamworks.NET

We will be using a 3rd party library to connect to Steamworks. It's called Steamworks.NET and is widely used by developers. Head over to steamworks.github.io/installation/ and download the latest release as a unitypackage file.

The following are basically condensed directions from the web site.

In your Unity project, you can import the unitypackage directly. Open up Unity and load your project. Go to "Assets" - "Import Customer Package..." in the Unity main menu and find your unitypackage file. Import everything into the project root folder. When the import is complete, a file "steam_appid.txt" is copied to your project root folder. Open it and replace 480 with your own App ID. Restart Unity so that the plugin can apply the changes.

Newsletter Subscription

Creating the SteamManager

We are going to need a script that loads the necessary Steam libraries and initializes the Steamworks API. Thankfully, there's the SteamManager script in the Steamworks.NET plugin.

Create a GameObject and attach the SteamManager script to it:

GameObject SteamManager

If you run your game and get any error messages, refer to Valve's documentation as there are multiple reasons for this.

Creating the AchievementManager

The AchievementManager needs to be created by you. In its most basic nature, it is a typical Singleton that offers a public method to unlock an achievement. If you uncomment the DebugAchievements call in the Start() method, it also prints out all achievements and their current status:

using Steamworks;
using UnityEngine;

public class AchievementManager : MonoBehaviour
{
    // Our GameID
    private CGameID m_GameID;

    // Callback for Achievement stored
    protected Callback<UserAchievementStored_t> m_UserAchievementStored;

    public static AchievementManager instance = null;

    private Achievement[] Achievements = new Achievement[] {
        new Achievement(AchievementEnum.ACH_MONEY_10_BILLS, "There must be order", ""),
        new Achievement(AchievementEnum.ACH_DAYS_1, "First Day", ""),
        new Achievement(AchievementEnum.ACH_DAYS_3, "Established", ""),
        new Achievement(AchievementEnum.ACH_THIEF_AWAY, "Get outta here!", "")
    };


    // Use this for initialization
    void Start()
    {
        if (instance == null)
        {
            instance = this;
        }
        else
        {
            Destroy(gameObject);
        }

        DontDestroyOnLoad(gameObject);

        //DebugAchievements();
    }

    private void DebugAchievements()
    {
        if (SteamManager.Initialized)
        {
            Debug.Log("SteamManager initialized.");

            // Cache the GameID for use in the Callbacks
            m_GameID = new CGameID(SteamUtils.GetAppID());
            m_UserAchievementStored = Callback<UserAchievementStored_t>.Create(OnAchievementStored);
            PrintAchievements();
        }
        else
        {
            Debug.LogWarning("SteamManager NOT initialized!");
        }
    }

    private void PrintAchievements()
    {
        foreach (Achievement ach in Achievements)
        {
            bool ret = SteamUserStats.GetAchievement(ach.AchievementID.ToString(), out ach.Achieved);
            if (ret)
            {
                ach.Name = SteamUserStats.GetAchievementDisplayAttribute(ach.AchievementID.ToString(), "name");
                ach.Description = SteamUserStats.GetAchievementDisplayAttribute(ach.AchievementID.ToString(), "desc");

                Debug.LogFormat("Achievement Name: {0} --- Unlocked: {1}", ach.Name, ach.Achieved);
            }
            else
            {
                Debug.LogWarning("SteamUserStats.GetAchievement failed for Achievement " + ach.AchievementID + "\nIs it registered in the Steam Partner site?");
            }
        }
    }

    /// 
    /// Calls SetAchiemement. Steam docs: 
    /// This method sets a given achievement to achieved and sends the results to Steam. You can set a given achievement multiple times so you don't
    /// need to worry about only setting achievements that aren't already set.
    /// This is an asynchronous call which will trigger two callbacks: OnUserStatsStored() and OnAchievementStored()
    /// 
    /// 
    public void UnlockAchievement(AchievementEnum achievement)
    {
        SteamUserStats.SetAchievement(achievement.ToString());
    }

    //-----------------------------------------------------------------------------
    // Purpose: An achievement was stored
    //-----------------------------------------------------------------------------
    public void OnAchievementStored(UserAchievementStored_t pCallback)
    {
        // We may get callbacks for other games' stats arriving, ignore them
        if ((ulong)m_GameID == pCallback.m_nGameID)
        {
            if (0 == pCallback.m_nMaxProgress)
            {
                Debug.Log("Achievement '" + pCallback.m_rgchAchievementName + "' unlocked!");
            }
            else
            {
                Debug.Log("Achievement '" + pCallback.m_rgchAchievementName + "' progress callback, (" + pCallback.m_nCurProgress + "," + pCallback.m_nMaxProgress + ")");
            }
        }
    }
}


The achievement class is just a POJO that encapsulates some achievement properties:

public class Achievement
{
    public AchievementEnum AchievementID;
    public string Name;
    public string Description;
    public bool Achieved;

    /// 
    /// Creates an Achievement. You must also mirror the data provided here in https://partner.steamgames.com/apps/achievements/yourappid
    /// 
    /// The "API Name Progress Stat" used to uniquely identify the achievement.
    /// The "Display Name" that will be shown to players in game and on the Steam Community.
    /// The "Description" that will be shown to players in game and on the Steam Community.
    public Achievement(AchievementEnum achievementID, string name, string desc)
    {
        AchievementID = achievementID;
        Name = name;
        Description = desc;
        Achieved = false;
    }
}

And AchievementEnum is just a simple enum corresponding to the defined achievements:

public enum AchievementEnum : int
{
    ACH_MONEY_10_BILLS,
    ACH_DAYS_1,
    ACH_DAYS_3,
    ACH_THIEF_AWAY,
    ACH_THIEF_STOLEN,
    ACH_TIEF_SERVED
}

Adding code to your project where applicable

Almost there! You now figure out where in your code the UnlockAchievement method needs to be called. As an example, I show you some code from Shopkeeper Simulator VR that unlocks the "Drive away a thief" achievement once the player throws an item at a thief with a certain velocity:

private void OnCollisionEnter(Collision collision)
    {
        if (collision.gameObject.CompareTag("Item"))
        {
            Debug.Log("relativeVelocity " + collision.relativeVelocity.magnitude);

            if (collision.relativeVelocity.magnitude > VelocityThreshold)
            {
                audioSource.PlayOneShot(customer.HitByItemClip);
                customer.LeaveShop();
                AchievementManager.instance.UnlockAchievement(AchievementEnum.ACH_THIEF_AWAY);
            }
        }
    }

The last line of code calls the method from our AchievementManager while passing the enum value.

And that's it! If you found this tutorial helpful, subscribe to my newsletter to be the first to hear about new tutorials.




G
G
Guest
User
PotatoWatts
PotatoWatts
·
One small adjustment to UnlockAchievement() -
    public void UnlockAchievement(AchievementEnum achievement)
    {
        SteamUserStats.SetAchievement(achievement.ToString());
        SteamUserStats.StoreStats();
    } Just add SteamUserStats.StoreStats(); and your achievements should show up through steam.