While our game may be running without any issues in the editor or even in…
Handling Steam Achievements – Steam Integration Part 2
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:
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:
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:
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:
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:
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:
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!
Hi orfeasel,
I cant find those headers such as “#include “OnlineStats.h”” ,#include “OnlineAchievementsInterface.h”,etc.
My engine version is 4.14.1,Can you help please?
Hello,
That’s weird, have you tried running your project with another version?
-Orfeas
You don’t need it for this tutorial but if you plan on doing anything else with the steam api you need to include these in your “MyProject.h”
PrivateDependencyModuleNames.Add(“Steamworks”);
AddThirdPartyPrivateStaticDependencies(Target, “Steamworks”);
and then include this inside of the header you plan on doing stuff in.
#include
This will allow you to call functions like this
//get the player iID
uint64 id = *((uint64*)UniqueNetId.UniqueNetId->GetBytes());
int Picture = 0;
// get the Avatar ID using the player ID
Picture = SteamFriends()->GetMediumFriendAvatar(id);
Hopefully that will save someone the few hours it took me to figure it out.
The #include didnt show up it was
#include
steam / steam api .h but with chevrons
If anyone is trying to create a plugin or Subsystem that does this and is having include errors, you will need to add your plugin/subsystem to the list of included modules for your project. This can be done by adding the name in the .uproject file and the “yourprojectname”.build.cs file.
Marin was right, this tutorial no longer works. Any help with making a new one? Even better if it’s just creating general function achievement nodes instead of tying them to the character.
Hi Orfeas,
Is there a way to do this just using blueprints? I am attempting to unlock achievements using the Cache Achievements and Write Achievements Progress nodes, but my Cache Achievements node always fails.
The Steam overlay works correctly. Any idea what would be going on?
Thanks,
Jackson
I managed to get this to work in Unreal Engine 5.3.1. The includes are slightly different and you also need module imports. Here they are.
#include “OnlineSubsystem.h”
#include “OnlineStats.h”
#include “Interfaces/OnlineIdentityInterface.h”
#include “OnlineSubsystemUtils.h”
#include “Interfaces/OnlineAchievementsInterface.h”
modules:
PrivateDependencyModuleNames.AddRange(new string[] { “OnlineSubsystem”, “OnlineSubsystemUtils” });
DynamicallyLoadedModuleNames.Add(“OnlineSubsystemSteam”);