Expose state context members to blueprint and add Finish function to state

This commit is contained in:
Kevin Poretti 2023-07-22 22:10:00 -04:00
parent c652716895
commit 3938ac54b5
5 changed files with 130 additions and 72 deletions

View File

@ -82,7 +82,7 @@ struct FFFInputSequence
} }
}; };
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) UCLASS( ClassGroup=(UnrealFightingFramework), meta=(BlueprintSpawnableComponent) )
class UNREALFIGHTINGFRAMEWORK_API UFFInputBufferComponent : public UActorComponent class UNREALFIGHTINGFRAMEWORK_API UFFInputBufferComponent : public UActorComponent
{ {
GENERATED_BODY() GENERATED_BODY()

View File

@ -44,6 +44,17 @@ void UFFState::Exit(const FFFStateContext& InStateContext)
void UFFState::Update(float OneFrame, const FFFStateContext& InStateContext) void UFFState::Update(float OneFrame, const FFFStateContext& InStateContext)
{ {
OnUpdate(OneFrame, 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<UFFStateMachineComponent>(GetOuter());
if(SMC)
{
SMC->SetSubStateLabel(InSubStateLabel);
}
}
UWorld* UFFState::GetWorld() const UWorld* UFFState::GetWorld() const
{ {
UFFStateMachineComponent* SMC = Cast<UFFStateMachineComponent>(GetOuter()); UFFStateMachineComponent* SMC = Cast<UFFStateMachineComponent>(GetOuter());

View File

@ -17,21 +17,21 @@ struct FFFStateContext
GENERATED_BODY() GENERATED_BODY()
/** Actor that owns the avatar. Typically a player controller. */ /** Actor that owns the avatar. Typically a player controller. */
UPROPERTY(BlueprintReadOnly)
AActor* Owner; AActor* Owner;
/** /**
* Actor that represents the player's avatar or an object associated with the player's avatar. * Actor that represents the player's avatar or an object associated with the player's avatar.
* This is typically a character or a weapon. * This is typically a character or a weapon.
*/ */
UPROPERTY(BlueprintReadOnly)
AActor* Avatar; AActor* Avatar;
/** /**
* Parent state machine that controls this state * Parent state machine that controls this state
*/ */
const class UFFStateMachineComponent* Parent; UPROPERTY(BlueprintReadOnly)
class UFFStateMachineComponent* Parent;
/** The number of ticks that have elapsed since this state was entered into */
int64 TicksInState;
}; };
/** /**
@ -95,8 +95,6 @@ public:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties") UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties")
UAnimMontage* MontageToPlay; UAnimMontage* MontageToPlay;
/** /**
* Returns true if Avatar's is in the correct stance AND * 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 * 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") UFUNCTION(BlueprintNativeEvent, Category="UFF|State")
bool CanTransition(const FFFStateContext& InStateContext); 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. * Called whenever this state is transitioned into.
* *
@ -135,6 +127,22 @@ public:
*/ */
virtual void Update(float OneFrame, const FFFStateContext& InStateContext); 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 * Blueprint hook for whenever this state is transitioned into
*/ */

View File

@ -14,15 +14,18 @@ UFFStateMachineComponent::UFFStateMachineComponent()
void UFFStateMachineComponent::Initialize() void UFFStateMachineComponent::Initialize()
{ {
UFFState* EntryStateInstance = AddState(EntryState);
for(const TSubclassOf<UFFState>& CurrState : DefaultStates) for(const TSubclassOf<UFFState>& CurrState : DefaultStates)
{ {
UFFState* TempState = AddState(CurrState); AddState(CurrState);
if(!CurrentState) // first state to be created is the entry into this state machine
{
CurrentState = TempState;
CurrentState->Enter(GetCurrentStateContext());
}
} }
// need an entry state or something went seriously wrong
check(EntryStateInstance);
CurrentState = EntryStateInstance;
CurrentState->Enter(GetCurrentStateContext());
} }
@ -38,10 +41,20 @@ UFFState* UFFStateMachineComponent::AddState(TSubclassOf<UFFState> StateClassToA
UFFState* TempState = NewObject<UFFState>(this, StateClassToAdd); UFFState* TempState = NewObject<UFFState>(this, StateClassToAdd);
if(TempState) if(TempState)
{ {
States.Add(TempState); if(!FindStateWithName(TempState->Name))
return TempState; {
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; return nullptr;
} }
@ -72,6 +85,20 @@ void UFFStateMachineComponent::SwitchStates(UFFState* NewState)
CurrentSubStateLabel = NAME_None; 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 FName UFFStateMachineComponent::GetCurrentStateName() const
{ {
@ -81,14 +108,9 @@ FName UFFStateMachineComponent::GetCurrentStateName() const
} }
FFFStateContext UFFStateMachineComponent::GetCurrentStateContext() const FName UFFStateMachineComponent::GetCurrentSubStateLabel() const
{ {
FFFStateContext CurrStateContext; return CurrentSubStateLabel;
CurrStateContext.Owner = Owner;
CurrStateContext.Avatar = Avatar;
CurrStateContext.Parent = this;
CurrStateContext.TicksInState = TicksInState;
return CurrStateContext;
} }
@ -98,28 +120,13 @@ void UFFStateMachineComponent::SetSubStateLabel(FName InSubStateLabel)
} }
UFFState* UFFStateMachineComponent::FindStateWithName(FName StateName) FFFStateContext UFFStateMachineComponent::GetCurrentStateContext()
{ {
for (UFFState* CurrState : States) FFFStateContext CurrStateContext;
{ CurrStateContext.Owner = Owner;
if(CurrState->Name == StateName) CurrStateContext.Avatar = Avatar;
{ CurrStateContext.Parent = this;
return CurrState; return CurrStateContext;
}
}
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();
} }
@ -158,3 +165,27 @@ void UFFStateMachineComponent::FixedTick(float OneFrame)
// Debug // 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;
}

View File

@ -18,7 +18,7 @@
* *
* This component also calls the appropriate state logic when a state is changed or the component ticks. * 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 class UNREALFIGHTINGFRAMEWORK_API UFFStateMachineComponent : public UActorComponent, public IFFSystemInterface
{ {
GENERATED_BODY() GENERATED_BODY()
@ -69,18 +69,30 @@ public:
*/ */
void SwitchStates(UFFState* NewState); 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 * Returns the name of the current state
*/ */
UFUNCTION(BlueprintPure) 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; FFFStateContext GetCurrentStateContext();
void SetSubStateLabel(FName InSubStateLabel);
// IFFSystemInterface interface // IFFSystemInterface interface
virtual void FixedTick(float OneFrame) override; virtual void FixedTick(float OneFrame) override;
@ -91,36 +103,42 @@ protected:
* Actor that owns this state machine. * Actor that owns this state machine.
* This will typically be a player controller that possesses the avatar. * This will typically be a player controller that possesses the avatar.
*/ */
UPROPERTY(BlueprintReadOnly) UPROPERTY()
AActor* Owner; AActor* Owner;
/** /**
* The avatar is an actor that this state machine represents. * The avatar is an actor that this state machine represents.
* This will typically be a pawn or character the state machine is attached to. * This will typically be a pawn or character the state machine is attached to.
*/ */
UPROPERTY(BlueprintReadOnly) UPROPERTY()
AActor* Avatar; AActor* Avatar;
/** How many ticks have elapsed since the currently active state was entered */ /** How many ticks have elapsed since the currently active state was entered */
UPROPERTY(BlueprintReadOnly, Category="UFF|State")
int64 TicksInState; 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<UFFState> 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") UPROPERTY(EditDefaultsOnly, Category="UFF|State Machine")
TArray<TSubclassOf<UFFState>> DefaultStates; TArray<TSubclassOf<UFFState>> DefaultStates;
/** Current active state for this state machine */ /** Current active state for this state machine */
UPROPERTY(BlueprintReadOnly) UPROPERTY()
UFFState* CurrentState; UFFState* CurrentState;
/** Current SubState label or NAME_None if there is no SubState label set for the current state*/ /** Current SubState label or NAME_None if there is no SubState label set for the current state*/
UPROPERTY(BlueprintReadOnly)
FName CurrentSubStateLabel; FName CurrentSubStateLabel;
// States that have been added // States that have been added
UPROPERTY(BlueprintReadOnly) UPROPERTY()
TArray<UFFState*> States; TArray<UFFState*> States;
/** /**