In the previous chapter we’ve programmed a character controller in our Java Micronaut application which you can find here. This part is split into two parts, second being available here.
In this chapter, we will look at integrating this with Unreal Engine. One interesting thing I’ve done is upgrade to Unreal Engine 5.0 to test it and you will find that the steps are actually the same as UE4 so feel free to use whichever version for this.
This will be split into two parts, and the next part can be found here.
Note that the server code will be available on this github link.
In this post I will reference all the C++ code that we will write in the UE project and in the next blog post I will show how we can connect it all to our Blueprints for use.
Here’s a video detailing high level overview of what we’re building in both part 1 and part 2
What we will implement
There are several functions that have been implemented that we will reference here, they are:
- login
- register
- get characters
- create character
Note that we’re going to do this with C++.
Why? I checked out the Blueprint methods and honestly, they’re not really worth it for this. They appear a little bloated and more complicated than necessary. C++ is also relatively complex, but I feel you will get more out of this as it will teach a bit more transferrable skills.
Creating your c++ class
First thing you want to do is open up your project and create a new c++ class.
I’m going to connect this C++ class to a user widget – i.e. the UI interface that I will create. So when choosing parent class, select UserWidget after selecting ‘All Classes’:
For the name, I selected ‘MyUserAccountWidget’. If you call it the same, you will be able to copy and paste the code as in this guide.
This will create two items for you:
- MyUserAccountWidget.h
- MyUserAccountWidget.cpp
You may now close your running unreal engine as we will open it up in your Visual Studio.
Open UE project in Visual Studio
First of all, just make sure your clean build works as expected, you may need to install some Visual Studio dependencies to get this running, there’s multiple posts around this. Have a look at this post for reference.
Now, you will want to find your project ‘.build’ file. Mine is called ‘Mmo_project.Build.cs’
You will want to add several public dependencies in here which we will use, which are JSON, JSON Utilities and Http. This is what you need to add/modify:
PublicDependencyModuleNames.AddRange(new string[] {
"Core",
"CoreUObject",
"Engine",
"InputCore",
"Json",
"JsonUtilities",
"Http"
});
Here’s a screenshot of the entire file and expected project structure for reference.
Once this is done, we can get cracking with the new user account widget c++ class.
MyUserAccountWidget header file
For the header file MyUserAccountWidget.h
we want the following content:
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Runtime/Online/HTTP/Public/HttpModule.h"
#include "Blueprint/UserWidget.h"
#include "MyUserAccountWidget.generated.h"
/**
* Account client class, handle:
* Login
* Register
* Get account characters
* Create new character
*/
USTRUCT(BlueprintType)
struct FLoginResponse
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AccountClient")
FString Username;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AccountClient")
TArray<FString> Roles;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AccountClient")
FString AccessToken;
};
USTRUCT(BlueprintType)
struct FRegisterResponse
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AccountClient")
FString Username;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AccountClient")
FString Email;
};
USTRUCT(BlueprintType)
struct FAccountCharacterBase
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AccountClient")
FString Name;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AccountClient")
int32 Xp;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AccountClient")
FString AccountName;
};
USTRUCT(BlueprintType)
struct FAccountCharacterResponse
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AccountClient")
TArray<FAccountCharacterBase> CharacterList;
};
UCLASS()
class MMO_PROJECT_API UMyUserAccountWidget : public UUserWidget
{
GENERATED_BODY()
public:
// Handle Login:
UFUNCTION(BlueprintCallable, Category = "AccountClient")
void HandleLogin(FString username, FString password);
void OnLoginResponseReceived(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful);
UFUNCTION(BlueprintImplementableEvent, Category = "AccountClient")
void NotifyLoginSuccess(const FLoginResponse& fLoginResponse);
UFUNCTION(BlueprintImplementableEvent, Category = "AccountClient")
void NotifyLoginFail();
// Handle Register:
UFUNCTION(BlueprintCallable, Category = "AccountClient")
void HandleRegister(FString username, FString password, FString email);
void OnRegisterResponseReceived(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful);
UFUNCTION(BlueprintImplementableEvent, Category = "AccountClient")
void NotifyRegisterSuccess(const FRegisterResponse& fRegisterResponse);
UFUNCTION(BlueprintImplementableEvent, Category = "AccountClient")
void NotifyRegisterFail();
// Get user characters
UFUNCTION(BlueprintCallable, Category = "AccountClient")
void GetAccountCharacters(FString AccessToken);
void OnGetCharacterResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful);
UFUNCTION(BlueprintImplementableEvent, Category = "AccountClient")
void NotifyGetCharacterSuccess(const FAccountCharacterResponse& fAccountCharacterResponse);
UFUNCTION(BlueprintImplementableEvent, Category = "AccountClient")
void NotifyGetCharacterFail();
// Create user characters
UFUNCTION(BlueprintCallable, Category = "AccountClient")
void CreateNewCharacter(FString AccessToken, FString name);
void OnCreateNewCharacterResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful);
UFUNCTION(BlueprintImplementableEvent, Category = "AccountClient")
void NotifyCreateNewCharacterSuccess(const FAccountCharacterBase& fAccountCharacterBase);
UFUNCTION(BlueprintImplementableEvent, Category = "AccountClient")
void NotifyCreateNewCharacterFail();
private:
FHttpModule* Http;
FString LoginApiPath;
FString RegisterApiPath;
FString GetAccountCharactersApiPath;
FString CreateNewCharacterApiPath;
void SetHeaders(TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request, FString AccessToken);
};
That is a lot of content in there and it can be refactored, but I wanted to make it as easy to bring to your project as possible.
In general, there are 4 functions for each api request.
- handle <function> call
- on response received
- Unreal function – notify success
- Unreal function notify failure
So the handle <function> call
will do as it says. It will take some input params, such as username
and password
and make the API call. This API call will happen async so you hook it to an event, i.e. the response received event.
The on response received
event, you decide whether the function was successful or not. If not, you simply call the notify failure
function, otherwise you populate the desired response object and call notify success
.
Both notify success
and notify failure
are blueprint implementable events. That is to say, we only define them in our header file and their logic will be implemented in the blueprint class.
MyUserAccountWidget cpp class
With this in place let’s import the code for MyUserAccountWidget.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "MyUserAccountWidget.h"
#include "Runtime/Online/HTTP/Public/Http.h"
void UMyUserAccountWidget::HandleLogin(FString username, FString password)
{
LoginApiPath = "http://localhost:8081/login";
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request = FHttpModule::Get().CreateRequest();
Request->OnProcessRequestComplete().BindUObject(this, &UMyUserAccountWidget::OnLoginResponseReceived);
TSharedPtr<FJsonObject> params = MakeShareable(new FJsonObject);
params->SetStringField(TEXT("username"), username);
params->SetStringField(TEXT("password"), password);
FString paramsString;
TSharedRef<TJsonWriter<TCHAR>> JsonWriter = TJsonWriterFactory<>::Create(¶msString);
FJsonSerializer::Serialize(params.ToSharedRef(), JsonWriter);
Request->SetURL(LoginApiPath);
Request->SetVerb("POST");
Request->SetHeader(TEXT("User-Agent"), "X-UnrealEngine-Agent");
Request->SetHeader("Content-Type", TEXT("application/json"));
Request->SetHeader("Accept", TEXT("application/json"));
Request->SetContentAsString(paramsString);
Request->ProcessRequest();
}
void UMyUserAccountWidget::OnLoginResponseReceived(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
bool success = false;
if (bWasSuccessful)
{
TSharedPtr<FJsonObject> JsonObject;
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Response->GetContentAsString());
if (FJsonSerializer::Deserialize(Reader, JsonObject))
{
if (JsonObject->HasField("access_token"))
{
FLoginResponse loginResponse;
loginResponse.Username = JsonObject->GetStringField("username");
JsonObject->TryGetStringArrayField("roles", loginResponse.Roles);
loginResponse.AccessToken = JsonObject->GetStringField("access_token");
if (!loginResponse.AccessToken.Equals(""))
{
success = true;
NotifyLoginSuccess(loginResponse);
}
}
}
}
if (success == false)
{
NotifyLoginFail();
}
}
void UMyUserAccountWidget::HandleRegister(FString username, FString password, FString email)
{
RegisterApiPath = "http://localhost:8081/register";
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request = FHttpModule::Get().CreateRequest();
Request->OnProcessRequestComplete().BindUObject(this, &UMyUserAccountWidget::OnRegisterResponseReceived);
TSharedPtr<FJsonObject> params = MakeShareable(new FJsonObject);
params->SetStringField(TEXT("username"), username);
params->SetStringField(TEXT("password"), password);
params->SetStringField(TEXT("email"), email);
FString paramsString;
TSharedRef<TJsonWriter<TCHAR>> JsonWriter = TJsonWriterFactory<>::Create(¶msString);
FJsonSerializer::Serialize(params.ToSharedRef(), JsonWriter);
Request->SetURL(RegisterApiPath);
Request->SetVerb("POST");
Request->SetHeader(TEXT("User-Agent"), "X-UnrealEngine-Agent");
Request->SetHeader("Content-Type", TEXT("application/json"));
Request->SetHeader("Accept", TEXT("application/json"));
Request->SetContentAsString(paramsString);
Request->ProcessRequest();
}
void UMyUserAccountWidget::OnRegisterResponseReceived(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
bool success = false;
if (bWasSuccessful)
{
TSharedPtr<FJsonObject> JsonObject;
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Response->GetContentAsString());
if (FJsonSerializer::Deserialize(Reader, JsonObject))
{
if (JsonObject->HasField("username"))
{
FRegisterResponse registerResponse;
registerResponse.Username = JsonObject->GetStringField("username");
registerResponse.Email = JsonObject->GetStringField("email");
if (!registerResponse.Username.Equals(""))
{
success = true;
NotifyRegisterSuccess(registerResponse);
}
}
}
}
if (success == false)
{
NotifyRegisterFail();
}
}
void UMyUserAccountWidget::GetAccountCharacters(FString AccessToken)
{
GetAccountCharactersApiPath = "http://localhost:8081/player/account-characters";
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request = FHttpModule::Get().CreateRequest();
Request->OnProcessRequestComplete().BindUObject(this, &UMyUserAccountWidget::OnGetCharacterResponse);
Request->SetURL(GetAccountCharactersApiPath);
Request->SetVerb("GET");
Request->SetHeader(TEXT("User-Agent"), "X-UnrealEngine-Agent");
Request->SetHeader("Content-Type", TEXT("application/json"));
Request->SetHeader("Accept", TEXT("application/json"));
FString TokenString = FString(TEXT("Bearer ") + AccessToken);
Request->SetHeader("Authorization", TokenString);
Request->ProcessRequest();
}
void UMyUserAccountWidget::OnGetCharacterResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
bool success = false;
if (bWasSuccessful)
{
TSharedPtr<FJsonObject> JsonObject;
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Response->GetContentAsString());
if (FJsonSerializer::Deserialize(Reader, JsonObject))
{
if (JsonObject->HasField("accountCharacters"))
{
TArray<TSharedPtr<FJsonValue>> Rows = JsonObject->GetArrayField("accountCharacters");
FAccountCharacterResponse Response;
success = true;
for (int i = 0; i < Rows.Num(); i++)
{
FAccountCharacterBase BaseCharacter;
TSharedPtr<FJsonObject> tempRow = Rows[i]->AsObject();
BaseCharacter.AccountName = tempRow->GetStringField("accountName");
BaseCharacter.Name = tempRow->GetStringField("name");
BaseCharacter.Xp = tempRow->GetNumberField("xp");
Response.CharacterList.Add(BaseCharacter);
}
NotifyGetCharacterSuccess(Response);
}
}
}
if (success == false)
{
NotifyGetCharacterFail();
}
}
void UMyUserAccountWidget::CreateNewCharacter(FString AccessToken, FString name)
{
CreateNewCharacterApiPath = "http://localhost:8081/player/create-character";
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request = FHttpModule::Get().CreateRequest();
Request->OnProcessRequestComplete().BindUObject(this, &UMyUserAccountWidget::OnCreateNewCharacterResponse);
TSharedPtr<FJsonObject> params = MakeShareable(new FJsonObject);
params->SetStringField(TEXT("name"), name);
FString paramsString;
TSharedRef<TJsonWriter<TCHAR>> JsonWriter = TJsonWriterFactory<>::Create(¶msString);
FJsonSerializer::Serialize(params.ToSharedRef(), JsonWriter);
Request->SetURL(CreateNewCharacterApiPath);
Request->SetVerb("POST");
SetHeaders(Request, AccessToken);
Request->SetContentAsString(paramsString);
Request->ProcessRequest();
}
void UMyUserAccountWidget::OnCreateNewCharacterResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
bool success = false;
if (bWasSuccessful)
{
TSharedPtr<FJsonObject> JsonObject;
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Response->GetContentAsString());
if (FJsonSerializer::Deserialize(Reader, JsonObject))
{
if (JsonObject->HasField("name"))
{
FAccountCharacterBase CharacterBase;
CharacterBase.AccountName = JsonObject->GetStringField("accountName");
CharacterBase.Name = JsonObject->GetStringField("name");
CharacterBase.Xp = JsonObject->GetNumberField("xp");
if (!CharacterBase.AccountName.Equals(""))
{
NotifyCreateNewCharacterSuccess(CharacterBase);
}
}
}
}
if (success == false)
{
NotifyCreateNewCharacterFail();
}
}
void UMyUserAccountWidget::SetHeaders(TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request, FString AccessToken)
{
Request->SetHeader(TEXT("User-Agent"), "X-UnrealEngine-Agent");
Request->SetHeader("Content-Type", TEXT("application/json"));
Request->SetHeader("Accept", TEXT("application/json"));
if (AccessToken != "")
{
FString TokenString = FString(TEXT("Bearer ") + AccessToken);
Request->SetHeader("Authorization", TokenString);
}
}
Compile and run
Now with the code available, compile and run!
This is not yet linked to any functionality in your UE project, we will cover this in the next blog post.
But if the project compiles and runs, that means you’re in a good place to start your blueprint integration.
Pingback: 9. Connect Unreal Engine to mmo web server – part 2