C++ State Machine: Question about UObject variable

Hi,

I’m sure this is basic, but can’t seem to figure out how to have one variable hold UObjects of same parent and have access to its functions. I can do it with Blueprints, but the idea is 100% C++ for this specifically.

This is what I have:

// In Actor Component
switch (ClassDefaults.CurrentState)
{
	case ENUM::StateOne:

		if (StateOne)
		{
			StateOne->Update(DeltaTime, WorldTime, ClassDefaults);
		}

		break;

	case ENUM::StateTwo:

		if (StateTwo)
		{
			StateTwo->Update(DeltaTime, WorldTime, ClassDefaults);
		}

		break;
}
  • ClassDefaults is a struct with TEnumAsByte< ENUM > CurrentState;
  • UStateOne and UStateTwo are both from the same parent: UBaseState.
  • Update() in BaseState.h:
virtual void Update(const float& DeltaTime, int32& WorldTime,
		FDefaults& ClassDefaults);

This works but it is getting long and repetitive… What I would like is to not depend on the enums and use a single pointer. Something like:

ClassDefaults.PrevState = ClassDefaults.CurrentState;
ClassDefaults.CurrentState = ClassDefaults.NextState;
ClassDefaults.NextState = ClassDefaults.States[];
// ..
if(ClassDefaults.CurrentState)
    ClassDefaults.CurrentState->Update(DeltaTime, WorldTime, ClassDefaults);

Thanks in advance.

Just store the pointers of the children as a pointer of the base class, then cast to the child class to get the overriden function?

//Make ClassDefaults.CurrentState of type UBaseState
//Assign CurrentState with child class objects normally, implicit upcast to UBaseState occurs
UChildState* foo;
ClassDefaults.CurrentState = foo;
//Call Update after downcast back to child class
if(ClassDefaults.CurrentState)
    Cast<ClassDefaults.CurrentState::StaticClass()>(ClassDefaults.CurrentState)->Update(DeltaTime, WorldTime, ClassDefaults);
1 Like

This won’t work. Template parameters must be compile time constants. You can’t Cast to a runtime value like the return value from StaticClass.

@pezzott1 What is the specific problem that you’re encountering? Your “Something like:” code is perfectly reasonable and should work fine. Why don’t you want to depend on the enum?

Personally, I’d recommend your Update parameters be ( float, int32, const Defaults& ) but that shouldn’t matter towards getting your state machine working.

1 Like

Thank you all for taking your time.

The problem I’m having is that I keep getting a nullptr. Will share later what I’ve tried.

Feel like my approach with enums is forcing me to hard code each state in the switch, even when it’s all copy / paste just changing the variable name. I’m looking for a solution that automates adding / editing / removing states.

Hmm… One thing I would suspect from your typename would be that because Defaults isn’t a USTRUCT (since if it was it would be FDefaults) your references to the class objects aren’t visible to the Garbage Collector and are getting destroyed. But then you should be hitting garbage and not nullptr. You haven’t shared enough yet for me to give you anything definitive though.

I can see how it would seem that way. However you can do this:

ClassDefaults.NextState = ClassDefaults.States[  static_cast< int >( ClassDefaults.CurrentState ) ];

to use the enum as the direct index into the array. This will work as long as all the enum values are valid array indexes. Another option would be to use a TMap, which would allow you to do TMap< ENUM, UBaseState* > which is a bit harder to setup but would avoid the casting and ignore any of the actual values of the enumeration.

Either way, I would recommend changing up your enum to be declared as:

enum class ENUM : uint8

and drop the use of TEnumAsByte. That’s an old workaround that is unnecessary with the .27 and EA versions of the engine you’ve tagged the question for.

1 Like

My bad, did not typed it properly, it’s a struct. What I copied above is an extract and variables names changed just so it made sense fast. Fixed :grimacing:

This is exactly what I’m after.

Maybe I don’t get what you are trying to do, but isn’t something like this?

class UBaseState
{
public:
virtual void Update() = 0;
};

class State1 : BaseState
{
public:
void Update()
{
printf(“Update from A”);
}
};

class State2 : BaseState
{
public:
void Update()
{
printf(“Update from B”);
}
};

…Pseudo-code follows
arr = array of BaseState
arr[0] = new State1();
arr[1] = new State2();
arr[0]->Update(); // Update from A
arr[1]->Update(); // Update from B

1 Like

Yes!!

And I’m stuck at this last part:

The “easiest” part… :grimacing:

That part is basically this one:

ClassDefaults.PrevState = ClassDefaults.CurrentState;
ClassDefaults.CurrentState = ClassDefaults.NextState;
ClassDefaults.NextState = ClassDefaults.States;
// …
if(ClassDefaults.CurrentState)
ClassDefaults.CurrentState->Update(DeltaTime, WorldTime, ClassDefaults);

1 Like

Yes, but this always returns false or crashes if I don’t check:

No errors on compile or during play, just crashes when this line runs:

That’s probably because CurrenState is not pointing to a valid object. How are you setting it?

1 Like

Thanks for your time.

Not in front of my pc right now, but if I remember correctly, this was one:

.h 
UPROPERTY()
TSubclassOf< UBaseState > CurrentState;
UPROPERTY()
UState1* State1;

.cpp
// Constructor
State1 = CreateDefaultSuboject<UState1>(TEXT("State1"));

// BeginPlay..
CurrentState = State1; 

if(CurrentState) // false
CurrentState->Updates(); // Crashes.

Agree, but have not been able to figure it out.

Just found this: TSubclassOf | Unreal Engine 4.27 Documentation

Assuming that you’re remembering correctly:

You shouldn’t be using TSubclassOf for CurrentState. That should just be UBaseState *CurrentState;

The reason it’s failing is because when you access it, it tries to return the UClass for a class that is derived from UBaseState. That’s not what you’ve assigned to it. You’ve assigned to it an instance of something derived from UBaseState and UBaseState is not a UClass.

1 Like

I sware this was the first thing I tried before getting creative… :face_with_symbols_over_mouth:

Anways… tested with new classes and it works! Thank you all for your time.

// ActorComponent.h

UENUM()
enum class UStateSelect : uint8
{
	State01 UMETA(DisplayName = "State01"),
	State02 UMETA(DisplayName = "State02"),
};

// UClass
UPROPERTY()
     UMyStateBase* MyStateBase01;
UPROPERTY()
     UMyState01* MyState01;
UPROPERTY()
     UMyState02* MyState02;
UPROPERTY()
      TArray<class UMyStateBase*> MyStateArray;
UPROPERTY()
      uint8 CurrentState;

void Update(const UStateSelect& ss);
// ActorComponent.cpp

// Constructor
MyState01 = CreateDefaultSubobject<UMyState01>(TEXT("MyState01"));
MyState02 = CreateDefaultSubobject<UMyState02>(TEXT("MyState02"));
MyStateArray.Add(MyState01);
MyStateArray.Add(MyState02);

// BeginPlay
    MyStateBase01 = MyState01;

	if (MyStateBase01) // true
		MyStateBase01->Update(); // MyState01 works.

	MyStateBase01 = MyState02;

	if (MyStateBase01) // true
		MyStateBase01->Update(); // MyState02 works.
        
    // THIS! I'm crying!
	CurrentState = UStateSelect::State01;
	Update(CurrentState); // MyState01 works.

	CurrentState = UStateSelect::State02;
	Update(CurrentState); // MyState02 works.
void UMyActorComponent::Update(const UStateSelect& ss)
{
	if (MyStateArray[(uint8)ss]) // true
		MyStateArray[(uint8)ss]->Update();
}

image

1 Like