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

View File

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

View File

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

View File

@ -10,6 +10,11 @@
// UE includes
#include "Components/SkeletalMeshComponent.h"
void UFFState::Init()
{
OnInit();
}
bool UFFState::CanTransition(const FFFStateContext& InStateContext)
{
/**
@ -17,12 +22,11 @@ bool UFFState::CanTransition(const FFFStateContext& InStateContext)
* if so then
* Check if the avatar is in the correct stance to perform this action
* 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 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
*/
IFFStateAvatarInterface* SAI = Cast<IFFStateAvatarInterface>(InStateContext.Avatar);
@ -80,6 +84,30 @@ void UFFState::Exit(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);
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)
{
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)
{
return true;
}
void UFFState::PostInitProperties()
{
UObject::PostInitProperties();
Init();
}
UWorld* UFFState::GetWorld() const
{
UFFStateMachineComponent* SMC = Cast<UFFStateMachineComponent>(GetOuter());

View File

@ -10,7 +10,6 @@
#include "FFState.generated.h"
USTRUCT(BlueprintType)
struct FFFStateContext
{
@ -34,6 +33,22 @@ struct FFFStateContext
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
* and logic to run when the state is entered, exited, and active.
@ -99,6 +114,17 @@ public:
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties")
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
* 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);
// 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,
* usually due to a lack of player input.
@ -146,6 +183,14 @@ public:
UFUNCTION(BlueprintCallable)
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
*/
@ -172,19 +217,23 @@ public:
UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events")
void OnUpdate(float OneFrame, const FFFStateContext& InStateContext);
// TODO: document
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")
void OnHit(const FFFStateContext& InStateContext);
// TODO: document
// TODO: pass in hitdata struct as well
UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events")
void OnBlock(const FFFStateContext& InStateContext);
UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events")
void OnInputEvent(const FFFStateContext& InStateContext);
// UObject interface
virtual void PostInitProperties() override;
virtual UWorld* GetWorld() const override;
// 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)
{
// CurrentState should never be null

View File

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

View File

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