From 3938ac54b55bad99cb5bb16ef710ae792af41edf Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Sat, 22 Jul 2023 22:10:00 -0400 Subject: [PATCH] Expose state context members to blueprint and add Finish function to state --- .../Input/FFInputBufferComponent.h | 2 +- .../UnrealFightingFramework/State/FFState.cpp | 21 ++-- .../UnrealFightingFramework/State/FFState.h | 32 ++++-- .../State/FFStateMachineComponent.cpp | 103 ++++++++++++------ .../State/FFStateMachineComponent.h | 44 +++++--- 5 files changed, 130 insertions(+), 72 deletions(-) diff --git a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h index c9121f4..0a720d0 100644 --- a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h +++ b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h @@ -82,7 +82,7 @@ struct FFFInputSequence } }; -UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) +UCLASS( ClassGroup=(UnrealFightingFramework), meta=(BlueprintSpawnableComponent) ) class UNREALFIGHTINGFRAMEWORK_API UFFInputBufferComponent : public UActorComponent { GENERATED_BODY() diff --git a/Source/UnrealFightingFramework/State/FFState.cpp b/Source/UnrealFightingFramework/State/FFState.cpp index a8491aa..6e8e2ad 100644 --- a/Source/UnrealFightingFramework/State/FFState.cpp +++ b/Source/UnrealFightingFramework/State/FFState.cpp @@ -44,6 +44,17 @@ void UFFState::Exit(const FFFStateContext& InStateContext) void UFFState::Update(float OneFrame, const FFFStateContext& InStateContext) { OnUpdate(OneFrame, InStateContext); + + if(InStateContext.Parent->GetTicksInState() >= StateDuration) + { + Finish(InStateContext); + } +} + + +void UFFState::Finish(const FFFStateContext& InStateContext) +{ + InStateContext.Parent->SwitchToEntryState(); } @@ -84,16 +95,6 @@ bool UFFState::CanTransition_Implementation(const FFFStateContext& InStateContex } -void UFFState::GotoSubState(FName InSubStateLabel) -{ - UFFStateMachineComponent* SMC = Cast(GetOuter()); - if(SMC) - { - SMC->SetSubStateLabel(InSubStateLabel); - } -} - - UWorld* UFFState::GetWorld() const { UFFStateMachineComponent* SMC = Cast(GetOuter()); diff --git a/Source/UnrealFightingFramework/State/FFState.h b/Source/UnrealFightingFramework/State/FFState.h index 63541f9..78c29b8 100644 --- a/Source/UnrealFightingFramework/State/FFState.h +++ b/Source/UnrealFightingFramework/State/FFState.h @@ -17,21 +17,21 @@ struct FFFStateContext GENERATED_BODY() /** Actor that owns the avatar. Typically a player controller. */ + UPROPERTY(BlueprintReadOnly) 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. */ + UPROPERTY(BlueprintReadOnly) AActor* Avatar; /** * Parent state machine that controls this state */ - const class UFFStateMachineComponent* Parent; - - /** The number of ticks that have elapsed since this state was entered into */ - int64 TicksInState; + UPROPERTY(BlueprintReadOnly) + class UFFStateMachineComponent* Parent; }; /** @@ -95,8 +95,6 @@ public: UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties") UAnimMontage* MontageToPlay; - - /** * Returns true if Avatar's is in the correct stance AND * the state type is enabled (or the state can be hit or whiff cancelled from the current state) AND @@ -106,12 +104,6 @@ public: UFUNCTION(BlueprintNativeEvent, Category="UFF|State") bool CanTransition(const FFFStateContext& InStateContext); - /** - * Sets the current SubState label on the state machine that controls this - */ - UFUNCTION(BlueprintCallable, Category="UFF|State") - void GotoSubState(FName InSubStateLabel); - /** * Called whenever this state is transitioned into. * @@ -135,6 +127,22 @@ public: */ virtual void Update(float OneFrame, const FFFStateContext& InStateContext); + /** + * Called when you want to exit from this state but no eligible transitions exist to other states, + * usually due to a lack of player input. + * + * By default this will transition to the owning state machine's entry state, which is usually + * an idle state. + * + * This function will get called automatically when the ticks in this state reaches the + * threshold set by the StateDuration member, but you can call it whenever you wish to return + * to the entry state. + * + * @param InStateContext + */ + UFUNCTION(BlueprintCallable) + virtual void Finish(const FFFStateContext& InStateContext); + /** * Blueprint hook for whenever this state is transitioned into */ diff --git a/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp b/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp index 5b8c77c..2deab97 100644 --- a/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp +++ b/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp @@ -14,15 +14,18 @@ UFFStateMachineComponent::UFFStateMachineComponent() void UFFStateMachineComponent::Initialize() { + UFFState* EntryStateInstance = AddState(EntryState); + for(const TSubclassOf& CurrState : DefaultStates) { - UFFState* TempState = AddState(CurrState); - if(!CurrentState) // first state to be created is the entry into this state machine - { - CurrentState = TempState; - CurrentState->Enter(GetCurrentStateContext()); - } + AddState(CurrState); } + + // need an entry state or something went seriously wrong + check(EntryStateInstance); + + CurrentState = EntryStateInstance; + CurrentState->Enter(GetCurrentStateContext()); } @@ -38,10 +41,20 @@ UFFState* UFFStateMachineComponent::AddState(TSubclassOf StateClassToA UFFState* TempState = NewObject(this, StateClassToAdd); if(TempState) { - States.Add(TempState); - return TempState; + if(!FindStateWithName(TempState->Name)) + { + States.Add(TempState); + return TempState; + } + + UE_LOG(LogTemp, Error, TEXT("State with name %s already exists in state machine for %s"), + *TempState->Name.ToString(), *GetOwner()->GetFName().ToString()); + return nullptr; } + UE_LOG(LogTemp, Error, TEXT("Could not create instance of state class %s"), + *StateClassToAdd.GetDefaultObject()->Name.ToString()); + return nullptr; } @@ -72,6 +85,20 @@ void UFFStateMachineComponent::SwitchStates(UFFState* NewState) CurrentSubStateLabel = NAME_None; } +void UFFStateMachineComponent::SwitchToEntryState() +{ + // can't have an entry state if there are no states + check(States.Num() > 0); + + SwitchStates(States[0]); +} + + +int64 UFFStateMachineComponent::GetTicksInState() const +{ + return TicksInState; +} + FName UFFStateMachineComponent::GetCurrentStateName() const { @@ -81,14 +108,9 @@ FName UFFStateMachineComponent::GetCurrentStateName() const } -FFFStateContext UFFStateMachineComponent::GetCurrentStateContext() const +FName UFFStateMachineComponent::GetCurrentSubStateLabel() const { - FFFStateContext CurrStateContext; - CurrStateContext.Owner = Owner; - CurrStateContext.Avatar = Avatar; - CurrStateContext.Parent = this; - CurrStateContext.TicksInState = TicksInState; - return CurrStateContext; + return CurrentSubStateLabel; } @@ -98,28 +120,13 @@ void UFFStateMachineComponent::SetSubStateLabel(FName InSubStateLabel) } -UFFState* UFFStateMachineComponent::FindStateWithName(FName StateName) +FFFStateContext UFFStateMachineComponent::GetCurrentStateContext() { - for (UFFState* 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 UFFStateMachineComponent::BeginPlay() -{ - Super::BeginPlay(); - - Initialize(); + FFFStateContext CurrStateContext; + CurrStateContext.Owner = Owner; + CurrStateContext.Avatar = Avatar; + CurrStateContext.Parent = this; + return CurrStateContext; } @@ -158,3 +165,27 @@ void UFFStateMachineComponent::FixedTick(float OneFrame) // Debug } + +void UFFStateMachineComponent::BeginPlay() +{ + Super::BeginPlay(); + + Initialize(); +} + + +UFFState* UFFStateMachineComponent::FindStateWithName(FName StateName) +{ + for (UFFState* 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; +} \ No newline at end of file diff --git a/Source/UnrealFightingFramework/State/FFStateMachineComponent.h b/Source/UnrealFightingFramework/State/FFStateMachineComponent.h index 9597c54..4b553a3 100644 --- a/Source/UnrealFightingFramework/State/FFStateMachineComponent.h +++ b/Source/UnrealFightingFramework/State/FFStateMachineComponent.h @@ -18,7 +18,7 @@ * * This component also calls the appropriate state logic when a state is changed or the component ticks. */ -UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) +UCLASS( ClassGroup=(UnrealFightingFramework), meta=(BlueprintSpawnableComponent) ) class UNREALFIGHTINGFRAMEWORK_API UFFStateMachineComponent : public UActorComponent, public IFFSystemInterface { GENERATED_BODY() @@ -69,18 +69,30 @@ public: */ void SwitchStates(UFFState* NewState); + /** + * Transitions from CurrentState to the default entry state + */ + void SwitchToEntryState(); + + UFUNCTION(BlueprintPure) + FORCEINLINE int64 GetTicksInState() const; + /** * Returns the name of the current state */ UFUNCTION(BlueprintPure) - FName GetCurrentStateName() const; + FORCEINLINE FName GetCurrentStateName() const; - /** + UFUNCTION(BlueprintPure) + FORCEINLINE FName GetCurrentSubStateLabel() const; + + UFUNCTION(BlueprintCallable) + FORCEINLINE void SetSubStateLabel(FName InSubStateLabel); + + /** * */ - FFFStateContext GetCurrentStateContext() const; - - void SetSubStateLabel(FName InSubStateLabel); + FFFStateContext GetCurrentStateContext(); // IFFSystemInterface interface virtual void FixedTick(float OneFrame) override; @@ -91,36 +103,42 @@ protected: * Actor that owns this state machine. * This will typically be a player controller that possesses the avatar. */ - UPROPERTY(BlueprintReadOnly) + UPROPERTY() 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) + UPROPERTY() AActor* Avatar; /** How many ticks have elapsed since the currently active state was entered */ - UPROPERTY(BlueprintReadOnly, Category="UFF|State") int64 TicksInState; /** - * States classes to create and add to this state machine when the game starts + * The state the state machine will enter when the game begins and the state that will be + * transitioned into if a state finishes and no other states are eligible for transition. + */ + UPROPERTY(EditDefaultsOnly, Category="UFF|State Machine") + TSubclassOf EntryState; + + /** + * States classes other than the entry state to create and add to this state machine when the + * game starts. */ UPROPERTY(EditDefaultsOnly, Category="UFF|State Machine") TArray> DefaultStates; /** Current active state for this state machine */ - UPROPERTY(BlueprintReadOnly) + UPROPERTY() UFFState* CurrentState; /** Current SubState label or NAME_None if there is no SubState label set for the current state*/ - UPROPERTY(BlueprintReadOnly) FName CurrentSubStateLabel; // States that have been added - UPROPERTY(BlueprintReadOnly) + UPROPERTY() TArray States; /**