In this guide, we’ll explore how to use Achievements and Sub Achievements to create a Battle Pass. It has become an extremely common feature in almost all live-service style games where a player is rewarded incrementally for progress they make in the game.
For our example, we will define a parent Achievement, and many Sub Achievements to act as the steps in the Battle Pass. Each will have a dependency on the previous Sub Achievement to create a chain for the player to progress through sequentially.
Let’s start by taking a look at the server-side code that we’ll be using to implement the Battle Pass, beginning with creating the config file where we can define the pass and sub-steps.
Next, we create our Hiro system definitions to implement a Battle Pass in our game. While each registered system needs a config file, you can set up base-system-dev1.json however you like, we only need to focus on achievements for this example in the base-achievements-dev1.json file.
For this example, we create a parent Achievement to act as the Battle Pass itself. We then create multiple Sub Achievements to act as the steps of the Battle Pass. Each step has a precondition_ids array with the previous step’s id. This is what allows us to have the sequential progression through the steps. We also set the max_count of the steps to define how much xp/progress is required to complete that step.
You will also want to assign rewards to these steps based on your game. Maybe some steps reward currencies, while others might reward items.
This file bootstraps our game with a list of systems to be used, and provides a list of systems for deterministic start-up. In our case, we’re initializing the Achievements system from Hiro.
1
2
3
4
5
6
7
8
9
// ...systems.Add(nakamaSystem);// Add the Achievements systemvarachievementsSystem=newAchievementsSystem(logger,nakamaSystem);systems.Add(achievementsSystem);returnTask.FromResult(systems);// ...
publicclassBattlePassDisplay:MonoBehaviour{ [SerializeField]privateSliderprogressSlider;privateAchievementsSystem_achievementsSystem;privateIDisposable_achievementsDisposer;privateIAchievement_battlePass;publicIEnumeratorStart(){// Get the relevant systems and listen for updates._achievementsSystem=this.GetSystem<AchievementsSystem>();_achievementsDisposer=SystemObserver<AchievementsSystem>.Create(_achievementsSystem,OnAchievementsSystemChanged);// Wait until achievements are ready to begin.yieldreturnnewWaitUntil(()=>_achievementsSystem.IsInitialized);// Refresh the system to get the latest data._=_achievementsSystem.RefreshAsync();}publicasyncvoidLevelUp(){// Progress the pass by 400xp.varsubAchievements=_battlePass.SubAchievements.Select(x=>x.Key);await_achievementsSystem.UpdateAchievementsAsync(subAchievements,400);}privatevoidOnAchievementsSystemChanged(AchievementsSystemsystem){// Load the first pass we find as we only have one._battlePass=system.GetAvailableAchievements("pass").FirstOrDefault();if(_battlePass==null){Debug.LogWarning("No available battle pass.");return;}foreach(varstepin_battlePass.SubAchievements){// Create UI element to display the step's reward(s).Debug.Log(step.Value.Reward);}// Update the pass slider element to visualize progress.varcompletedSteps=_battlePass.SubAchievements.Count(x=>x.Value.Count>=x.Value.MaxCount);progressSlider.value=completedSteps;}privatevoidOnDestroy(){_achievementsDisposer?.Dispose();}}