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
{
GENERATED_BODY()

View File

@ -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<UFFStateMachineComponent>(GetOuter());
if(SMC)
{
SMC->SetSubStateLabel(InSubStateLabel);
}
}
UWorld* UFFState::GetWorld() const
{
UFFStateMachineComponent* SMC = Cast<UFFStateMachineComponent>(GetOuter());

View File

@ -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
*/

View File

@ -14,15 +14,18 @@ UFFStateMachineComponent::UFFStateMachineComponent()
void UFFStateMachineComponent::Initialize()
{
UFFState* EntryStateInstance = AddState(EntryState);
for(const TSubclassOf<UFFState>& 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<UFFState> StateClassToA
UFFState* TempState = NewObject<UFFState>(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;
}

View File

@ -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<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")
TArray<TSubclassOf<UFFState>> 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<UFFState*> States;
/**