Ahora que tenemos la parte del servidor lista para funcionar necesitamos implementar el código del lado del cliente. Esta parte se debe encargar de gestionar los resultados de las peticiones http y también de realizar las llamadas al controlador del juego para que muestre por pantalla estos datos.
Parte 1: Archivos del servidor
Parte 2: Archivos del cliente
Parte 3: Integración con el proyecto UE4
C++
Tenemos que abrir las solución de Visual Studio de nuestro proyecto UE4 y añadir una nueva clase LeaderboardManager, o podemos utilizar el mismo método visto en el tutorial sobre cómo añadir contenedores c++.
Esta clase tiene que gestionar los datos recibidos desde el servidor sobre la tabla de clasificación, tanto la tabla del top 100 como los datos del jugador que hace la petición, y, por supuesto, debe encargarse de la comunicación con nuestro servidor también.
Por ello vamos a preparar un pareja de callbacks éxito/error para cada una de las dos peticiones. La UI deberá mostrar el widget relacionado con cada una de las situaciones usando estas llamadas.
- 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(); };
Tenemos que añadir la lista de dependencias en el archivo ProjectName.Build.cs antes de poder continuar.En este caso tenemos que añadir las dependencias para las herramientas de http y json.
// PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" }); //
// PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" , "Http", "Json", "JsonUtilities" }); //
Ahora ya podemos implementar la comunicación con el servidor. Debemos tener en cuenta que hay que añadir el código relacionado con la seguridad básica implementada en los anteriores archivos php, tenemos que generar el hash para la misma estructura de datos usando las mismas palabras secretas que en los php. Si necesitas una implementación en C++ de sha256 te incluyo los ficheros de la clase en un paquete descargable al final de esta publicación. La función encargado del envío de la puntuación, sendScore deberá parecerse a esto:
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(); }
El callback para la anterior petición de sendScore OnResponseSendScore debe comprobar si la petición se ha completado éxitosamente, lo que indicará si las puntuaciones se han insertado correctamente. Para ello tenemos que comprobar el valor devuelto por la función php, si el valor es igual a cero podemos realizar la llamada a la función ShowSuccessSendScore() y que la UI se actualize. En caso de que fallara, extraeríamos el mensaje de error y lo guardaríamos en la variable de nuestro leaderboardManager correspondiente para después realizar la llamada a ShowFailedSend()
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(); } }
La función para recuperar la lista de puntuaciones getLeaderboard tiene una parametrización php un poco más simple, solo necesita el userId, y el nombre de la función. Nos devolverá la lista de las mejores 100 puntuaciones y la posición del jugador en el caso de que se encuentre dentro de esta lista. Un valor cero en su posición indicaría que no está en ese Top 100.
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(); }
En el callback de esta petición vamos a extraer la información de la lista y rellenar los miembros de la clase según corresponda, el juego podrá acceder a esta información de manera total o parcial una vez se realice la llamada a ShowSuccessList(). También debemos gestionar la situación de error de la misma manera que hemos hecho en 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(); } }
Finalmente el archivo .cpp queda tal que así: (No te olvides de poner la URL de tu archivo gameFunctions.php en el constructor de 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
Es hora de integrar este cliente con nuestro proyecto Blueprint. Cómo crear una tabla de clasificación online (parte 3)
2021/10/05 – Updated to UE4 4.27
Te puede interesar:
Ayudanos con este blog!
El último año he estado dedicando cada vez más tiempo a la creación de tutoriales, en su mayoria sobre desarrollo de videojuegos. Si crees que estos posts te han ayudado de alguna manera o incluso inspirado, por favor considera ayudarnos a mantener este blog con alguna de estas opciones. Gracias por hacerlo posible!