Today we are going to start looking at some new content related to networking operations with the Unreal Engine.
Specifically we are going to review how to make HTTP GET calls from within Unreal to an external REST API.
The project files for this video & article can be found on our GitHub page.
In order to start using the HTTP module we first need to modify our Build.cs file to include a few new public dependencies. Specifically Http, Json and JsonUtilities.
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public class Network1 : ModuleRules
{
public Network1(ReadOnlyTargetRules Target):base(Target)
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"), Category = "HTTP")
UBoxComponent* OverlapComponent;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"), Category = "HTTP")
UWidgetComponent* ResponseWidgetComponent;
private:
FHttpModule* Http;
};
UCLASS()
class NETWORK1_API AHTTPGETActor : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AHTTPGETActor();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
UFUNCTION()
void OnBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
/*Assign this function to call when the GET request processes sucessfully*/
void OnGetUsersResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful);
void OnGetUserByUsernameResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful);
UFUNCTION()
void SendHTTPGet(FString Username);
private:
void AddUserToWidget(TSharedPtr<FJsonObject> JsonObject);
public:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"), Category = "HTTP")
UBoxComponent* OverlapComponent;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"), Category = "HTTP")
UWidgetComponent* ResponseWidgetComponent;
private:
FHttpModule* Http;
};
UCLASS()
class NETWORK1_API AHTTPGETActor : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AHTTPGETActor();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
UFUNCTION()
void OnBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
/*Assign this function to call when the GET request processes sucessfully*/
void OnGetUsersResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful);
void OnGetUserByUsernameResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful);
UFUNCTION()
void SendHTTPGet(FString Username);
private:
void AddUserToWidget(TSharedPtr<FJsonObject> JsonObject);
public:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"), Category = "HTTP")
UBoxComponent* OverlapComponent;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"), Category = "HTTP")
UWidgetComponent* ResponseWidgetComponent;
private:
FHttpModule* Http;
};
The other property is our reference to the FHttpModule that will be used for all of our network communication with the server. This is available to us by including the “Http.h” header in our Actor.
Let’s step through our functions one by one and see how they all communicate.
First thing is our overlap that is triggered when the character interacts with this Actor as well as the instantiation of our components and the FHttpModule.
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// Sets default values
AHTTPGETActor::AHTTPGETActor()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
// Sets default values
AHTTPGETActor::AHTTPGETActor()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
OverlapComponent = CreateDefaultSubobject<UBoxComponent>(TEXT("Overlap Area"));
SetRootComponent(OverlapComponent);
ResponseWidgetComponent = CreateDefaultSubobject<UWidgetComponent>(TEXT("Response Widget"));
ResponseWidgetComponent->SetupAttachment(OverlapComponent);
Http = &FHttpModule::Get();
}
// Called when the game starts or when spawned
void AHTTPGETActor::BeginPlay()
{
Super::BeginPlay();
OverlapComponent->OnComponentBeginOverlap.AddDynamic(this, &ThisClass::OnBeginOverlap);
}
// Sets default values
AHTTPGETActor::AHTTPGETActor()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
OverlapComponent = CreateDefaultSubobject<UBoxComponent>(TEXT("Overlap Area"));
SetRootComponent(OverlapComponent);
ResponseWidgetComponent = CreateDefaultSubobject<UWidgetComponent>(TEXT("Response Widget"));
ResponseWidgetComponent->SetupAttachment(OverlapComponent);
Http = &FHttpModule::Get();
}
// Called when the game starts or when spawned
void AHTTPGETActor::BeginPlay()
{
Super::BeginPlay();
OverlapComponent->OnComponentBeginOverlap.AddDynamic(this, &ThisClass::OnBeginOverlap);
}
With our objects all setup we can now proceed to looking at the OnBeginOverlap method as that is the first thing our character will interact with.
In the begin overlap we do a few things. We update our user widget ( HTTPResponseWidget ) that a loading operation has started and we try to retrieve the username of the player that interacted with our component.
We then send the players username over to SendHTTPGet() which will try to determine if it’s a valid username or not and make it’s HTTP calls out to an external service.
Here is where we start creating our request structure for the HTTP GET call. This means we have to provide a URL and depending on the username we either use it to retrieve a single record or instead get all users available to us.
We also create callbacks via OnProcessRequestComplete to two separate methods: OnGetUsersResponse and OnGetUserByUsernameResponse.
This allows us to handle the response structure per each GET call separately and offload the processing of those requests to different components if required.
We also include some custom headers that can be used for things like the Content-Type definition but also for more complex security and authentication situations.
The OnGetUsersResponse method will be processing this JSON Payload which includes an array of elements as part of the response.
Lastly in our OnGetUsersResponse and OnGetUserByUsernameResponse methods we can use the reference to the FHttpRequestPtr and FHttpResponsePtr parameters to determine if our calls were successful and came back with a status code of 200
Additionally we can observe how the JSON structure is parsed by first using the FJsonObjectin combination with the TJsonReader
That’s it, you successfully processed a JSON response from an external service using your Unreal code.
Hope this helps you guys make your own network requests.
If you would like to see more examples check out the video as well as the GitHub project for examples of child classes that move the player and particles around.
We are going to continue our C++ Fundamentals lessons by creating a custom HUD using AHUD class, then adding a custom widget to the HUD using UUserWidget and finally rounding things off by adding some UWidgetAnimations to make the whole thing look a bit more pretty.
As usual you can find the starter project on our GitHub page.
Hold up, lets get some background on all this stuff first !
HUD – heads.up.display. this is a way of projecting various bits of information in the form of text, images, animations, etc to inform the player as to what is happening to him, other things, or his environment.
Alternatively a hud can also represent menu screens, credits, etc.
Essentially they are the large canvas on which various components and widgets will be displayed on.
For example:
– hud that show cases all the player attributes like health, mana and stamina
– options menu for configuring graphics properties
– scrolling credits
User Widget – a single purpose component that you can attach to various locations on the hud as well as the view port. this is normally either a single “ui thing” or a logical grouping of “ui things”.
For example:
– a cross hair in a FPS game
– health and mana bars on top of each other
– speed gauge in a car sim
Widget Animation – are user widget components responsible for handling the animation for all the various components added to the user widget.
This includes manipulation of states like transformation, visibility, color and opacity. Which allows designers to create various fun and exciting ways of presenting data and feedback to the player.
Example:
– health bar is actually red liquid that boils away as the player takes damage
– damage indicators that show up when a player hits something and proceed to fall to the ground while fading away
That’s better, now what ?
Well lets take a look at our HUD class and what we can do with it.
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
UCLASS()
class UE4FUNDAMENTALS08_API AInGameHUD : public AHUD
Similar layout to our player or prop classes but you get a few extra functions like DrawHUD() that allows you to control the various aspects of screen components and widgets.
We also include a TSubclassOf<UUserWidget> that allows us to include various custom widget components as part of this HUD.
void AInGameHUD::BeginPlay()
{
Super::BeginPlay();
if (HitComboWidgetClass)
{
HitComboWidget = CreateWidget<UHitComboWidget>(GetWorld(), HitComboWidgetClass);
/** Make sure widget was created */
if (HitComboWidget)
{
/** Add it to the viewport */
HitComboWidget->AddToViewport();
}
}
}
void AInGameHUD::BeginPlay()
{
Super::BeginPlay();
if (HitComboWidgetClass)
{
HitComboWidget = CreateWidget<UHitComboWidget>(GetWorld(), HitComboWidgetClass);
/** Make sure widget was created */
if (HitComboWidget)
{
/** Add it to the viewport */
HitComboWidget->AddToViewport();
}
}
}
The important method here is the one that instantiates the custom widget class and adds it to our view port.
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
void AInGameHUD::UpdateComboCount(int32 Value)
{
if(HitComboWidget)
{
HitComboWidget->UpdateComboCount(Value);
}
}
void AInGameHUD::UpdateComboCount(int32 Value)
{
if (HitComboWidget)
{
HitComboWidget->UpdateComboCount(Value);
}
}
void AInGameHUD::UpdateComboCount(int32 Value)
{
if (HitComboWidget)
{
HitComboWidget->UpdateComboCount(Value);
}
}
This is just an example of how we would call our user widget via our hud.
Great we have a HUD but what else do we need ?!
So let’s examine our user widget to see what makes it tick.
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
UCLASS()
class UE4FUNDAMENTALS08_API UHitComboWidget : public UUserWidget
As we can see in the header the deafult constructor for a UUserWidget is a bit different as it expects an ObjectInitializer to be passed in.
We also don’t have a BeginPlay but rather we expose NativeConstruct which esentially behaves the same way and loads stuff after the constructor is already loaded.
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (BindWidget))
class UTextBlock* TXTCombo;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (BindWidget))
class UTextBlock* TXTCombo;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (BindWidget))
class UTextBlock* TXTCombo;
This bit here allows us to auto bind by name to a component on the user widget. In this case we are looking for a UTextBlock of name TXTCombo.
[ insert warning image ]
NOTE: You will get warnings if these attributes are not present.
void UHitComboWidget::UpdateComboCount(int32 Value)
{
// only update if more than one hit
if (TXTCombo && Value > 1)
{
if (TXTCombo->Visibility == ESlateVisibility::Hidden)
{
TXTCombo->SetVisibility(ESlateVisibility::Visible);
}
TXTCombo->SetText(FText::FromString((FString::FromInt(Value) + "x Combo")));
if (ComboShakeAnimation)
{
PlayAnimation(ComboShakeAnimation, 0.f, 1, EUMGSequencePlayMode::Forward, 1.f);
}
if (ComboFadeAnimation)
{
PlayAnimation(ComboFadeAnimation, 0.f, 1, EUMGSequencePlayMode::Forward, 1.f);
}
}
}
void UHitComboWidget::UpdateComboCount(int32 Value)
{
// only update if more than one hit
if (TXTCombo && Value > 1)
{
if (TXTCombo->Visibility == ESlateVisibility::Hidden)
{
TXTCombo->SetVisibility(ESlateVisibility::Visible);
}
TXTCombo->SetText(FText::FromString((FString::FromInt(Value) + "x Combo")));
if (ComboShakeAnimation)
{
PlayAnimation(ComboShakeAnimation, 0.f, 1, EUMGSequencePlayMode::Forward, 1.f);
}
if (ComboFadeAnimation)
{
PlayAnimation(ComboFadeAnimation, 0.f, 1, EUMGSequencePlayMode::Forward, 1.f);
}
}
}
In this method, we manipulate the state of the TXTCombo viability based on it being reset and set it back to visible.
We also take in the user provided combo count and add it to the text.
Once that is in place we play back the two animations, one to shake the text and the other to fade it away after a few seconds.
void UHitComboWidget::StoreWidgetAnimations()
{
AnimationsMap.Empty();
UProperty* Prop = GetClass()->PropertyLink;
// check each property of this class
while (Prop)
{
// only evaluate object properties, skip rest
if (Prop->GetClass() == UObjectProperty::StaticClass())
{
UObjectProperty* ObjProp = Cast<UObjectProperty>(Prop);
// only get back properties that are of type widget animation
if (ObjProp->PropertyClass == UWidgetAnimation::StaticClass())
{
UObject* Obj = ObjProp->GetObjectPropertyValue_InContainer(this);
// only get back properties that are of type widget animation
UWidgetAnimation* WidgetAnimation = Cast<UWidgetAnimation>(Obj);
// if casting worked update map with new animation
if (WidgetAnimation && WidgetAnimation->MovieScene)
{
FName AnimName = WidgetAnimation->MovieScene->GetFName();
GEngine->AddOnScreenDebugMessage(-1, 4.5f, FColor::Magenta, AnimName.ToString());
AnimationsMap.Add(AnimName, WidgetAnimation);
}
}
}
Prop = Prop->PropertyLinkNext;
}
}
void UHitComboWidget::StoreWidgetAnimations()
{
AnimationsMap.Empty();
UProperty* Prop = GetClass()->PropertyLink;
// check each property of this class
while (Prop)
{
// only evaluate object properties, skip rest
if (Prop->GetClass() == UObjectProperty::StaticClass())
{
UObjectProperty* ObjProp = Cast<UObjectProperty>(Prop);
// only get back properties that are of type widget animation
if (ObjProp->PropertyClass == UWidgetAnimation::StaticClass())
{
UObject* Obj = ObjProp->GetObjectPropertyValue_InContainer(this);
// only get back properties that are of type widget animation
UWidgetAnimation* WidgetAnimation = Cast<UWidgetAnimation>(Obj);
// if casting worked update map with new animation
if (WidgetAnimation && WidgetAnimation->MovieScene)
{
FName AnimName = WidgetAnimation->MovieScene->GetFName();
GEngine->AddOnScreenDebugMessage(-1, 4.5f, FColor::Magenta, AnimName.ToString());
AnimationsMap.Add(AnimName, WidgetAnimation);
}
}
}
Prop = Prop->PropertyLinkNext;
}
}
This is the big method that allows us to process each property of the current user widget and evaluate them for widget animations.
When a widget animation is found we update our private map with those details.