top of page

UE5 Finite State Machine - Part 3

In the last post, we added some logic that would highlight the farm plot when the player looked at it. Now we need to figure out where the player is looking. To do this we're going create some blueprint functions in our third person character blueprint.


Trace to Object

The first function we're going to make is called Trace To Object. Its purpose is to do a hit test from the camera x amount of units and return the first actor hit.

The inputs for this function are draw debug type (EDrawDebugTraceEnum) and the interact distance (float). This function does a visibility trace starting at the follow camera's world location and traces out to the interact distance. We forward the draw debug type from the function input to determine if we should draw debug traces, defaulting to off. If we hit something, we get the actor from the hit result and return it. We can use this trace for lots of things in the future, not just observable items.


Now that we can look at something, lets do something. We're going to write another blueprint function that'll call this Trace To Object function. Call this new function Get Looked At Object.


Create a new event graph, call it Interaction and add the tick event. For each tick we call our get looked at object function. This function is a little back and forth. There are a lot of checking here to determine if what we are looking at is observable, if we are already looking at it and respond accordingly.

First thing we do is get the trace to a new object and hold on to the result for later in a variable called _newObserved. If that new object and the last observed object are the same, return the last observed object. Now, if they aren't the same, we go into a sequence.


First check to see if our last observed implements our observerable interface, if so, call the look away method. Next, check to see if our new observed implements our observable, if so, call the look at method and return the new observed actor. if it doesn't implement the observable interface, just return the new observed actor (without calling look at method). In the interaction event graph we create, create a node for the event tick and call the get looked at object function. we need to update the last observed variable.

Now, dragging the farm plot CPP class will create an instance in your world and you can go look at it and away and see the highlight mesh appear and disappear. This is good. we are now getting our observable actor. in much the same way, we're going to invoke the work method to change the state of our FSM farm plot, and watch it update.


Checking FSM Initialization

Lets add an interact input for our project. Lets make sure we have our fsm is being initialized correctly. If you haven't already make sure the farmplot constructor, before creating the materials and meshes initialize the fsm.

#include "Farmplot.h"
#include "FarmplotMeshMaterialTableRow.h"

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

	fsm = new FarmplotFSM();

	groundMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Ground Mesh"));
	groundMesh->SetupAttachment(RootComponent);
...
}
...
void AFarmplot::Interact_Implementation()
{
    fsm->BeginWork();
}

With this setup, add an interact action event. The event checks if the last observed currently implements the interactable interface and then calls the interact method.

To see this working you can add print messages in the state operators. To see this working the best, add multiple farmplots into the world and interact with them to see how each holds their own state.


class FarmplotFSM
{
...
private
struct Work 
{
private:

    State m_state;

public:
    Work() 
    {
        m_state.Set<Placed>(Placed());
    };

    State operator()(const Placed&)
    {
        GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, TEXT("Changing state to Tilled"));
        m_state.Set<Tilled>(Tilled());
        return m_state;
    }
    State operator()(const Tilled&)
    {
        GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, TEXT("Changing state to Fertilized"));
        m_state.Set<Fertilized>(Fertilized());
        return m_state;
    }
    State operator()(const Fertilized&)
    {
        m_state.Set<Plowed>(Plowed());
        return m_state;
    }
    State operator()(const Plowed&)
    {
        m_state.Set<Planted>(Planted());
        return m_state;
    }
    State operator()(const Planted&)
    {
        m_state.Set<Placed>(Placed());
        return m_state;
    }
};
...

Another location you can add additional print messages are in the FarmplotFSM::BeginWork() method


#include "FarmplotFSM.h"

void FarmplotFSM::BeginWork()
{
    GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, TEXT("Changing state"));
    _currentState = Visit(_work, _currentState);
    GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Green, TEXT("State Changed"));
}

This is where the technical end of the FSM is, but none of the changes are reflected in the game. So the next post will expand upon the FSM. Some things to add include change notification events and gated transitions.

Comments


Southern Software Engineer

©2023 by The Souther Software Engineer. Proudly created with Wix.com

bottom of page