Implement input sequence and landed events

This commit is contained in:
Kevin Poretti 2023-08-05 14:58:08 -04:00
parent d89face881
commit 08d52a913f
8 changed files with 151 additions and 12 deletions

View File

@ -51,11 +51,11 @@ struct FFFInputCondition
GENERATED_BODY() GENERATED_BODY()
// Buttons required for this specific condition to be valid // Buttons required for this specific condition to be valid
UPROPERTY(EditAnywhere) UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 RequiredButtons; int32 RequiredButtons;
// The button state required for condition to be valid i.e. pressed or released // The button state required for condition to be valid i.e. pressed or released
UPROPERTY(EditAnywhere) UPROPERTY(EditAnywhere, BlueprintReadWrite)
EFFButtonState RequiredButtonState; EFFButtonState RequiredButtonState;
FFFInputCondition() FFFInputCondition()
@ -70,10 +70,10 @@ struct FFFInputSequence
{ {
GENERATED_BODY() GENERATED_BODY()
UPROPERTY(EditAnywhere) UPROPERTY(EditAnywhere, BlueprintReadWrite)
TArray<FFFInputCondition> Sequence; TArray<FFFInputCondition> Sequence;
UPROPERTY(EditAnywhere) UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 MaxDuration; int32 MaxDuration;
FFFInputSequence() FFFInputSequence()

View File

@ -32,6 +32,11 @@ void AFFPlayerController::FixedTick(float OneFrame)
//SendInputsToRemote(); //SendInputsToRemote();
} }
bool AFFPlayerController::CheckInputSequence(const FFFInputSequence& InInputSequence)
{
return InputBuffer->CheckInputSequence(InInputSequence);
}
bool AFFPlayerController::CheckInputSequences(const TArray<FFFInputSequence>& InputSequences) bool AFFPlayerController::CheckInputSequences(const TArray<FFFInputSequence>& InputSequences)
{ {

View File

@ -52,6 +52,8 @@ public:
// End of IFFSystemInterface // End of IFFSystemInterface
// IFFStateOwnerInterface // IFFStateOwnerInterface
virtual bool CheckInputSequence(const FFFInputSequence& InInputSequence) override;
virtual bool CheckInputSequences(const TArray<FFFInputSequence>& InputSequences) override; virtual bool CheckInputSequences(const TArray<FFFInputSequence>& InputSequences) override;
// End of IFFStateOwnerInterface // End of IFFStateOwnerInterface

View File

@ -10,6 +10,11 @@
// UE includes // UE includes
#include "Components/SkeletalMeshComponent.h" #include "Components/SkeletalMeshComponent.h"
void UFFState::Init()
{
OnInit();
}
bool UFFState::CanTransition(const FFFStateContext& InStateContext) bool UFFState::CanTransition(const FFFStateContext& InStateContext)
{ {
/** /**
@ -17,12 +22,11 @@ bool UFFState::CanTransition(const FFFStateContext& InStateContext)
* if so then * if so then
* Check if the avatar is in the correct stance to perform this action * Check if the avatar is in the correct stance to perform this action
* Check if the state is enabled * Check if the state is enabled
* Check that all state entry conditions are met if there are any
* *
* Check to see if the owner implements the StateOwnerInterface * Check to see if the owner implements the StateOwnerInterface
* Check input conditions if there are any * Check input conditions if there are any
* *
* If all state entry conditions are good and at least one input condition is good then we can transition * If at least one input condition is good then we can transition
* so return true otherwise return false * so return true otherwise return false
*/ */
IFFStateAvatarInterface* SAI = Cast<IFFStateAvatarInterface>(InStateContext.Avatar); IFFStateAvatarInterface* SAI = Cast<IFFStateAvatarInterface>(InStateContext.Avatar);
@ -80,6 +84,30 @@ void UFFState::Exit(const FFFStateContext& InStateContext)
void UFFState::Update(float OneFrame, const FFFStateContext& InStateContext) void UFFState::Update(float OneFrame, const FFFStateContext& InStateContext)
{ {
IFFStateOwnerInterface* SOI = Cast<IFFStateOwnerInterface>(InStateContext.Owner);
// TODO: maybe a check/asset is better because I can't think of a reason that not having the Owner implement
// IFFStateOwnerInterface is valid. Although there could be objects that have state that don't necessarily directly
// respond to input and don't have an Owner/controller responsible for them. Or they do have an owner but are associated
// with another Avatar like a character who is owned by a player/player controller.
if(!SOI)
{
UE_LOG(LogTemp, Error, TEXT("Owner of FFFStateContext does not implement IFFStateOwnerInterface"));
return;
}
for(FFFInputEventHandler InputHandler : InputHandlers)
{
if(SOI->CheckInputSequence(InputHandler.RequiredSequence))
{
if(InputHandler.Delegate.ExecuteIfBound(InStateContext))
{
UE_LOG(LogTemp, Error,
TEXT("Trying to execute an input handler delegate on %s but it is not bound"),
*Name.ToString());
}
}
}
OnUpdate(OneFrame, InStateContext); OnUpdate(OneFrame, InStateContext);
if(bStateHasDuration && InStateContext.Parent->GetTicksInState() >= StateDuration) if(bStateHasDuration && InStateContext.Parent->GetTicksInState() >= StateDuration)
@ -89,18 +117,60 @@ void UFFState::Update(float OneFrame, const FFFStateContext& InStateContext)
} }
void UFFState::Landed(const FHitResult& Hit, const FFFStateContext& InStateContext)
{
OnLanded(Hit, InStateContext);
// TODO: might want to also finish the state here by default and have the subclasses overwrite
// that behavior in the few cases we don't want to finish the state when we land
}
void UFFState::Hit(const FFFStateContext& InStateContext)
{
OnHit(InStateContext);
}
void UFFState::Block(const FFFStateContext& InStateContext)
{
OnBlock(InStateContext);
}
void UFFState::Finish(const FFFStateContext& InStateContext) void UFFState::Finish(const FFFStateContext& InStateContext)
{ {
InStateContext.Parent->GoToEntryState(); InStateContext.Parent->GoToEntryState();
} }
void UFFState::RegisterInputHandler(const FFFInputSequence& InRequiredSequence, FFFInputEventDelegate InDelegate)
{
FFFInputEventHandler TempHandler;
TempHandler.RequiredSequence = InRequiredSequence;
TempHandler.Delegate = InDelegate;
InputHandlers.Add(TempHandler);
}
void UFFState::OnInit_Implementation()
{
}
bool UFFState::OnCanTransition_Implementation(const FFFStateContext& InStateContext) bool UFFState::OnCanTransition_Implementation(const FFFStateContext& InStateContext)
{ {
return true; return true;
} }
void UFFState::PostInitProperties()
{
UObject::PostInitProperties();
Init();
}
UWorld* UFFState::GetWorld() const UWorld* UFFState::GetWorld() const
{ {
UFFStateMachineComponent* SMC = Cast<UFFStateMachineComponent>(GetOuter()); UFFStateMachineComponent* SMC = Cast<UFFStateMachineComponent>(GetOuter());

View File

@ -10,7 +10,6 @@
#include "FFState.generated.h" #include "FFState.generated.h"
USTRUCT(BlueprintType) USTRUCT(BlueprintType)
struct FFFStateContext struct FFFStateContext
{ {
@ -34,6 +33,22 @@ struct FFFStateContext
class UFFStateMachineComponent* Parent; class UFFStateMachineComponent* Parent;
}; };
DECLARE_DYNAMIC_DELEGATE_OneParam(FFFInputEventDelegate, const FFFStateContext&, InStateContext);
USTRUCT(BlueprintType)
struct FFFInputEventHandler
{
GENERATED_BODY()
// TODO: document
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
FFFInputSequence RequiredSequence;
// TODO: document
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
FFFInputEventDelegate Delegate;
};
/** /**
* 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.
@ -99,6 +114,17 @@ public:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties") UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties")
UAnimMontage* MontageToPlay; UAnimMontage* MontageToPlay;
/**
* Event delegates to call when a certain input condition is detected in the Owner's input buffer
*/
UPROPERTY(BlueprintReadOnly, Category="UFF State Events")
TArray<FFFInputEventHandler> InputHandlers;
/**
* Called when state is first created.
*/
virtual void Init();
/** /**
* 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
@ -130,6 +156,17 @@ public:
*/ */
virtual void Update(float OneFrame, const FFFStateContext& InStateContext); virtual void Update(float OneFrame, const FFFStateContext& InStateContext);
// TODO: document
virtual void Landed(const FHitResult& Hit, const FFFStateContext& InStateContext);
// TODO: document
// TODO: pass in hitdata struct as well
virtual void Hit(const FFFStateContext& InStateContext);
// TODO: document
// TODO: pass in hitdata struct as well
virtual void Block(const FFFStateContext& InStateContext);
/** /**
* Called when you want to exit from this state but no eligible transitions exist to other states, * 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. * usually due to a lack of player input.
@ -146,6 +183,14 @@ public:
UFUNCTION(BlueprintCallable) UFUNCTION(BlueprintCallable)
virtual void Finish(const FFFStateContext& InStateContext); virtual void Finish(const FFFStateContext& InStateContext);
// TODO: document
UFUNCTION(BlueprintCallable)
virtual void RegisterInputHandler(
const FFFInputSequence& InRequiredSequence, FFFInputEventDelegate InDelegate);
UFUNCTION(BlueprintNativeEvent, Category="UFF|State|Events")
void OnInit();
/** /**
* Blueprint hook for overriding the CanTransition logic * Blueprint hook for overriding the CanTransition logic
*/ */
@ -172,19 +217,23 @@ public:
UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events") UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events")
void OnUpdate(float OneFrame, const FFFStateContext& InStateContext); void OnUpdate(float OneFrame, const FFFStateContext& InStateContext);
// TODO: document
UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events") UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events")
void OnLanded(const FFFStateContext& InStateContext); void OnLanded(const FHitResult& Hit, const FFFStateContext& InStateContext);
// TODO: document
// TODO: pass in hitdata struct as well
UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events") UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events")
void OnHit(const FFFStateContext& InStateContext); void OnHit(const FFFStateContext& InStateContext);
// TODO: document
// TODO: pass in hitdata struct as well
UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events") UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events")
void OnBlock(const FFFStateContext& InStateContext); void OnBlock(const FFFStateContext& InStateContext);
UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events")
void OnInputEvent(const FFFStateContext& InStateContext);
// UObject interface // UObject interface
virtual void PostInitProperties() override;
virtual UWorld* GetWorld() const override; virtual UWorld* GetWorld() const override;
// End of UObject interface // End of UObject interface
}; };

View File

@ -151,6 +151,14 @@ FFFStateContext UFFStateMachineComponent::GetCurrentStateContext()
} }
void UFFStateMachineComponent::Landed(const FHitResult& Hit)
{
check(CurrentState)
CurrentState->Landed(Hit, GetCurrentStateContext());
}
void UFFStateMachineComponent::FixedTick(float OneFrame) void UFFStateMachineComponent::FixedTick(float OneFrame)
{ {
// CurrentState should never be null // CurrentState should never be null

View File

@ -101,6 +101,9 @@ public:
*/ */
FFFStateContext GetCurrentStateContext(); FFFStateContext GetCurrentStateContext();
// Events
virtual void Landed(const FHitResult& Hit);
// IFFSystemInterface interface // IFFSystemInterface interface
virtual void FixedTick(float OneFrame) override; virtual void FixedTick(float OneFrame) override;
// End of IFFSystemInterface interface // End of IFFSystemInterface interface

View File

@ -21,5 +21,7 @@ class UNREALFIGHTINGFRAMEWORK_API IFFStateOwnerInterface
GENERATED_BODY() GENERATED_BODY()
public: public:
virtual bool CheckInputSequences(const TArray<FFFInputSequence>& InputSequences) = 0; virtual bool CheckInputSequence(const FFFInputSequence& InInputSequence) = 0;
virtual bool CheckInputSequences(const TArray<FFFInputSequence>& InInputSequences) = 0;
}; };