In this post we’re going to add Achievements to the Third Person Template project we have created in the previous post.

Setting up Achievements Name and ID

Before we type any code, we need to inform our game about each achievement (including the name and it’s ID). Remember that Steam offers the Spacewar game as a sample project in order to integrate any platform specific features to our game that hasn’t yet been Greenlit. Having said that, we’re going to use Spacewar’s achievements for our game.

To inform our game about each achievement, open up the DefaultEngine.ini located inside the Config folder and in [OnlineSubsystemSteam] category, add the following lines:

DefaultEngine.ini Achievement_0_Id="ACH_WIN_ONE_GAME" Achievement_1_Id="ACH_WIN_100_GAMES" Achievement_3_Id="ACH_TRAVEL_FAR_SINGLE" 1 2 3 Achievement_0_Id = "ACH_WIN_ONE_GAME" Achievement_1_Id = "ACH_WIN_100_GAMES" Achievement_3_Id = "ACH_TRAVEL_FAR_SINGLE"

The id and name of each achievement was retrieved from the following Steamworks screenshot:

If your game gets Greenlit, you can add your own achievements.

Progressing Achievements

In order to progress any achievement, we’re going to use the following logic:

We’re going to cache each achievement when the game starts (BeginPlay).

If the player completes an achievement, we’re going to search the cached achievements and if the achievement is valid we’re going to complete it.

Open up the header file of your Character class and add the following header file:

#include "OnlineStats.h"

Then, add the following code:

Character's Header file virtual void BeginPlay() override; /** Get all the available achievements and cache them */ void QueryAchievements(); /** Called when the cache finishes */ void OnQueryAchievementsComplete(const FUniqueNetId& PlayerId, const bool bWasSuccessful); /** Updates the achievement progress */ void UpdateAchievementProgress(const FString& Id, float Percent); /** The object we're going to use in order to progress any achievement */ FOnlineAchievementsWritePtr AchievementsWriteObjectPtr; /** Progress win game achievement */ UFUNCTION(BlueprintCallable, Category = SteamTut) void WinGameAchievement(); /** Progress win 100 games achievement */ UFUNCTION(BlueprintCallable, Category = SteamTut) void WinManyGamesAchievement(); /** Progress travel far away single achievement */ UFUNCTION(BlueprintCallable, Category = SteamTut) void TravelFarAwayAchievement(); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 virtual void BeginPlay ( ) override ; /** Get all the available achievements and cache them */ void QueryAchievements ( ) ; /** Called when the cache finishes */ void OnQueryAchievementsComplete ( const FUniqueNetId & PlayerId , const bool bWasSuccessful ) ; /** Updates the achievement progress */ void UpdateAchievementProgress ( const FString & Id , float Percent ) ; /** The object we're going to use in order to progress any achievement */ FOnlineAchievementsWritePtr AchievementsWriteObjectPtr ; /** Progress win game achievement */ UFUNCTION ( BlueprintCallable , Category = SteamTut ) void WinGameAchievement ( ) ; /** Progress win 100 games achievement */ UFUNCTION ( BlueprintCallable , Category = SteamTut ) void WinManyGamesAchievement ( ) ; /** Progress travel far away single achievement */ UFUNCTION ( BlueprintCallable , Category = SteamTut ) void TravelFarAwayAchievement ( ) ;

Before we implement any more logic, inside your character’s source file, add the following libraries and macros:

Libraries & macros #include "OnlineAchievementsInterface.h" #include "OnlineIdentityInterface.h" #include "OnlineSubsystem.h" #include "Engine/LocalPlayer.h" #define ACH_WIN_ONE_GAME FString("ACH_WIN_ONE_GAME") #define AC_WIN_100_GAMES FString("ACH_WIN_100_GAMES") #define AC_TRAVEL_FAR_SINGLE FString("ACH_TRAVEL_FAR_SINGLE") 1 2 3 4 5 6 7 8 9 #include "OnlineAchievementsInterface.h" #include "OnlineIdentityInterface.h" #include "OnlineSubsystem.h" #include "Engine/LocalPlayer.h" #define ACH_WIN_ONE_GAME FString("ACH_WIN_ONE_GAME") #define AC_WIN_100_GAMES FString("ACH_WIN_100_GAMES") #define AC_TRAVEL_FAR_SINGLE FString("ACH_TRAVEL_FAR_SINGLE")

The reason we wrap each achievement inside a macro is because it’s less prone to errors since we can avoid typing wrong strings later in our code. Let’s implement the QueryAchievements functions and BeginPlay:

BeginPlay, QueryAchievements, OnQueryAchievementsComplete void ASteamIntegrationTutCharacter::BeginPlay() { Super::BeginPlay(); //Cache all the available achievements QueryAchievements(); } void ASteamIntegrationTutCharacter::QueryAchievements() { //Get the online sub system IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get(); if (OnlineSub) { //Get the Identity from the sub system //Meaning our player's profile and various online services IOnlineIdentityPtr Identity = OnlineSub->GetIdentityInterface(); if (Identity.IsValid()) { //Get a thread-safe pointer (for more info check out this link: https://docs.unrealengine.com/latest/INT/API/Runtime/Core/Templates/TSharedPtr/index.html ) TSharedPtr<const FUniqueNetId> UserId = Identity->GetUniquePlayerId(0); //Get the achievements interface for this platform IOnlineAchievementsPtr Achievements = OnlineSub->GetAchievementsInterface(); if (Achievements.IsValid()) { //Cache all the game's achievements for future use and bind the OnQueryAchievementsComplete function to fire when we're finished caching Achievements->QueryAchievements(*UserId.Get(), FOnQueryAchievementsCompleteDelegate::CreateUObject(this, &ASteamIntegrationTutCharacter::OnQueryAchievementsComplete)); } } } } void ASteamIntegrationTutCharacter::OnQueryAchievementsComplete(const FUniqueNetId& PlayerId, const bool bWasSuccessful) { if (bWasSuccessful) { GLog->Log("achievements were cached"); } else { GLog->Log("failed to cache achievements"); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 void ASteamIntegrationTutCharacter :: BeginPlay ( ) { Super :: BeginPlay ( ) ; //Cache all the available achievements QueryAchievements ( ) ; } void ASteamIntegrationTutCharacter :: QueryAchievements ( ) { //Get the online sub system IOnlineSubsystem * OnlineSub = IOnlineSubsystem :: Get ( ) ; if ( OnlineSub ) { //Get the Identity from the sub system //Meaning our player's profile and various online services IOnlineIdentityPtr Identity = OnlineSub -> GetIdentityInterface ( ) ; if ( Identity . IsValid ( ) ) { //Get a thread-safe pointer (for more info check out this link: https://docs.unrealengine.com/latest/INT/API/Runtime/Core/Templates/TSharedPtr/index.html ) TSharedPtr < const FUniqueNetId > UserId = Identity -> GetUniquePlayerId ( 0 ) ; //Get the achievements interface for this platform IOnlineAchievementsPtr Achievements = OnlineSub -> GetAchievementsInterface ( ) ; if ( Achievements . IsValid ( ) ) { //Cache all the game's achievements for future use and bind the OnQueryAchievementsComplete function to fire when we're finished caching Achievements -> QueryAchievements ( * UserId . Get ( ) , FOnQueryAchievementsCompleteDelegate :: CreateUObject ( this , &ASteamIntegrationTutCharacter :: OnQueryAchievementsComplete ) ) ; } } } } void ASteamIntegrationTutCharacter :: OnQueryAchievementsComplete ( const FUniqueNetId & PlayerId , const bool bWasSuccessful ) { if ( bWasSuccessful ) { GLog -> Log ( "achievements were cached" ) ; } else { GLog -> Log ( "failed to cache achievements" ) ; } }

Then, let’s implement the UpdateAchievementProgress function:

UpdateAchievementProgress implementation void ASteamIntegrationTutCharacter::UpdateAchievementProgress(const FString& Id, float Percent) { //Get the online sub system IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get(); if (OnlineSub) { //Get the Identity from the sub system //Meaning our player's profile and various online services IOnlineIdentityPtr Identity = OnlineSub->GetIdentityInterface(); if (Identity.IsValid()) { //Get a thread-safe pointer (for more info check out this link: https://docs.unrealengine.com/latest/INT/API/Runtime/Core/Templates/TSharedPtr/index.html ) TSharedPtr<const FUniqueNetId> UserId = Identity->GetUniquePlayerId(0); //Get the achievements interface for this platform IOnlineAchievementsPtr Achievements = OnlineSub->GetAchievementsInterface(); if (Achievements.IsValid() && (!AchievementsWriteObjectPtr.IsValid() || !AchievementsWriteObjectPtr->WriteState != EOnlineAsyncTaskState::InProgress)) { //Make a shared pointer for achievement writing AchievementsWriteObjectPtr = MakeShareable(new FOnlineAchievementsWrite()); //Sets the progress of the desired achievement - does nothing if the id is not valid AchievementsWriteObjectPtr->SetFloatStat(*Id, Percent); //Write the achievements progress FOnlineAchievementsWriteRef AchievementsWriteObjectRef = AchievementsWriteObjectPtr.ToSharedRef(); Achievements->WriteAchievements(*UserId, AchievementsWriteObjectRef); } } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 void ASteamIntegrationTutCharacter :: UpdateAchievementProgress ( const FString & Id , float Percent ) { //Get the online sub system IOnlineSubsystem * OnlineSub = IOnlineSubsystem :: Get ( ) ; if ( OnlineSub ) { //Get the Identity from the sub system //Meaning our player's profile and various online services IOnlineIdentityPtr Identity = OnlineSub -> GetIdentityInterface ( ) ; if ( Identity . IsValid ( ) ) { //Get a thread-safe pointer (for more info check out this link: https://docs.unrealengine.com/latest/INT/API/Runtime/Core/Templates/TSharedPtr/index.html ) TSharedPtr < const FUniqueNetId > UserId = Identity -> GetUniquePlayerId ( 0 ) ; //Get the achievements interface for this platform IOnlineAchievementsPtr Achievements = OnlineSub -> GetAchievementsInterface ( ) ; if ( Achievements . IsValid ( ) && ( ! AchievementsWriteObjectPtr . IsValid ( ) || ! AchievementsWriteObjectPtr -> WriteState != EOnlineAsyncTaskState :: InProgress ) ) { //Make a shared pointer for achievement writing AchievementsWriteObjectPtr = MakeShareable ( new FOnlineAchievementsWrite ( ) ) ; //Sets the progress of the desired achievement - does nothing if the id is not valid AchievementsWriteObjectPtr -> SetFloatStat ( * Id , Percent ) ; //Write the achievements progress FOnlineAchievementsWriteRef AchievementsWriteObjectRef = AchievementsWriteObjectPtr . ToSharedRef ( ) ; Achievements -> WriteAchievements ( * UserId , AchievementsWriteObjectRef ) ; } } } }

The last thing we need is to implement the achievement functions. These function are going to call the UpdateAchievementProgress function with the desired Achievement they wish to complete. I’ve added the BlueprintCallable specifier in order to test them using key binds in the Blueprint graph. Here is the logic for each function:

Achievement functions void ASteamIntegrationTutCharacter::WinGameAchievement() { UpdateAchievementProgress(ACH_WIN_ONE_GAME, 100.f); } void ASteamIntegrationTutCharacter::WinManyGamesAchievement() { UpdateAchievementProgress(AC_WIN_100_GAMES, 100.f); } void ASteamIntegrationTutCharacter::TravelFarAwayAchievement() { UpdateAchievementProgress(AC_TRAVEL_FAR_SINGLE, 100.f); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 void ASteamIntegrationTutCharacter :: WinGameAchievement ( ) { UpdateAchievementProgress ( ACH_WIN_ONE_GAME , 100.f ) ; } void ASteamIntegrationTutCharacter :: WinManyGamesAchievement ( ) { UpdateAchievementProgress ( AC_WIN_100_GAMES , 100.f ) ; } void ASteamIntegrationTutCharacter :: TravelFarAwayAchievement ( ) { UpdateAchievementProgress ( AC_TRAVEL_FAR_SINGLE , 100.f ) ; }

At this point, launch your game on Standalone mode (this assumes that you have enabled the Online Subsystem Steam plugin which resides in the Online Platform category) and test your achievements!