<style>.lazy{display:none}</style> Skip to main content
Category

Development

Unreal C++ – Puzzle Mechanics – Pressure Plates and Doors by moving Static Meshes & using Interfaces

By Props, Puzzle, Tutorial, Unreal No Comments

Hey guys,

This new set of tutorials is meant to tackle re-creating puzzle mechanics from our favorite games, starting with pressure plates and doors.

The project files for this video & article can be found on our GitHub page.

To get started we need to create four elements that will comprise our puzzle scene:

  • First we need to extend a Static Mesh Component so we can make it move around.
  • We need a pressure plate object that will handle the triggering of other elements in the level.
  • We need elements in the level that will be affected by the trigger ie) doors / windows
  • And lastly we need an interface that will allow for ease of communication between the last two elements.

So let’s start by creating a custom Static Mesh Component. Below is the header definition.

UENUM(BlueprintType)
enum class ERotationAxis : uint8
{
  Yaw, Pitch, Roll
};

UENUM(BlueprintType)
enum class ELocationAxis : uint8
{
  X, Y, Z
};

UENUM(BlueprintType)
enum class EMovementType : uint8
{
  Location, Rotation
};

/**
 * 
 */
UCLASS()
class PUZZLE1_API UMovableStaticMeshComponent : public UStaticMeshComponent
{
  GENERATED_BODY()

public:

  UMovableStaticMeshComponent();

  virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
  virtual void BeginPlay() override;

  UFUNCTION(BlueprintCallable)
  void Move(bool bTriggered);

  UFUNCTION()
  void OnMove();

  UFUNCTION()
  void OnMoveFinished();

  bool IsMoving() const { return bIsMoving; }
  bool IsTriggered() const { return bIsTriggered; }

public:
  UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Movement")
  UCurveFloat* MoveCurve;

  UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Movement")
  EMovementType MovementType;

  UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Movement", meta=(EditCondition="MovementType == EMovementType::Rotation"))
  ERotationAxis RotateAxis;

  UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Movement", meta=(EditCondition="MovementType == EMovementType::Location"))
  ELocationAxis LocationAxis;

  UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Movement")
  bool bIsReversed;

private:
  void UpdateRotation(float CurveValue);
  void UpdateLocation(float CurveValue);

private:
  bool bIsMoving;
  FTimeline MoveTimeline;

  bool bIsTriggered;
  float PreviousTimelineValue;
};

Then we can define the inner workings of our component

UMovableStaticMeshComponent::UMovableStaticMeshComponent()
{
  PrimaryComponentTick.bCanEverTick = true;
}

void UMovableStaticMeshComponent::BeginPlay()
{
  Super::BeginPlay();

  if(MoveCurve)
  {
    FOnTimelineFloat TimelineCallback;
    FOnTimelineEventStatic TimelineFinishCallback;

    TimelineCallback.BindUFunction(this, FName("OnMove"));
    TimelineFinishCallback.BindUFunction(this, FName("OnMoveFinished"));

    MoveTimeline.AddInterpFloat(MoveCurve, TimelineCallback);
    MoveTimeline.SetTimelineFinishedFunc(TimelineFinishCallback);
  }
}

void UMovableStaticMeshComponent::TickComponent(float DeltaTime, ELevelTick Tick, FActorComponentTickFunction* ThisTickFunction)
{
  Super::TickComponent(DeltaTime, Tick, ThisTickFunction);

  if(bIsMoving)
  {
    MoveTimeline.TickTimeline(DeltaTime);
  }
}

void UMovableStaticMeshComponent::Move(bool bTriggered)
{
  bIsTriggered = bTriggered;

  if(bTriggered)
  {
    if(MoveTimeline.IsReversing())
    {
      MoveTimeline.Play();
    }
    else
    {
      MoveTimeline.PlayFromStart();	
    }
  }
  else
  {
    MoveTimeline.Reverse();
  }

  bIsMoving = true;
}

void UMovableStaticMeshComponent::OnMove()
{
  const float PlaybackPosition = MoveTimeline.GetPlaybackPosition();
  float CurveValue = MoveCurve->GetFloatValue(PlaybackPosition);

  if(bIsReversed)
  {
    CurveValue = -CurveValue;
  }

  const float AdjustedValue = CurveValue - PreviousTimelineValue;

  if(MovementType == EMovementType::Location)
  {
    UpdateLocation(AdjustedValue);
  }
  else if(MovementType == EMovementType::Rotation)
  {
    UpdateRotation(AdjustedValue);
  }

  PreviousTimelineValue = CurveValue;
}

void UMovableStaticMeshComponent::OnMoveFinished()
{
  bIsMoving = false;
}

void UMovableStaticMeshComponent::UpdateRotation(float CurveValue)
{
  FRotator NewRotation = GetRelativeRotation();

  switch(RotateAxis)
  {
    case ERotationAxis::Pitch:
      NewRotation.Pitch += CurveValue;
      break;
    case ERotationAxis::Roll:
      NewRotation.Roll += CurveValue;
      break;
    case ERotationAxis::Yaw:
      NewRotation.Yaw += CurveValue;
      break;
    default:
      break;
  }

  SetRelativeRotation(NewRotation);
}


void UMovableStaticMeshComponent::UpdateLocation(float CurveValue)
{
  FVector NewLocation = GetRelativeLocation();

  switch(LocationAxis)
  {
    case ELocationAxis::X:
      NewLocation.X += CurveValue;
      break;
    case ELocationAxis::Y:
      NewLocation.Y += CurveValue;
      break;
    case ELocationAxis::Z:
      NewLocation.Z += CurveValue;
      break;
    default:
      break;
  }

  SetRelativeLocation(NewLocation);
}

As you can see we pass the majority of the work two the two callback functions our timeline is interfacing with: OnMove and OnMoveFinished.

TimelineCallback.BindUFunction(this, FName("OnMove")); 
TimelineFinishCallback.BindUFunction(this, FName("OnMoveFinished"));

OnMove handles the actual position / rotation of the static mesh relative to it’s parent base mesh object.

By overwriting the components Tick Component function we can start / stop the timeline which in turn affects the behavior of our static mesh.

So let’s see how this component can now be used to create a pressure plate.

Let’s define the header first.

UCLASS()
class PUZZLE1_API APressurePlateTrigger : public AActor
{
  GENERATED_BODY()
  
public:	
  // Sets default values for this actor's properties
  APressurePlateTrigger();

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);
  UFUNCTION()
  void OnEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);

private:
  void Interact(bool bIsInteracting);

public:
  UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, meta=(AllowPrivateAcess = "true"), Category = "Trigger")
  UStaticMeshComponent* BaseMesh;

  UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, meta=(AllowPrivateAcess = "true"), Category = "Trigger")
  UMovableStaticMeshComponent* MoveableMesh;

  UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, meta=(AllowPrivateAcess = "true"), Category = "Trigger")
  USphereComponent* OverlapComponent;

  UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Trigger")
  bool bIsDisabled;

  UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Trigger")
  bool bResetTrigger;

  UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Trigger")
  TArray<FName> TargetTags;

private:
  bool bIsTriggered;
};

Based on this definition we can see there are a couple of parts that go into our pressureplate.

We have a Static Mesh that acts as our base and a Movable Static Mesh Component as the piece that will go down as our character steps on it.

Lastly we use the overlap of our Sphere Component to trigger the behavior.

// Sets default values
APressurePlateTrigger::APressurePlateTrigger()
{
 	// 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;

  BaseMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Base Mesh"));
  SetRootComponent(BaseMesh);

  MoveableMesh = CreateDefaultSubobject<UMovableStaticMeshComponent>(TEXT("Movable Mesh"));
  MoveableMesh->SetupAttachment(BaseMesh);

  OverlapComponent = CreateDefaultSubobject<USphereComponent>(TEXT("Overlap Area"));
  OverlapComponent->SetupAttachment(MoveableMesh);
}

// Called when the game starts or when spawned
void APressurePlateTrigger::BeginPlay()
{
  Super::BeginPlay();

  OverlapComponent->OnComponentBeginOverlap.AddDynamic(this, &ThisClass::OnBeginOverlap);
  OverlapComponent->OnComponentEndOverlap.AddDynamic(this, &ThisClass::OnEndOverlap);
}

// Called every frame
void APressurePlateTrigger::Tick(float DeltaTime)
{
  Super::Tick(DeltaTime);

}

void APressurePlateTrigger::Interact(bool bIsInteracting)
{
  TArray<AActor*> InteractableActors;
  UGameplayStatics::GetAllActorsWithInterface(GetWorld(), UInteractableInterface::StaticClass(), InteractableActors);

  for(AActor* Actor : InteractableActors)
  {
    FName InteractTag = IInteractableInterface::Execute_GetInteractTag(Actor);
    for(FName Tag : TargetTags)
    {
      if(InteractTag.IsEqual(Tag, ENameCase::IgnoreCase))
      {
        IInteractableInterface::Execute_Interact(Actor, bIsInteracting);
      }
    }
  }
}

void APressurePlateTrigger::OnBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
  if(!bIsDisabled && !bIsTriggered)
  {
    MoveableMesh->Move(true);
    Interact(true);

    bIsTriggered = true;
  }
}

void APressurePlateTrigger::OnEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
  if(!bIsDisabled && bResetTrigger)
  {
    MoveableMesh->Move(false);
    Interact(false);

    bIsTriggered = false;
  }
}

Since we offloaded the majority of the work to our Movable Static Mesh Component you will notice that this actor is really not doing much of anything.

It handles the Begin and End overlap states to simply notify the movable component that it needs to perform some work.

The only tricky bit is examining the world for our affected components. This is done by using the TargetTags against any actor that implements a specific interface.

TArray<AActor*> InteractableActors;
UGameplayStatics::GetAllActorsWithInterface(GetWorld(), UInteractableInterface::StaticClass(), InteractableActors);

for(AActor* Actor : InteractableActors)
{
  FName InteractTag = IInteractableInterface::Execute_GetInteractTag(Actor);
  for(FName Tag : TargetTags)
  {
    if(InteractTag.IsEqual(Tag, ENameCase::IgnoreCase))
    {
      IInteractableInterface::Execute_Interact(Actor, bIsInteracting);
    }
  }
}

Using UGameplayStatics::GetAllActorsWithInterface we can find all the appropriate actors and then use the interface calls directly to call the specific methods on t hose actors.

Let’s go ahead and define our interface as we haven’t done that yet.

// This class does not need to be modified.
UINTERFACE(MinimalAPI)
class UInteractableInterface : public UInterface
{
  GENERATED_BODY()
};

/**
 * 
 */
class PUZZLE1_API IInteractableInterface
{
  GENERATED_BODY()

  // Add interface functions to this class. This is the class that will be inherited to implement this interface.
public:
  UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Interact")
  FName GetInteractTag();

  UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Interact")
  void Interact(bool bInteracting);
};

The two methods of GetInteractTag and Interact is what our doors / windows / chests etc will have to implement in order for our pressure plate to work.

We are going to create a basic actor that similar to our pressure plate actor only contains a few meshes but inherits our new interface.

UCLASS()
class PUZZLE1_API AMovingStaticMeshActor : public AActor, public IInteractableInterface
{
  GENERATED_BODY()
  
public:	
  // Sets default values for this actor's properties
  AMovingStaticMeshActor();

  virtual FName GetInteractTag_Implementation() override;
  virtual void Interact_Implementation(bool bInteracting) override;

  bool IsDisabled() const { return bIsDisabled; }

protected:
  // Called when the game starts or when spawned
  virtual void BeginPlay() override;

public:	
  // Called every frame
  virtual void Tick(float DeltaTime) override;


public:
  UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"), Category = "Moving")
  UStaticMeshComponent* BaseMesh;

  UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"), Category = "Moving")
  UMovableStaticMeshComponent* MovableMesh;

  UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Moving")
  FName InteractTag;

  UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Moving")
  bool bIsDisabled;
};

The other property we need to ensure is available to our new actor is the InteractTag which our GetInteractTag method returns. This is important as we use those values do to a string match.

Now let’s define the body of our new actor.

// Sets default values
AMovingStaticMeshActor::AMovingStaticMeshActor()
{
 	// 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;

  BaseMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Base Mesh"));
  SetRootComponent(BaseMesh);

  MovableMesh = CreateDefaultSubobject<UMovableStaticMeshComponent>(TEXT("Movable Mesh"));
  MovableMesh->SetupAttachment(GetRootComponent());
}

// Called when the game starts or when spawned
void AMovingStaticMeshActor::BeginPlay()
{
  Super::BeginPlay();
}

// Called every frame
void AMovingStaticMeshActor::Tick(float DeltaTime)
{
  Super::Tick(DeltaTime);
}

FName AMovingStaticMeshActor::GetInteractTag_Implementation()
{
  return InteractTag;
}

void AMovingStaticMeshActor::Interact_Implementation(bool bInteracting)
{
  if(!bIsDisabled)
  {
    MovableMesh->Move(bInteracting);
  }
}

Again very simple logic as all we are doing is triggering the Movable Static Mesh Component when someone interacts with this actor.

This actor can now be used to represent a door, window, trunk of a car, or even an elevator. Basically anything that has a single component that you need to move or rotate this will give you that flexbility.

Please check out the video and the GitHub page for more details.

Additionally find the Blueprint version of this tutorial on our Patreon.

Unreal C++ Networking – HTTP GET JSON Request Using REST API

By Networking, Tutorial, Unreal No Comments

Hey guys,

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.

public class Network1 : ModuleRules
{
  public Network1(ReadOnlyTargetRules Target) : base(Target)
  {
    PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;

    PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "HeadMountedDisplay",
                    "Http", "Json", "JsonUtilities",
                    "UMG" });

    PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
  }
}

With these dependencies included we can define our Actor that is going to handle the work of doing the network communication.

In this case we are using an actor that will relay it’s information to a custom UUserWidget that is attached to this actor via a WidgetComponent.

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.

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

void AHTTPGETActor::OnBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
                                   UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
  if(ResponseWidgetComponent)
  {
    UHTTPResponseWidget* ResponseWidget = Cast<UHTTPResponseWidget>(ResponseWidgetComponent->GetWidget());
    if(ResponseWidget)
    {
      ResponseWidget->ShowLoading(true);

      FString Username;
      ANetwork1Character* Character = Cast<ANetwork1Character>(OtherActor);
      if(Character && !Character->GetUsername().IsEmpty())
      {
        Username = Character->GetUsername();
      }

      SendHTTPGet(Username);
    }
  }
}

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.

void AHTTPGETActor::SendHTTPGet(FString Username)
{
  TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request = Http->CreateRequest();

  if(Username.IsEmpty())
  {
    Request->OnProcessRequestComplete().BindUObject(this, &ThisClass::OnGetUsersResponse);
    Request->SetURL("http://localhost:8080/jms-api/users");
  }
  else
  {
    Request->OnProcessRequestComplete().BindUObject(this, &ThisClass::OnGetUserByUsernameResponse);
    Request->SetURL(FString::Printf(TEXT("http://localhost:8080/jms-api/user/%s"), *Username));
  }

  Request->SetVerb("GET");
  Request->SetHeader("User-Agent", "X-UnrealEngine-Agent");
  Request->SetHeader("Content-Type", "application/json");
  Request->ProcessRequest();
}

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.

{
  "users": [
    {
      "id": 1,
      "username": "test-user",
      "email": "[email protected]"
    },
    {
      "id": 2,
      "username": "jms-user",
      "email": "[email protected]"
    },
    {
      "id": 3,
      "username": "john.smith",
      "email": "[email protected]"
    }
  ]
}

While the OnGetUserByUsernameResponse will process a single user entity response.

{
  "id": 1,
  "username": "test-user",
  "email": "[email protected]"
}

Now let’s take a look at the implementation details for these two payloads.

void AHTTPGETActor::OnGetUsersResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
  TSharedPtr<FJsonObject> JsonObject;

  if(ResponseWidgetComponent)
  {
    UHTTPResponseWidget* ResponseWidget = Cast<UHTTPResponseWidget>(ResponseWidgetComponent->GetWidget());
    if(ResponseWidget)
    {
      ResponseWidget->ShowLoading(false);

      if(Response->GetResponseCode() == 200)
      {
        const FString ResponseBody = Response->GetContentAsString();

        TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(ResponseBody);

        if(FJsonSerializer::Deserialize(Reader, JsonObject))
        {
          TArray<TSharedPtr<FJsonValue>> UserArray = JsonObject->GetArrayField("users");

          for(const TSharedPtr<FJsonValue> UserValue : UserArray)
          {
            AddUserToWidget(UserValue->AsObject());
          }
        }
      }
      else
      {
        // TODO: trigger error
        ResponseWidget->ShowError(Response->GetResponseCode(), "Error occured");
      }
    }
  }
}


void AHTTPGETActor::OnGetUserByUsernameResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
  TSharedPtr<FJsonObject> JsonObject;

  if(ResponseWidgetComponent)
  {
    UHTTPResponseWidget* ResponseWidget = Cast<UHTTPResponseWidget>(ResponseWidgetComponent->GetWidget());
    if(ResponseWidget)
    {
      ResponseWidget->ShowLoading(false);

      if(Response->GetResponseCode() == 200)
      {
        const FString ResponseBody = Response->GetContentAsString();

        TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(ResponseBody);

        if(FJsonSerializer::Deserialize(Reader, JsonObject))
        {
          AddUserToWidget(JsonObject);
        }
      }
      else
      {
        // TODO: trigger error
        ResponseWidget->ShowError(Response->GetResponseCode(), "Error occured");
      }
    }
  }
}

void AHTTPGETActor::AddUserToWidget(TSharedPtr<FJsonObject> JsonObject)
{
  const int32 UserId = JsonObject->GetIntegerField("id");
  const FString Username = JsonObject->GetStringField("username");

  UHTTPResponseWidget* ResponseWidget = Cast<UHTTPResponseWidget>(ResponseWidgetComponent->GetWidget());
  if(ResponseWidget)
  {
    ResponseWidget->AddUser(UserId, Username);
  }
}

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 FJsonObject in combination with the TJsonReader

const FString ResponseBody = Response->GetContentAsString(); 
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(ResponseBody);

This gives us the ability to then get at the individual properties of a JSON response. Specifically since one requires access to an Array.

if(FJsonSerializer::Deserialize(Reader, JsonObject))
{
  TArray<TSharedPtr<FJsonValue>> UserArray = JsonObject->GetArrayField("users");

  for(const TSharedPtr<FJsonValue> UserValue : UserArray)
  {
    AddUserToWidget(UserValue->AsObject());
  }
}

void AHTTPGETActor::AddUserToWidget(TSharedPtr<FJsonObject> JsonObject)
{
  const int32 UserId = JsonObject->GetIntegerField("id");
  const FString Username = JsonObject->GetStringField("username");

  UHTTPResponseWidget* ResponseWidget = Cast<UHTTPResponseWidget>(ResponseWidgetComponent->GetWidget());
  if(ResponseWidget)
  {
    ResponseWidget->AddUser(UserId, Username);
  }
}

By using the various getters ( GetArrayField / GetIntegerField / GetStringField ) we can iterate and access the various properties of our payload.

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.

Unreal Engine C++ Fundamentals – Using Inheritance by moving the player & particles along a spline

By Tutorial, Unreal No Comments

Hey guys,

Today we are going to continue exploring our last tutorial by looking at inheritance and what it takes to refactor our class.

The project files for this video & article can be found on our GitHub page.

The majority of the inheritance work is by creating a base class that our children can derive from which contains the primary logic that all children are always expected to perform.

To do this we are going to take our previous MovingSplineActor class make some of it’s methods virtual and migrate all of it’s Mesh specific logic to a new MeshMovingSplineActor class.

My making our MovingSplineActor functions virtual we provide the child class to override said functions and implement it’s own logic.

Since we need to have our MeshMovingSplineActor do it’s own work on a static mesh that the parent MovingSplineActor those methods need to be accessible otherwise it’s going to cause conflicts in our logic.

UCLASS()
class UE4FUNDAMENTALS14LIVE_API AMovingSplineActor : public AActor
{
  GENERATED_BODY()
  
public:	
  // Sets default values for this actor's properties
  AMovingSplineActor();

protected:
  // Called when the game starts or when spawned
  virtual void BeginPlay() override;

public:	
  // Called every frame
  virtual void Tick(float DeltaTime) override;

  UFUNCTION()
  virtual void ProcessMovementTimeline(float Value);

  UFUNCTION()
  virtual void OnEndMovementTimeline();

  UFUNCTION()
  virtual void TriggerBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
  UFUNCTION()
  virtual void TriggerEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
}

Additionally we need to ensure that the logic we had in our MovingSplineActor is available to our child class. In this case we need to move the SplineLocation and SplineRotation variables out of the initial processing logic and into their own protected variables.

protected:
  FVector StartingSplineLocation;
  FVector CurrentSplineLocation;
  FRotator CurrentSplineRotation;

We can then modify our CPP file to ensure those variables are populated.

void AMovingSplineActor::ProcessMovementTimeline(float Value)
{
  const float SplineLength = SplineComponent->GetSplineLength();

  CurrentSplineLocation = SplineComponent->GetLocationAtDistanceAlongSpline(Value * SplineLength, ESplineCoordinateSpace::World);
  CurrentSplineRotation = SplineComponent->GetRotationAtDistanceAlongSpline(Value * SplineLength, ESplineCoordinateSpace::World);
}

By migrating all of our logic to the MeshMovingSplineActor we need to ensure it inherits from our new parent class. To do this we need to modify the header definitions to change it from AActor.

UCLASS()
class UE4FUNDAMENTALS14LIVE_API AMeshMovingSplineActor : public AMovingSplineActor
{
  GENERATED_BODY()
  
public:	
  // Sets default values for this actor's properties
  AMeshMovingSplineActor();

protected:
  // Called when the game starts or when spawned
  virtual void BeginPlay() override;

public:	
  // Called every frame
  virtual void Tick(float DeltaTime) override;

  virtual void ProcessMovementTimeline(float Value) override;

  UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Spline", meta=(AllowPrivateAccess = "true"))
  UStaticMeshComponent* MeshComponent;
};

With our headers defined we can now migrate the logic of actually moving the spline component into our new child actor.

AMeshMovingSplineActor::AMeshMovingSplineActor()
{
 	// 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;

  MeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh Component"));
  MeshComponent->SetupAttachment(SplineComponent);

  TriggerComponent->SetupAttachment(MeshComponent);
}

// Called when the game starts or when spawned
void AMeshMovingSplineActor::BeginPlay()
{
  Super::BeginPlay();

  MeshComponent->SetWorldLocation(StartingSplineLocation);
}

// Called every frame
void AMeshMovingSplineActor::Tick(float DeltaTime)
{
  Super::Tick(DeltaTime);

}

void AMeshMovingSplineActor::ProcessMovementTimeline(float Value)
{
  Super::ProcessMovementTimeline(Value);

  FRotator SplineRotation = CurrentSplineRotation;
  SplineRotation.Pitch = 0.f;
  MeshComponent->SetWorldLocationAndRotation(CurrentSplineLocation, SplineRotation);
}

If you notice, each override method ends up calling “Super::MethodSignature()” in order to execute the logic in the parent. Since our parent is simply moving the timeline around we don’t have to worry about anything other than the mesh in our timeline.

So that’s it, you just did some inhertiance work by shuffling around a few class definitions.

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.