online_leaderboard_tutorial_2

en Tutoriales, UE4

Como crear una tabla de clasificación online (Parte 2)

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++.

project solution

Project solution location

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
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#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.

1
2
3
//
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" });
//
1
2
3
//
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 unpaquete descargable al final de esta publicación. La función encargado del envío de la puntuación, sendScore deberá parecerse a esto:

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
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 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()

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
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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void ULeaderboardManager::getLeaderboard()
{
	//Top100+ personalHS
	FString data = "funcName=getLeaderboard";
 
	if (!userId.IsEmpty())
		data += "&userId=" + userId;
 
	TSharedRef 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

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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)

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
#include "LeaderboardManager.h"
 
#include "Tutorial.h"
#include "sha256.h"
#include 
#include 
 
//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 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 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();
}

Archivos del Tutorial:

Download

Download Leaderboard Manager

Download

Download sha256

Es hora de integrar este cliente con nuestro proyecto Blueprint.

Cómo crear una tabla de clasificación online (parte 3)


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!


Escribe un comentario

Comentario

diecinueve − 17 =