From a42e4c666744f7138a35490da4bee198eba382cc Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Sun, 4 Jun 2023 15:56:36 -0400 Subject: [PATCH] Start implementing state machine --- Source/UnrealFightingEngine/State/FEState.h | 6 +- .../State/FEStateMachineComponent.cpp | 123 ++++++++++++++++-- .../State/FEStateMachineComponent.h | 106 ++++++++++++++- 3 files changed, 219 insertions(+), 16 deletions(-) diff --git a/Source/UnrealFightingEngine/State/FEState.h b/Source/UnrealFightingEngine/State/FEState.h index 395c143..29b003b 100644 --- a/Source/UnrealFightingEngine/State/FEState.h +++ b/Source/UnrealFightingEngine/State/FEState.h @@ -2,8 +2,9 @@ #pragma once +// UE includes #include "CoreMinimal.h" -#include "UObject/NoExportTypes.h" + #include "FEState.generated.h" /** @@ -19,6 +20,9 @@ public: /** * Initializes pointers to what owns this state and what avatar this state represents. + * + * @param InOwner Actor that owns this state machine. + * @param InAvatar Actor that this state machine represents. */ virtual void InitActorInfo(AActor* InOwner, AActor* InAvatar); diff --git a/Source/UnrealFightingEngine/State/FEStateMachineComponent.cpp b/Source/UnrealFightingEngine/State/FEStateMachineComponent.cpp index 7a8b740..f83c169 100644 --- a/Source/UnrealFightingEngine/State/FEStateMachineComponent.cpp +++ b/Source/UnrealFightingEngine/State/FEStateMachineComponent.cpp @@ -1,28 +1,135 @@ // Unreal Fighting Engine by Kevin Poretti +// FE includes +#include "FEState.h" + #include "FEStateMachineComponent.h" -// Sets default values for this component's properties UFEStateMachineComponent::UFEStateMachineComponent() { - // Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features - // off to improve performance if you don't need them. + // Don't use Unreal's tick instead use a fixed tick PrimaryComponentTick.bCanEverTick = false; - - // ... } -// Called when the game starts +void UFEStateMachineComponent::Initialize() +{ + for(const TSubclassOf& CurrState : DefaultStates) + { + UFEState* TempState = AddState(CurrState); + if(!CurrentState) // first state to be created is the entry into this state machine + { + CurrentState = TempState; + CurrentState->Enter(); + } + } +} + + +void UFEStateMachineComponent::InitActorInfo(AActor* InOwner, AActor* InAvatar) +{ + Owner = InOwner; + Avatar = InAvatar; +} + + +UFEState* UFEStateMachineComponent::AddState(TSubclassOf StateClassToAdd) +{ + UFEState* TempState = NewObject(this, StateClassToAdd); + if(TempState) + { + States.Add(TempState); + TempState->InitActorInfo(Owner, Avatar); + return TempState; + } + + return nullptr; +} + + +void UFEStateMachineComponent::AddStates(const TArray>& StateClassesToAdd) +{ + for(const TSubclassOf& CurrState : StateClassesToAdd) + { + AddState(CurrState); + } +} + + +void UFEStateMachineComponent::RemoveState(FName StateToRemove) +{ + UE_LOG(LogTemp, Error, TEXT("UFEStateMachineComponent::RemoveState is not yet implemented")); +} + +void UFEStateMachineComponent::SwitchStates(UFEState* NewState) +{ + check(NewState); + + CurrentState->Exit(); + CurrentState = NewState; + CurrentState->Enter(); +} + + +FName UFEStateMachineComponent::GetCurrentStateName() const +{ + return CurrentState ? CurrentState->Name : NAME_None; +} + + +UFEState* UFEStateMachineComponent::FindStateWithName(FName StateName) +{ + for (UFEState* CurrState : States) + { + if(CurrState ->Name == StateName) + { + return CurrState; + } + } + + UE_LOG(LogTemp, Warning, + TEXT("Could not find state in state machine with name %s on %s"), *StateName.ToString(), *Owner->GetName()); + + return nullptr; +} + + void UFEStateMachineComponent::BeginPlay() { Super::BeginPlay(); - // ... - + Initialize(); } + void UFEStateMachineComponent::FixedTick(float OneFrame) { + // Should we switch states? + + for(UFEState* CurrState : States) + { + // Check if the state is enabled + + // Check state entry conditions if there are any + + // Check input conditions if there are any + + // If all state entry conditions are good and at least one input condition is good then we can transition + + // Lastly just check if the state we're about to transition into isn't the current state. + // It is OK to transition if state's "CanTransitionToSelf" is true + + // SwitchStates(NewState); + // return; + } + + // CurrentState should never be null + // TODO: Should probably assert or whatever UE's equivalent is + check(CurrentState); + + // Tick current state + CurrentState->FixedTick(OneFrame); + + // Debug } diff --git a/Source/UnrealFightingEngine/State/FEStateMachineComponent.h b/Source/UnrealFightingEngine/State/FEStateMachineComponent.h index 2077de8..5cb2428 100644 --- a/Source/UnrealFightingEngine/State/FEStateMachineComponent.h +++ b/Source/UnrealFightingEngine/State/FEStateMachineComponent.h @@ -2,25 +2,117 @@ #pragma once +// FE includes +#include "UnrealFightingEngine/IFESystemInterface.h" + +// UE includes #include "CoreMinimal.h" #include "Components/ActorComponent.h" -#include "UnrealFightingEngine/IFESystemInterface.h" + #include "FEStateMachineComponent.generated.h" - +/** + * A state machine is a component that evaluates and controls the transitions for state objects that + * are a part of this state machine. + * + * This component also calls the appropriate state logic when a state is changed or the component ticks. + */ UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) class UNREALFIGHTINGENGINE_API UFEStateMachineComponent : public UActorComponent, public IIFESystemInterface { GENERATED_BODY() public: - // Sets default values for this component's properties UFEStateMachineComponent(); -protected: - // Called when the game starts - virtual void BeginPlay() override; + /** + * Creates and adds default states and enters the first state + */ + void Initialize(); -public: + /** + * Initializes pointers to what owns this state machine and what avatar this state represents. + * + * These pointers will also be passed to the states owned by this state machine. + * + * @param InOwner Actor that owns this state machine. + * @param InAvatar Actor that this state machine represents. + */ + virtual void InitActorInfo(AActor* InOwner, AActor* InAvatar); + + /** + * Creates an instance of the state class and adds newly created state to this state machine. + * + * @param StateClassToAdd State class type to be added to this state machine + * + * @return A pointer to the state that was added or nullptr if there was an issue adding or creating the state + */ + UFEState* AddState(TSubclassOf StateClassToAdd); + + /** + * Creates an instance of the state classes and adds newly created states to this state machine. + * + * @param StateClassesToAdd Array of state class types to be added to this state machine + */ + void AddStates(const TArray>& StateClassesToAdd); + + /** + * Destroys the state with corresponding name and removes it from this state machine. + */ + void RemoveState(FName StateToRemove); + + /** + * Transitions from CurrentState to the new state passed to this function + * + * Triggers the Exit callback on the CurrentState and the Enter callback on the new state + */ + void SwitchStates(UFEState* NewState); + + /** + * Returns the name of the current state + */ + UFUNCTION(BlueprintPure) + FName GetCurrentStateName() const; + + // IIFESystemInterface interface virtual void FixedTick(float OneFrame) override; + // End of IIFESystemInterface interface + +protected: + /** + * Actor that owns this state machine. + * This will typically be a player controller that possesses the avatar. + */ + UPROPERTY(BlueprintReadOnly) + AActor* Owner; + + /** + * The avatar is an actor that this state machine represents. + * This will typically be a pawn or character the state machine is attached to. + */ + UPROPERTY(BlueprintReadOnly) + AActor* Avatar; + + /** + * States classes to create and add to this state machine when the game starts + */ + UPROPERTY(EditDefaultsOnly, Category="UFE|State Machine") + TArray> DefaultStates; + + /** Current active state for this state machine */ + UPROPERTY(BlueprintReadOnly) + UFEState* CurrentState; + + // States that have been added + UPROPERTY(BlueprintReadOnly) + TArray States; + + /** + * Returns the state with corresponding name + */ + UFEState* FindStateWithName(FName StateName); + + // UActorComponent interface + virtual void BeginPlay() override; + // End of UActorComponent interface };