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>();
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<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;
};
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);
};

View File

@ -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<AFFGameState>()->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
{

View File

@ -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; }