From d53fba996a99d2b918e06a7fdf342fe206e0f901 Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Mon, 30 Oct 2023 18:51:56 -0400 Subject: [PATCH] Options for when to stop an anim montage when the state ends --- .../UnrealFightingFramework/State/FFState.cpp | 51 ++++++++++++++----- .../UnrealFightingFramework/State/FFState.h | 26 +++++++++- .../State/FFStateMachineComponent.cpp | 15 +++--- .../State/FFStateMachineComponent.h | 12 +++-- 4 files changed, 78 insertions(+), 26 deletions(-) diff --git a/Source/UnrealFightingFramework/State/FFState.cpp b/Source/UnrealFightingFramework/State/FFState.cpp index 3afcf08..2693143 100644 --- a/Source/UnrealFightingFramework/State/FFState.cpp +++ b/Source/UnrealFightingFramework/State/FFState.cpp @@ -69,16 +69,15 @@ void UFFState::Enter(const FFFStateContext& InStateContext) } -void UFFState::Exit(const FFFStateContext& InStateContext) +void UFFState::Exit(const FFFStateContext& InStateContext, EFFStateFinishReason StateFinishReason) { - if(InStateContext.Avatar) + + + if(InStateContext.Avatar && + (bStopMontageOnStateEnd && StateFinishReason == EFFStateFinishReason::SFT_DurationMetOrExceeded || StateFinishReason == EFFStateFinishReason::SFT_Interrupted) || + (bStopMontageOnMovementModeChange && StateFinishReason == EFFStateFinishReason::SFT_NotInReqMovementMode)) { - USkeletalMeshComponent* SMC = InStateContext.Avatar->GetComponentByClass(); - if(SMC && MontageToPlay) - { - FAlphaBlendArgs BlendOutArgs = MontageToPlay->GetBlendOutArgs(); - SMC->GetAnimInstance()->Montage_Stop(BlendOutArgs.BlendTime, MontageToPlay); - } + StopCurrentAnimMontage(InStateContext); } OnExit(InStateContext); @@ -115,7 +114,7 @@ void UFFState::Update(float OneFrame, const FFFStateContext& InStateContext) if(bStateHasDuration && InStateContext.Parent->GetTicksInState() >= StateDuration) { - Finish(InStateContext); + Finish(InStateContext, EFFStateFinishReason::SFT_DurationMetOrExceeded); } } @@ -142,7 +141,7 @@ void UFFState::MovementModeChanged(EMovementMode PrevMovementMode, uint8 Previou if((ReqMovementMode != EMovementMode::MOVE_None && NewMovementMode != ReqMovementMode) || ((ReqMovementMode == MOVE_Custom) && NewCustomMode != RequiredCustomMode)) { - Finish(InStateContext); + Finish(InStateContext, EFFStateFinishReason::SFT_NotInReqMovementMode); } } @@ -159,9 +158,15 @@ void UFFState::Block(const FFFStateContext& InStateContext) } -void UFFState::Finish(const FFFStateContext& InStateContext) +void UFFState::Finish(const FFFStateContext& InStateContext, EFFStateFinishReason StateFinishReason) { - InStateContext.Parent->GoToEntryState(); + // TODO: I really don't like having to pass this state finish reason into this GoToEntryState + // function, which then passes it to GoToState, which then passes it back to the state through + // it's "Exit" function all so we can stop the current anim montage when we exit from a state if + // the appropriate flags are set. I think having this state finish reason is good but I may want + // to rethink the way we handle logic for ending a state and which class is in charge of handling + // what + InStateContext.Parent->GoToEntryState(StateFinishReason); } @@ -175,6 +180,7 @@ void UFFState::RegisterInputHandler(const FFFInputSequence& InRequiredSequence, void UFFState::PlayMontage(const FFFStateContext& InStateContext) { + // TODO: think of a better way to handle optionally playing montages other than the one set as MontageToPlay OnPlayMontage(InStateContext); } @@ -189,7 +195,7 @@ bool UFFState::OnCanTransition_Implementation(const FFFStateContext& InStateCont return true; } - +// TODO: think of a better way to handle optionally playing montages other than the one set as MontageToPlay void UFFState::OnPlayMontage_Implementation(const FFFStateContext& InStateContext) { if(InStateContext.Avatar) @@ -213,3 +219,22 @@ UWorld* UFFState::GetWorld() const return nullptr; } + + +void UFFState::StopCurrentAnimMontage(const FFFStateContext& InStateContext) +{ + USkeletalMeshComponent* SMC = InStateContext.Avatar->GetComponentByClass(); + if(SMC) + { + // TODO: Need a system for keeping track what anim montages were played during a state + // so we can stop only those when the state ends (and if the bStopMontageOnStateEnd flag is set) + // ALSO, this will be important for when we need to fast forward an animation when the game rollsback + // and we have to resimulate to the current local tick + // For now just stop all anim montages + UAnimMontage* CurrAnim = SMC->GetAnimInstance()->GetCurrentActiveMontage(); + if(CurrAnim) + { + SMC->GetAnimInstance()->Montage_Stop(CurrAnim->BlendOut.GetBlendTime()); + } + } +} diff --git a/Source/UnrealFightingFramework/State/FFState.h b/Source/UnrealFightingFramework/State/FFState.h index 693d4ae..8a80d93 100644 --- a/Source/UnrealFightingFramework/State/FFState.h +++ b/Source/UnrealFightingFramework/State/FFState.h @@ -49,6 +49,17 @@ struct FFFInputEventHandler FFFInputEventDelegate Delegate; }; +UENUM(BlueprintType) +enum class EFFStateFinishReason : uint8 +{ + // TODO: document + SFT_Interrupted UMETA(DisplayName="Interrupted"), + SFT_DurationMetOrExceeded UMETA(DisplayName="Duration Reached"), + SFT_NotInReqMovementMode UMETA(DisplayName="Not In Required Movement Mode"), + SFT_MAX UMETA(Hidden) +}; + + /** * 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. @@ -120,6 +131,12 @@ public: UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties") UAnimMontage* MontageToPlay; + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties") + bool bStopMontageOnStateEnd; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties") + bool bStopMontageOnMovementModeChange; + /** * Event delegates to call when a certain input condition is detected in the Owner's input buffer */ @@ -150,8 +167,10 @@ public: * Called whenever this state is transitioned out of into a new state. * * Calls appropriate Blueprint hooks. + * + * TODO: document StateFinishReason */ - virtual void Exit(const FFFStateContext& InStateContext); + virtual void Exit(const FFFStateContext& InStateContext, EFFStateFinishReason StateFinishReason); /** * Called whenever this state is active and the game logic ticks. @@ -191,7 +210,7 @@ public: * @param InStateContext */ UFUNCTION(BlueprintCallable) - virtual void Finish(const FFFStateContext& InStateContext); + virtual void Finish(const FFFStateContext& InStateContext, EFFStateFinishReason StateFinishReason); // TODO: document UFUNCTION(BlueprintCallable) @@ -260,4 +279,7 @@ public: // UObject interface virtual UWorld* GetWorld() const override; // End of UObject interface + +private: + void StopCurrentAnimMontage(const FFFStateContext& InStateContext); }; diff --git a/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp b/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp index e98b05a..120c8cf 100644 --- a/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp +++ b/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp @@ -89,16 +89,16 @@ void UFFStateMachineComponent::RemoveState(FName StateToRemove) UE_LOG(LogTemp, Error, TEXT("UFFStateMachineComponent::RemoveState is not yet implemented")); } -void UFFStateMachineComponent::GoToState(FName NewStateName) +void UFFStateMachineComponent::GoToState(FName NewStateName, EFFStateFinishReason StateFinishReason) { UFFState* NewState = FindStateWithName(NewStateName); if(NewState) { - GoToState(NewState); + GoToState(NewState, StateFinishReason); } } -void UFFStateMachineComponent::GoToState(UFFState* NewState) +void UFFStateMachineComponent::GoToState(UFFState* NewState, EFFStateFinishReason StateFinishReason) { check(CurrentState); check(NewState); @@ -115,8 +115,7 @@ void UFFStateMachineComponent::GoToState(UFFState* NewState) } } - - CurrentState->Exit(GetCurrentStateContext()); + CurrentState->Exit(GetCurrentStateContext(), StateFinishReason); TicksInState = 0; TickStateWasEntered = GetWorld()->GetGameState()->GetCurrentTick(); CurrentSubStateLabel = NAME_None; @@ -125,12 +124,12 @@ void UFFStateMachineComponent::GoToState(UFFState* NewState) CurrentState = NewState; } -void UFFStateMachineComponent::GoToEntryState() +void UFFStateMachineComponent::GoToEntryState(EFFStateFinishReason StateFinishReason) { // can't have an entry state if there are no states check(States.Num() > 0); - GoToState(States[0]); + GoToState(States[0], StateFinishReason); } @@ -213,7 +212,7 @@ void UFFStateMachineComponent::FixedTick(float OneFrame) // It is OK to transition if state's "CanTransitionToSelf" is true if(StateToTransitionTo) { - GoToState(StateToTransitionTo); + GoToState(StateToTransitionTo, EFFStateFinishReason::SFT_Interrupted); } else { diff --git a/Source/UnrealFightingFramework/State/FFStateMachineComponent.h b/Source/UnrealFightingFramework/State/FFStateMachineComponent.h index d6aae42..2c0c84d 100644 --- a/Source/UnrealFightingFramework/State/FFStateMachineComponent.h +++ b/Source/UnrealFightingFramework/State/FFStateMachineComponent.h @@ -72,20 +72,26 @@ public: * Transitions from CurrentState to the new state with the name passed to this function * * Triggers the Exit callback on the CurrentState and the Enter callback on the new state + * + * TODO: document StateFinishReason */ - void GoToState(FName NewStateName); + void GoToState(FName NewStateName, EFFStateFinishReason StateFinishReason); /** * 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 + * + * TODO: document StateFinishReason */ - void GoToState(UFFState* NewState); + void GoToState(UFFState* NewState, EFFStateFinishReason StateFinishReason); /** * Transitions from CurrentState to the default entry state + * + * TODO: document StateFinishReason */ - void GoToEntryState(); + void GoToEntryState(EFFStateFinishReason StateFinishReason); UFUNCTION(BlueprintPure) FORCEINLINE int64 GetTicksInState() const { return TicksInState; }