Options for when to stop an anim montage when the state ends

This commit is contained in:
Kevin Poretti 2023-10-30 18:51:56 -04:00
parent d9ec367b82
commit d53fba996a
4 changed files with 78 additions and 26 deletions

View File

@ -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<USkeletalMeshComponent>(); StopCurrentAnimMontage(InStateContext);
if(SMC && MontageToPlay)
{
FAlphaBlendArgs BlendOutArgs = MontageToPlay->GetBlendOutArgs();
SMC->GetAnimInstance()->Montage_Stop(BlendOutArgs.BlendTime, MontageToPlay);
}
} }
OnExit(InStateContext); OnExit(InStateContext);
@ -115,7 +114,7 @@ void UFFState::Update(float OneFrame, const FFFStateContext& InStateContext)
if(bStateHasDuration && InStateContext.Parent->GetTicksInState() >= StateDuration) 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) || if((ReqMovementMode != EMovementMode::MOVE_None && NewMovementMode != ReqMovementMode) ||
((ReqMovementMode == MOVE_Custom) && NewCustomMode != RequiredCustomMode)) ((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) 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); OnPlayMontage(InStateContext);
} }
@ -189,7 +195,7 @@ bool UFFState::OnCanTransition_Implementation(const FFFStateContext& InStateCont
return true; 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) void UFFState::OnPlayMontage_Implementation(const FFFStateContext& InStateContext)
{ {
if(InStateContext.Avatar) if(InStateContext.Avatar)
@ -213,3 +219,22 @@ UWorld* UFFState::GetWorld() const
return nullptr; return nullptr;
} }
void UFFState::StopCurrentAnimMontage(const FFFStateContext& InStateContext)
{
USkeletalMeshComponent* SMC = InStateContext.Avatar->GetComponentByClass<USkeletalMeshComponent>();
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());
}
}
}

View File

@ -49,6 +49,17 @@ struct FFFInputEventHandler
FFFInputEventDelegate Delegate; 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 * 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. * and logic to run when the state is entered, exited, and active.
@ -120,6 +131,12 @@ public:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties") UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties")
UAnimMontage* MontageToPlay; 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 * 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. * Called whenever this state is transitioned out of into a new state.
* *
* Calls appropriate Blueprint hooks. * 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. * Called whenever this state is active and the game logic ticks.
@ -191,7 +210,7 @@ public:
* @param InStateContext * @param InStateContext
*/ */
UFUNCTION(BlueprintCallable) UFUNCTION(BlueprintCallable)
virtual void Finish(const FFFStateContext& InStateContext); virtual void Finish(const FFFStateContext& InStateContext, EFFStateFinishReason StateFinishReason);
// TODO: document // TODO: document
UFUNCTION(BlueprintCallable) UFUNCTION(BlueprintCallable)
@ -260,4 +279,7 @@ public:
// UObject interface // UObject interface
virtual UWorld* GetWorld() const override; virtual UWorld* GetWorld() const override;
// End of UObject interface // End of UObject interface
private:
void StopCurrentAnimMontage(const FFFStateContext& InStateContext);
}; };

View File

@ -89,16 +89,16 @@ void UFFStateMachineComponent::RemoveState(FName StateToRemove)
UE_LOG(LogTemp, Error, TEXT("UFFStateMachineComponent::RemoveState is not yet implemented")); 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); UFFState* NewState = FindStateWithName(NewStateName);
if(NewState) if(NewState)
{ {
GoToState(NewState); GoToState(NewState, StateFinishReason);
} }
} }
void UFFStateMachineComponent::GoToState(UFFState* NewState) void UFFStateMachineComponent::GoToState(UFFState* NewState, EFFStateFinishReason StateFinishReason)
{ {
check(CurrentState); check(CurrentState);
check(NewState); check(NewState);
@ -115,8 +115,7 @@ void UFFStateMachineComponent::GoToState(UFFState* NewState)
} }
} }
CurrentState->Exit(GetCurrentStateContext(), StateFinishReason);
CurrentState->Exit(GetCurrentStateContext());
TicksInState = 0; TicksInState = 0;
TickStateWasEntered = GetWorld()->GetGameState<AFFGameState>()->GetCurrentTick(); TickStateWasEntered = GetWorld()->GetGameState<AFFGameState>()->GetCurrentTick();
CurrentSubStateLabel = NAME_None; CurrentSubStateLabel = NAME_None;
@ -125,12 +124,12 @@ void UFFStateMachineComponent::GoToState(UFFState* NewState)
CurrentState = NewState; CurrentState = NewState;
} }
void UFFStateMachineComponent::GoToEntryState() void UFFStateMachineComponent::GoToEntryState(EFFStateFinishReason StateFinishReason)
{ {
// can't have an entry state if there are no states // can't have an entry state if there are no states
check(States.Num() > 0); 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 // It is OK to transition if state's "CanTransitionToSelf" is true
if(StateToTransitionTo) if(StateToTransitionTo)
{ {
GoToState(StateToTransitionTo); GoToState(StateToTransitionTo, EFFStateFinishReason::SFT_Interrupted);
} }
else else
{ {

View File

@ -72,20 +72,26 @@ public:
* Transitions from CurrentState to the new state with the name passed to this function * 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 * 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 * 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 * 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 * Transitions from CurrentState to the default entry state
*
* TODO: document StateFinishReason
*/ */
void GoToEntryState(); void GoToEntryState(EFFStateFinishReason StateFinishReason);
UFUNCTION(BlueprintPure) UFUNCTION(BlueprintPure)
FORCEINLINE int64 GetTicksInState() const { return TicksInState; } FORCEINLINE int64 GetTicksInState() const { return TicksInState; }