Now that we have the server side ready to go we need to implement the code for the client side. This part needs to manage the results of the http request as well as make the calls to the game controller with this data.
Part 1: Server files
Part 2: Client files
Part 3: UE4 game integration
C++
We need to open the visual studio solution of our game project and add a new class LeaderboardManager, or we can use the same method explained in the previous tutorial about how to add c++ containers.
This class must manage the data retrieved from the server about the leaderboard, the top 100 list and the personal data and send the user high score, and must take care of the communication with our server.
So we make a success/error callback for each one of our two requests. The UI must show the related widget for each situation using this calls.
- SendScore
- ShowSuccessSendScore
- ShowFailedSend
- GetLeaderboard
- ShowSuccessList
- ShowFailedList
#pragma once #include "Object.h" #include "Runtime/Online/HTTP/Public/Http.h" #include "LeaderboardManager.generated.h" UCLASS(Blueprintable, BlueprintType) class PINGVIN_API ULeaderboardManager : public UObject { GENERATED_BODY() private: FHttpModule* Http; FString serverPHPpath; //http callbacks void OnResponseSendScore(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); void OnResponseGetLeaderboard(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); public: //personal userId UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Variable") FString userId; //Leaderboard data Top 100 UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Variable") TArray t100_names; UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Variable") TArray t100_score; //Leaderboard personal data UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Variable") int32 personal_pos; UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Variable") FString personal_name; UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Variable") int32 personal_score; // returned error message UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Variable") FString errorMessage; // client functions UFUNCTION(BlueprintCallable, Category = "LBM_funtions") void setUserId(const FString& newuserId); UFUNCTION(BlueprintCallable, Category = "LBM_funtions") void sendScore(const FString& userName, const int32& score); UFUNCTION(BlueprintCallable, Category = "LBM_funtions") void getLeaderboard(); //Callbacks UFUNCTION(BlueprintImplementableEvent, BlueprintCallable, Category = "Function") void ShowSuccessSendScore(); UFUNCTION(BlueprintImplementableEvent, BlueprintCallable, Category = "Function") void ShowFailedSend(); UFUNCTION(BlueprintImplementableEvent, BlueprintCallable, Category = "Function") void ShowSuccessList(); UFUNCTION(BlueprintImplementableEvent, BlueprintCallable, Category = "Function") void ShowFailedList(); // Sets default values for this actor's properties ULeaderboardManager(); };
We need to add the dependencies list in the ProjectName.Build.cs file before we can continue. In this case for the http and json tools.
// PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" }); //
// PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" , "Http", "Json", "JsonUtilities" }); //
Now we can implement the communication with the server. Note that we must add the code related to the basic security implemented in the php files, we need to generate the hash for the same data structure with the same words of the phpfiles. If you need a sha256 implementation I will include the files in a downloadable package at the end of this post. The sendScore will look like this:
void ULeaderboardManager::sendScore(const FString& userName, const int32& score) { FString data = "funcName=sendScore"; std::string output1; std::string userName_str(TCHAR_TO_UTF8(*userName)); if (!userId.IsEmpty()) { data += "&userId=" + userId; std::string userid_str(TCHAR_TO_UTF8(*userId)); output1 = sha256(userid_str + userName_str + "secret1" + to_string(score) + "secret2"); } else { //is a new user output1 = sha256(userName_str + "secret1" + to_string(score) +"secret2"); } data += "&userName=" + userName + "&score=" + FString::FromInt(score) + "&userData=" + FString(output1.c_str()); TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request = Http->CreateRequest(); Request->OnProcessRequestComplete().BindUObject(this, &ULeaderboardManager::OnResponseSendScore); //This is the url on which to process the request Request->SetURL(serverPHPpath); Request->SetVerb("POST"); Request->SetHeader(TEXT("User-Agent"), "X-UnrealEngine-Agent"); Request->SetHeader("Content-Type", "application/x-www-form-urlencoded"); Request->SetContentAsString(data); Request->ProcessRequest(); }
The callback for the sendScore http request OnResponseSendScore must check if the request was successful and the score data insertion was OK. To do this we need to check the code return of the our php function, if the value is equal to zero we can use a call to our function ShowSuccessSendScore() to report the success. It it fails, we’ll use the ShowFailedSend(), filling the errorMessage member of our leaderboard manager with the retrieved error message, so the game can access it later.
void ULeaderboardManager::OnResponseSendScore(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) { if (bWasSuccessful) { //Create a pointer to hold the json serialized data TSharedPtr JsonObject; FString contenico = Response->GetContentAsString(); //Create a reader pointer to read the json data TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Response->GetContentAsString()); //Deserialize the json data given Reader and the actual object to deserialize if (FJsonSerializer::Deserialize(Reader, JsonObject)) { //Get the value of the json object by field name int32 returnCode = JsonObject->GetIntegerField("returnCode"); if (returnCode == 0) { FString receivedUserId = JsonObject->GetStringField("returnData"); if (userId.IsEmpty()) userId = receivedUserId; ShowSuccessSendScore(); } else { //show error errorMessage = JsonObject->GetStringField("returnData"); ShowFailedSend(); } } else { errorMessage = "Error on data deserialization"; ShowFailedSend(); } } else { errorMessage = "Error on http request sendscore"; ShowFailedSend(); } }
The getLeaderboard function has a bit more simple call, it only needs the userId, and the php function name, to retrieve the top 100 list and the current user position if he is in the top. A value of zero in their position indicates that he is not in the top.
void ULeaderboardManager::getLeaderboard() { //Top100+ personalHS FString data = "funcName=getLeaderboard"; if (!userId.IsEmpty()) data += "&userId=" + userId; TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request = Http->CreateRequest(); Request->OnProcessRequestComplete().BindUObject(this, &ULeaderboardManager::OnResponseGetLeaderboard); //This is the url on which to process the request Request->SetURL(serverPHPpath); Request->SetVerb("POST"); Request->SetHeader(TEXT("User-Agent"), "X-UnrealEngine-Agent"); Request->SetHeader("Content-Type", "application/x-www-form-urlencoded"); Request->SetContentAsString(data); Request->ProcessRequest(); }
In the request callback we’ll extract the list info and fill the class members to let the game to access to it on the success function call ShowSuccessList(). We need to manage the error in this function too, with the same method as in OnResponseSendScore
void ULeaderboardManager::OnResponseGetLeaderboard(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) { if (bWasSuccessful) { //Create a pointer to hold the json serialized data TSharedPtr JsonObject; //Create a reader pointer to read the json data TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Response->GetContentAsString()); //Deserialize the json data given Reader and the actual object to deserialize if (FJsonSerializer::Deserialize(Reader, JsonObject)) { int32 returnCode = JsonObject->GetIntegerField("returnCode"); if (returnCode == 0) { //Get the value of the json object by field name TArray<TSharedPtr> arr = JsonObject->GetArrayField("returnData"); t100_names.Empty(); t100_score.Empty(); for (int i = 0; i < arr.Num() - 1; ++i) { auto elemento = arr[i]->AsArray(); t100_names.Add(elemento[1]->AsString()); t100_score.Add(elemento[2]->AsNumber()); } //personal result auto elemento = arr[arr.Num()-1]->AsArray(); if (elemento[0]->AsNumber() != 0) { personal_pos = elemento[0]->AsNumber(); personal_name = elemento[1]->AsString(); personal_score = elemento[2]->AsNumber(); } else { personal_name = ""; personal_score = 0; } //Output it to the engine ShowSuccessList(); } else { //muestra error errorMessage = JsonObject->GetStringField("returnData"); ShowFailedSend(); } } else { errorMessage = "Error on data deserialization 2"; ShowFailedSend(); } } else { errorMessage = "Error on http request leaderboard"; ShowFailedList(); } }
Finally the .cpp file will look like this: (Don’t forget to put the URL to our gameFunctions.php file in the constructor of the LeaderboardManager)
#include "LeaderboardManager.h" #include "Tutorial.h" #include "sha256.h" #include <string> #include <sstream> //NDK fix template std::string to_string(T value) { std::ostringstream os; os << value; return os.str(); } ULeaderboardManager::ULeaderboardManager() { //When the object is constructed, Get the HTTP module Http = &FHttpModule::Get(); serverPHPpath = "http://*****************/gameFunctions.php"; // replace with the gameFunctions URL } void ULeaderboardManager::OnResponseSendScore(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) { if (bWasSuccessful) { //Create a pointer to hold the json serialized data TSharedPtr JsonObject; FString contenico = Response->GetContentAsString(); //Create a reader pointer to read the json data TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Response->GetContentAsString()); //Deserialize the json data given Reader and the actual object to deserialize if (FJsonSerializer::Deserialize(Reader, JsonObject)) { //Get the value of the json object by field name int32 returnCode = JsonObject->GetIntegerField("returnCode"); if (returnCode == 0) { FString receivedUserId = JsonObject->GetStringField("returnData"); if (userId.IsEmpty()) userId = receivedUserId; ShowSuccessSendScore(); } else { //show error errorMessage = JsonObject->GetStringField("returnData"); ShowFailedSend(); } } else { errorMessage = "Error on data deserialization"; ShowFailedSend(); } } else { errorMessage = "Error on http request sendscore"; ShowFailedSend(); } } void ULeaderboardManager::OnResponseGetLeaderboard(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) { if (bWasSuccessful) { //Create a pointer to hold the json serialized data TSharedPtr JsonObject; //Create a reader pointer to read the json data TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Response->GetContentAsString()); //Deserialize the json data given Reader and the actual object to deserialize if (FJsonSerializer::Deserialize(Reader, JsonObject)) { int32 returnCode = JsonObject->GetIntegerField("returnCode"); if (returnCode == 0) { //Get the value of the json object by field name TArray<TSharedPtr> arr = JsonObject->GetArrayField("returnData"); t100_names.Empty(); t100_score.Empty(); for (int i = 0; i < arr.Num() - 1; ++i) { auto elemento = arr[i]->AsArray(); t100_names.Add(elemento[1]->AsString()); t100_score.Add(elemento[2]->AsNumber()); } //personal result auto elemento = arr[arr.Num()-1]->AsArray(); if (elemento[0]->AsNumber() != 0) { personal_pos = elemento[0]->AsNumber(); personal_name = elemento[1]->AsString(); personal_score = elemento[2]->AsNumber(); } else { personal_name = ""; personal_score = 0; } //Output it to the engine ShowSuccessList(); } else { //muestra error errorMessage = JsonObject->GetStringField("returnData"); ShowFailedSend(); } } else { errorMessage = "Error on data deserialization 2"; ShowFailedSend(); } } else { errorMessage = "Error on http request leaderboard"; ShowFailedList(); } } void ULeaderboardManager::setUserId(const FString& newuserId) { userId = newuserId; } void ULeaderboardManager::sendScore(const FString& userName, const int32& score) { FString data = "funcName=sendScore"; std::string output1; std::string userName_str(TCHAR_TO_UTF8(*userName)); if (!userId.IsEmpty()) { data += "&userId=" + userId; std::string userid_str(TCHAR_TO_UTF8(*userId)); output1 = sha256(userid_str + userName_str + "secret1" + to_string(score) + "secret2"); } else { // is a new user output1 = sha256(userName_str + "secret1" + to_string(score) +"secret2"); } data += "&userName=" + userName + "&score=" + FString::FromInt(score) + "&userData=" + FString(output1.c_str()); TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request = Http->CreateRequest(); Request->OnProcessRequestComplete().BindUObject(this, &ULeaderboardManager::OnResponseSendScore); //This is the url on which to process the request Request->SetURL(serverPHPpath); Request->SetVerb("POST"); Request->SetHeader(TEXT("User-Agent"), "X-UnrealEngine-Agent"); Request->SetHeader("Content-Type", "application/x-www-form-urlencoded"); Request->SetContentAsString(data); Request->ProcessRequest(); } void ULeaderboardManager::getLeaderboard() { //Top100+ personalHS FString data = "funcName=getLeaderboard"; if (!userId.IsEmpty()) data += "&userId=" + userId; TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request = Http->CreateRequest(); Request->OnProcessRequestComplete().BindUObject(this, &ULeaderboardManager::OnResponseGetLeaderboard); //This is the url on which to process the request Request->SetURL(serverPHPpath); Request->SetVerb("POST"); Request->SetHeader(TEXT("User-Agent"), "X-UnrealEngine-Agent"); Request->SetHeader("Content-Type", "application/x-www-form-urlencoded"); Request->SetContentAsString(data); Request->ProcessRequest(); }
Tutorial files
It’s time to integrate this client with our Blueprint game project. How to make an online leaderboard (part 3)
2021/10/05 – Updated to UE4 4.27
You may also like:
Support this blog!
For the past year I've been dedicating more of my time to the creation of tutorials, mainly about game development. If you think these posts have either helped or inspired you, please consider supporting this blog. Thank you so much for your contribution!
Hi, I’m a bit stuck.
I can’t find Tutorial.h and it generates a lot of errors while building.
Tutorial.h must be replaced with your project include, search a yourprojectname.h file