From 08d52a913f5f88cefd35e8b1372207b6ab57bf07 Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Sat, 5 Aug 2023 14:58:08 -0400 Subject: [PATCH] Implement input sequence and landed events --- .../Input/FFInputBufferComponent.h | 8 +- .../Input/FFPlayerController.cpp | 5 ++ .../Input/FFPlayerController.h | 2 + .../UnrealFightingFramework/State/FFState.cpp | 74 ++++++++++++++++++- .../UnrealFightingFramework/State/FFState.h | 59 +++++++++++++-- .../State/FFStateMachineComponent.cpp | 8 ++ .../State/FFStateMachineComponent.h | 3 + .../State/IFFStateOwnerInterface.h | 4 +- 8 files changed, 151 insertions(+), 12 deletions(-) diff --git a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h index 0a720d0..35184d3 100644 --- a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h +++ b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h @@ -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 Sequence; - UPROPERTY(EditAnywhere) + UPROPERTY(EditAnywhere, BlueprintReadWrite) int32 MaxDuration; FFFInputSequence() diff --git a/Source/UnrealFightingFramework/Input/FFPlayerController.cpp b/Source/UnrealFightingFramework/Input/FFPlayerController.cpp index 7fe45cd..9744321 100644 --- a/Source/UnrealFightingFramework/Input/FFPlayerController.cpp +++ b/Source/UnrealFightingFramework/Input/FFPlayerController.cpp @@ -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& InputSequences) { diff --git a/Source/UnrealFightingFramework/Input/FFPlayerController.h b/Source/UnrealFightingFramework/Input/FFPlayerController.h index b509246..acf648b 100644 --- a/Source/UnrealFightingFramework/Input/FFPlayerController.h +++ b/Source/UnrealFightingFramework/Input/FFPlayerController.h @@ -52,6 +52,8 @@ public: // End of IFFSystemInterface // IFFStateOwnerInterface + virtual bool CheckInputSequence(const FFFInputSequence& InInputSequence) override; + virtual bool CheckInputSequences(const TArray& InputSequences) override; // End of IFFStateOwnerInterface diff --git a/Source/UnrealFightingFramework/State/FFState.cpp b/Source/UnrealFightingFramework/State/FFState.cpp index 8e498dc..a41541e 100644 --- a/Source/UnrealFightingFramework/State/FFState.cpp +++ b/Source/UnrealFightingFramework/State/FFState.cpp @@ -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(InStateContext.Avatar); @@ -80,6 +84,30 @@ void UFFState::Exit(const FFFStateContext& InStateContext) void UFFState::Update(float OneFrame, const FFFStateContext& InStateContext) { + IFFStateOwnerInterface* SOI = Cast(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(GetOuter()); diff --git a/Source/UnrealFightingFramework/State/FFState.h b/Source/UnrealFightingFramework/State/FFState.h index 1efcdea..0669863 100644 --- a/Source/UnrealFightingFramework/State/FFState.h +++ b/Source/UnrealFightingFramework/State/FFState.h @@ -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 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 }; diff --git a/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp b/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp index 286b4dd..7649068 100644 --- a/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp +++ b/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp @@ -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 diff --git a/Source/UnrealFightingFramework/State/FFStateMachineComponent.h b/Source/UnrealFightingFramework/State/FFStateMachineComponent.h index 82066d9..4030cfc 100644 --- a/Source/UnrealFightingFramework/State/FFStateMachineComponent.h +++ b/Source/UnrealFightingFramework/State/FFStateMachineComponent.h @@ -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 diff --git a/Source/UnrealFightingFramework/State/IFFStateOwnerInterface.h b/Source/UnrealFightingFramework/State/IFFStateOwnerInterface.h index 8004cf2..d6de2c3 100644 --- a/Source/UnrealFightingFramework/State/IFFStateOwnerInterface.h +++ b/Source/UnrealFightingFramework/State/IFFStateOwnerInterface.h @@ -21,5 +21,7 @@ class UNREALFIGHTINGFRAMEWORK_API IFFStateOwnerInterface GENERATED_BODY() public: - virtual bool CheckInputSequences(const TArray& InputSequences) = 0; + virtual bool CheckInputSequence(const FFFInputSequence& InInputSequence) = 0; + + virtual bool CheckInputSequences(const TArray& InInputSequences) = 0; };