From 4cf3834b2d5514071034668db2aefc405c722347 Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Sat, 15 Jul 2023 21:36:46 -0400 Subject: [PATCH] Start of state system rework --- .../UnrealFightingFramework/State/FFState.cpp | 55 ++++++---- .../UnrealFightingFramework/State/FFState.h | 103 ++++++++++-------- .../State/FFStateContextInterface.cpp | 6 + .../State/FFStateContextInterface.h | 31 ++++++ .../State/FFStateMachineComponent.cpp | 43 +++++--- .../State/FFStateMachineComponent.h | 23 ++-- 6 files changed, 172 insertions(+), 89 deletions(-) create mode 100644 Source/UnrealFightingFramework/State/FFStateContextInterface.cpp create mode 100644 Source/UnrealFightingFramework/State/FFStateContextInterface.h diff --git a/Source/UnrealFightingFramework/State/FFState.cpp b/Source/UnrealFightingFramework/State/FFState.cpp index 7240cbc..a06214a 100644 --- a/Source/UnrealFightingFramework/State/FFState.cpp +++ b/Source/UnrealFightingFramework/State/FFState.cpp @@ -5,51 +5,60 @@ // FF includes #include "FFStateMachineComponent.h" -void UFFState::InitActorInfo(AActor* InOwner, AActor* InAvatar) + +void UFFStateBehavior::Enter(const FFFStateContext& InStateContext) { - Owner = InOwner; - Avatar = InAvatar; + OnEnter(InStateContext); } -void UFFState::Enter() +void UFFStateBehavior::Exit(const FFFStateContext& InStateContext) { - TicksInState = 0; - - OnEnter(); + OnExit(InStateContext); } -void UFFState::Exit() +void UFFStateBehavior::Update(float OneFrame, const FFFStateContext& InStateContext) { - OnExit(); + OnUpdate(OneFrame, InStateContext); } - -void UFFState::Update(float OneFrame) -{ - TicksInState++; - - OnUpdate(OneFrame); -} - - -void UFFState::OnEnter_Implementation() +void UFFStateBehavior::OnLanded_Implementation(const FFFStateContext& InStateContext) { } -void UFFState::OnExit_Implementation() +void UFFStateBehavior::OnEnter_Implementation(const FFFStateContext& InStateContext) { } -void UFFState::OnUpdate_Implementation(float OneFrame) +void UFFStateBehavior::OnExit_Implementation(const FFFStateContext& InStateContext) { } -UWorld* UFFState::GetWorld() const +void UFFStateBehavior::OnUpdate_Implementation(float OneFrame, const FFFStateContext& InStateContext) +{ +} + + +void UFFStateBehavior::OnHit_Implementation(const FFFStateContext& InStateContext) +{ +} + + +void UFFStateBehavior::OnBlock_Implementation(const FFFStateContext& InStateContext) +{ +} + + +void UFFStateBehavior::OnInputEvent_Implementation(const FFFStateContext& InStateContext) +{ +} + + +UWorld* UFFStateBehavior::GetWorld() const { UFFStateMachineComponent* SMC = Cast(GetOuter()); if(SMC) @@ -58,4 +67,4 @@ UWorld* UFFState::GetWorld() const } return nullptr; -} \ No newline at end of file +} diff --git a/Source/UnrealFightingFramework/State/FFState.h b/Source/UnrealFightingFramework/State/FFState.h index bc0073c..c851b80 100644 --- a/Source/UnrealFightingFramework/State/FFState.h +++ b/Source/UnrealFightingFramework/State/FFState.h @@ -7,26 +7,12 @@ #include "FFState.generated.h" -/** - * A state is an object that provides rules and conditions for when a state can be transitioned into - * and logic to run when the state is entered, exited, and active. - */ -UCLASS() -class UNREALFIGHTINGFRAMEWORK_API UFFState : public UObject +USTRUCT(BlueprintType) +struct FFFStateData { - GENERATED_BODY() + GENERATED_BODY() -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); - - /** Name of this state */ + /** Name of this state */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="UFF|State") FName Name; @@ -38,21 +24,56 @@ public: UPROPERTY(EditAnywhere) uint8 StateType; - /** How many ticks have elapsed since this state was entered */ - UPROPERTY(BlueprintReadOnly, Category="UFF|State") - int32 TicksInState; +}; +USTRUCT(BlueprintType) +struct FFFStateContext +{ + GENERATED_BODY() + + /** Actor that owns the avatar. Typically a player controller. */ + AActor* Owner; + + /** + * Actor that represents the player's avatar or an object associated with the player's avatar. + * This is typically a character or a weapon. + */ + AActor* Avatar; + + /** + * Data associated with this state. + * For example this can be new movement values or data about the hitboxes if this state represents an attack. + */ + FFFStateData StateData; +}; + +/** + * A state is an object that provides rules and conditions for when a state can be transitioned into + * and logic to run when the state is entered, exited, and active. + */ +UCLASS() +class UNREALFIGHTINGFRAMEWORK_API UFFStateBehavior : public UObject +{ + GENERATED_BODY() + +public: + /** Name of this state behavior */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="UFF|State") + FName Name; + + // TODO: since state's are now purely behavioral can we remove these function calls? + // They are basically redundant with OnEnter, OnUpdate, OnExit, etc. /** * Called whenever this state is transitioned into. * * Resets TicksInState and calls appropriate Blueprint hooks */ - void Enter(); + void Enter(const FFFStateContext& InStateContext); /** * Called whenever this state is transitioned out of into a new state. */ - void Exit(); + void Exit(const FFFStateContext& InStateContext); /** * Called whenever this state is active and the game logic ticks. @@ -61,19 +82,19 @@ public: * * @param OneFrame the time that elapses during one fixed tick */ - void Update(float OneFrame); + void Update(float OneFrame, const FFFStateContext& InStateContext); /** * Blueprint hook that is called whenever this state is transitioned into */ UFUNCTION(BlueprintNativeEvent, Category="UFF|State|Events") - void OnEnter(); + void OnEnter(const FFFStateContext& InStateContext); /** * Blueprint hook that is called whenever this state is transitioned out of into a new state */ UFUNCTION(BlueprintNativeEvent, Category="UFF|State|Events") - void OnExit(); + void OnExit(const FFFStateContext& InStateContext); /** * Blueprint hook that is called whenever this state is active and the game logic ticks @@ -81,25 +102,21 @@ public: * @param OneFrame the time that elapses during one fixed tick */ UFUNCTION(BlueprintNativeEvent, Category="UFF|State|Events") - void OnUpdate(float OneFrame); + void OnUpdate(float OneFrame, const FFFStateContext& InStateContext); + + UFUNCTION(BlueprintNativeEvent, Category="UFF|State|Events") + void OnLanded(const FFFStateContext& InStateContext); + + UFUNCTION(BlueprintNativeEvent, Category="UFF|State|Events") + void OnHit(const FFFStateContext& InStateContext); + + UFUNCTION(BlueprintNativeEvent, Category="UFF|State|Events") + void OnBlock(const FFFStateContext& InStateContext); + + UFUNCTION(BlueprintNativeEvent, Category="UFF|State|Events") + void OnInputEvent(const FFFStateContext& InStateContext); // UObject interface virtual UWorld* GetWorld() const override; // End of UObject interface - -protected: - - /** - * Actor that owns this state. - * This will typically be a player controller that possesses the avatar. - */ - UPROPERTY(BlueprintReadOnly) - AActor* Owner; - - /** - * The avatar is an actor that this state represents. - * This will typically be a pawn or character. - */ - UPROPERTY(BlueprintReadOnly) - AActor* Avatar; }; diff --git a/Source/UnrealFightingFramework/State/FFStateContextInterface.cpp b/Source/UnrealFightingFramework/State/FFStateContextInterface.cpp new file mode 100644 index 0000000..045d032 --- /dev/null +++ b/Source/UnrealFightingFramework/State/FFStateContextInterface.cpp @@ -0,0 +1,6 @@ +// Unreal Fighting Framework by Kevin Poretti + + +#include "State/FFStateContextInterface.h" + +// Add default functionality here for any IFFStateContextInterface functions that are not pure virtual. diff --git a/Source/UnrealFightingFramework/State/FFStateContextInterface.h b/Source/UnrealFightingFramework/State/FFStateContextInterface.h new file mode 100644 index 0000000..a797aad --- /dev/null +++ b/Source/UnrealFightingFramework/State/FFStateContextInterface.h @@ -0,0 +1,31 @@ +// Unreal Fighting Framework by Kevin Poretti + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Interface.h" + +#include "FFStateContextInterface.generated.h" + +UINTERFACE(MinimalAPI) +class UFFStateContextInterface : public UInterface +{ + GENERATED_BODY() +}; + +/** + * + */ +class UNREALFIGHTINGFRAMEWORK_API IFFStateContextInterface +{ + GENERATED_BODY() + +public: + virtual bool CheckStateEnabled(uint8 StateType) = 0; + + virtual bool CheckStance(uint8 Stance) = 0; + + virtual bool CheckStateEntryConditions(const TArray& EntryConditions) = 0; + + virtual bool CheckInputSequences(const TArray& InputSequences) = 0; +}; diff --git a/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp b/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp index 9c74dd9..42d19aa 100644 --- a/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp +++ b/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp @@ -14,13 +14,13 @@ UFFStateMachineComponent::UFFStateMachineComponent() void UFFStateMachineComponent::Initialize() { - for(const TSubclassOf& CurrState : DefaultStates) + for(const TSubclassOf& CurrState : DefaultStates) { - UFFState* TempState = AddState(CurrState); + UFFStateBehavior* TempState = AddState(CurrState); if(!CurrentState) // first state to be created is the entry into this state machine { CurrentState = TempState; - CurrentState->Enter(); + CurrentState->Enter(GetCurrentStateContext()); } } } @@ -33,13 +33,12 @@ void UFFStateMachineComponent::InitActorInfo(AActor* InOwner, AActor* InAvatar) } -UFFState* UFFStateMachineComponent::AddState(TSubclassOf StateClassToAdd) +UFFStateBehavior* UFFStateMachineComponent::AddState(TSubclassOf StateClassToAdd) { - UFFState* TempState = NewObject(this, StateClassToAdd); + UFFStateBehavior* TempState = NewObject(this, StateClassToAdd); if(TempState) { States.Add(TempState); - TempState->InitActorInfo(Owner, Avatar); return TempState; } @@ -47,9 +46,9 @@ UFFState* UFFStateMachineComponent::AddState(TSubclassOf StateClassToA } -void UFFStateMachineComponent::AddStates(const TArray>& StateClassesToAdd) +void UFFStateMachineComponent::AddStates(const TArray>& StateClassesToAdd) { - for(const TSubclassOf& CurrState : StateClassesToAdd) + for(const TSubclassOf& CurrState : StateClassesToAdd) { AddState(CurrState); } @@ -61,13 +60,15 @@ void UFFStateMachineComponent::RemoveState(FName StateToRemove) UE_LOG(LogTemp, Error, TEXT("UFFStateMachineComponent::RemoveState is not yet implemented")); } -void UFFStateMachineComponent::SwitchStates(UFFState* NewState) +void UFFStateMachineComponent::SwitchStates(UFFStateBehavior* NewState) { check(NewState); - CurrentState->Exit(); + + CurrentState->Exit(GetCurrentStateContext()); CurrentState = NewState; - CurrentState->Enter(); + CurrentState->Enter(GetCurrentStateContext()); + TicksInState = 0; } @@ -77,11 +78,20 @@ FName UFFStateMachineComponent::GetCurrentStateName() const } -UFFState* UFFStateMachineComponent::FindStateWithName(FName StateName) +FFFStateContext UFFStateMachineComponent::GetCurrentStateContext() const { - for (UFFState* CurrState : States) + FFFStateContext CurrStateContext; + CurrStateContext.Owner = Owner; + CurrStateContext.Avatar = Avatar; + return CurrStateContext; +} + + +UFFStateBehavior* UFFStateMachineComponent::FindStateWithName(FName StateName) +{ + for (UFFStateBehavior* CurrState : States) { - if(CurrState ->Name == StateName) + if(CurrState->Name == StateName) { return CurrState; } @@ -106,7 +116,7 @@ void UFFStateMachineComponent::FixedTick(float OneFrame) { // Should we switch states? - for(UFFState* CurrState : States) + for(UFFStateBehavior* CurrState : States) { // Check if the state is enabled @@ -128,7 +138,8 @@ void UFFStateMachineComponent::FixedTick(float OneFrame) check(CurrentState); // Tick current state - CurrentState->Update(OneFrame); + TicksInState++; + CurrentState->Update(OneFrame, GetCurrentStateContext()); // Debug } diff --git a/Source/UnrealFightingFramework/State/FFStateMachineComponent.h b/Source/UnrealFightingFramework/State/FFStateMachineComponent.h index 2261c88..96f371d 100644 --- a/Source/UnrealFightingFramework/State/FFStateMachineComponent.h +++ b/Source/UnrealFightingFramework/State/FFStateMachineComponent.h @@ -47,14 +47,14 @@ public: * * @return A pointer to the state that was added or nullptr if there was an issue adding or creating the state */ - UFFState* AddState(TSubclassOf StateClassToAdd); + UFFStateBehavior* 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); + void AddStates(const TArray>& StateClassesToAdd); /** * Destroys the state with corresponding name and removes it from this state machine. @@ -66,7 +66,7 @@ public: * * Triggers the Exit callback on the CurrentState and the Enter callback on the new state */ - void SwitchStates(UFFState* NewState); + void SwitchStates(UFFStateBehavior* NewState); /** * Returns the name of the current state @@ -74,6 +74,11 @@ public: UFUNCTION(BlueprintPure) FName GetCurrentStateName() const; + /** + * + */ + FFFStateContext GetCurrentStateContext() const; + // IFFSystemInterface interface virtual void FixedTick(float OneFrame) override; // End of IFFSystemInterface interface @@ -93,24 +98,28 @@ protected: UPROPERTY(BlueprintReadOnly) AActor* Avatar; + /** How many ticks have elapsed since the currently active state was entered */ + UPROPERTY(BlueprintReadOnly, Category="UFF|State") + int32 TicksInState; + /** * States classes to create and add to this state machine when the game starts */ UPROPERTY(EditDefaultsOnly, Category="UFF|State Machine") - TArray> DefaultStates; + TArray> DefaultStates; /** Current active state for this state machine */ UPROPERTY(BlueprintReadOnly) - UFFState* CurrentState; + UFFStateBehavior* CurrentState; // States that have been added UPROPERTY(BlueprintReadOnly) - TArray States; + TArray States; /** * Returns the state with corresponding name */ - UFFState* FindStateWithName(FName StateName); + UFFStateBehavior* FindStateWithName(FName StateName); // UActorComponent interface virtual void BeginPlay() override;