From fef1217877e09eec282ad34aa58c2783a9a92445 Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Sat, 3 Jun 2023 14:37:14 -0400 Subject: [PATCH 01/38] Reorganize plugin files and start creating some state machine classes --- .../IFESystemInterface.cpp | 9 +++++ .../UnrealFightingEngine/IFESystemInterface.h | 33 +++++++++++++++++++ Source/UnrealFightingEngine/State/FEState.cpp | 5 +++ Source/UnrealFightingEngine/State/FEState.h | 17 ++++++++++ .../State/FEStateMachineComponent.cpp | 29 ++++++++++++++++ .../State/FEStateMachineComponent.h | 26 +++++++++++++++ .../UnrealFightingEngine.Build.cs | 2 +- .../UnrealFightingEngineBPLibrary.cpp | 2 +- .../UnrealFightingEngineBPLibrary.h | 0 ...ine.cpp => UnrealFightingEngineModule.cpp} | 2 +- ...gEngine.h => UnrealFightingEngineModule.h} | 0 11 files changed, 122 insertions(+), 3 deletions(-) create mode 100644 Source/UnrealFightingEngine/IFESystemInterface.cpp create mode 100644 Source/UnrealFightingEngine/IFESystemInterface.h create mode 100644 Source/UnrealFightingEngine/State/FEState.cpp create mode 100644 Source/UnrealFightingEngine/State/FEState.h create mode 100644 Source/UnrealFightingEngine/State/FEStateMachineComponent.cpp create mode 100644 Source/UnrealFightingEngine/State/FEStateMachineComponent.h rename Source/UnrealFightingEngine/{Private => }/UnrealFightingEngineBPLibrary.cpp (89%) rename Source/UnrealFightingEngine/{Public => }/UnrealFightingEngineBPLibrary.h (100%) rename Source/UnrealFightingEngine/{Private/UnrealFightingEngine.cpp => UnrealFightingEngineModule.cpp} (93%) rename Source/UnrealFightingEngine/{Public/UnrealFightingEngine.h => UnrealFightingEngineModule.h} (100%) diff --git a/Source/UnrealFightingEngine/IFESystemInterface.cpp b/Source/UnrealFightingEngine/IFESystemInterface.cpp new file mode 100644 index 0000000..c715879 --- /dev/null +++ b/Source/UnrealFightingEngine/IFESystemInterface.cpp @@ -0,0 +1,9 @@ +// Project Sword & Gun Copyright Kevin Poretti + + +#include "IFESystemInterface.h" + +// Add default functionality here for any IIFESystemInterface functions that are not pure virtual. +void IIFESystemInterface::FixedTick(float OneFrame) +{ +} diff --git a/Source/UnrealFightingEngine/IFESystemInterface.h b/Source/UnrealFightingEngine/IFESystemInterface.h new file mode 100644 index 0000000..78573e7 --- /dev/null +++ b/Source/UnrealFightingEngine/IFESystemInterface.h @@ -0,0 +1,33 @@ +// Project Sword & Gun Copyright Kevin Poretti + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Interface.h" +#include "IFESystemInterface.generated.h" + +// This class does not need to be modified. +UINTERFACE(MinimalAPI) +class UIFESystemInterface : public UInterface +{ + GENERATED_BODY() +}; + +/** + * This interface defines functions that need to be implemented for any objects that are "gameplay affecting". + * + * This ensures all gameplay effecting objects can be assumed to have certain properties that enable serialization, networking and + * some form of determinism. + */ +class UNREALFIGHTINGENGINE_API IIFESystemInterface +{ + GENERATED_BODY() + +public: + /** + * Function to be called at a fixed interval/frame rate rather than using Unreal's variable Tick function + * + * @param OneFrame the time that elapses during one fixed tick + */ + virtual void FixedTick(float OneFrame); +}; diff --git a/Source/UnrealFightingEngine/State/FEState.cpp b/Source/UnrealFightingEngine/State/FEState.cpp new file mode 100644 index 0000000..7e49f67 --- /dev/null +++ b/Source/UnrealFightingEngine/State/FEState.cpp @@ -0,0 +1,5 @@ +// Project Sword & Gun Copyright Kevin Poretti + + +#include "FEState.h" + diff --git a/Source/UnrealFightingEngine/State/FEState.h b/Source/UnrealFightingEngine/State/FEState.h new file mode 100644 index 0000000..8fd888f --- /dev/null +++ b/Source/UnrealFightingEngine/State/FEState.h @@ -0,0 +1,17 @@ +// Project Sword & Gun Copyright Kevin Poretti + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/NoExportTypes.h" +#include "FEState.generated.h" + +/** + * + */ +UCLASS() +class UNREALFIGHTINGENGINE_API UFEState : public UObject +{ + GENERATED_BODY() + +}; diff --git a/Source/UnrealFightingEngine/State/FEStateMachineComponent.cpp b/Source/UnrealFightingEngine/State/FEStateMachineComponent.cpp new file mode 100644 index 0000000..85bec85 --- /dev/null +++ b/Source/UnrealFightingEngine/State/FEStateMachineComponent.cpp @@ -0,0 +1,29 @@ +// Project Sword & Gun Copyright Kevin Poretti + + +#include "FEStateMachineComponent.h" + +// Sets default values for this component's properties +UFEStateMachineComponent::UFEStateMachineComponent() +{ + // Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features + // off to improve performance if you don't need them. + PrimaryComponentTick.bCanEverTick = false; + + // ... +} + + +// Called when the game starts +void UFEStateMachineComponent::BeginPlay() +{ + Super::BeginPlay(); + + // ... + +} + +void UFEStateMachineComponent::FixedTick(float OneFrame) +{ +} + diff --git a/Source/UnrealFightingEngine/State/FEStateMachineComponent.h b/Source/UnrealFightingEngine/State/FEStateMachineComponent.h new file mode 100644 index 0000000..d017375 --- /dev/null +++ b/Source/UnrealFightingEngine/State/FEStateMachineComponent.h @@ -0,0 +1,26 @@ +// Project Sword & Gun Copyright Kevin Poretti + +#pragma once + +#include "CoreMinimal.h" +#include "Components/ActorComponent.h" +#include "UnrealFightingEngine/IFESystemInterface.h" +#include "FEStateMachineComponent.generated.h" + + +UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) +class UNREALFIGHTINGENGINE_API UFEStateMachineComponent : public UActorComponent, public IIFESystemInterface +{ + GENERATED_BODY() + +public: + // Sets default values for this component's properties + UFEStateMachineComponent(); + +protected: + // Called when the game starts + virtual void BeginPlay() override; + +public: + virtual void FixedTick(float OneFrame) override; +}; diff --git a/Source/UnrealFightingEngine/UnrealFightingEngine.Build.cs b/Source/UnrealFightingEngine/UnrealFightingEngine.Build.cs index 5015cba..4cb47fa 100644 --- a/Source/UnrealFightingEngine/UnrealFightingEngine.Build.cs +++ b/Source/UnrealFightingEngine/UnrealFightingEngine.Build.cs @@ -10,7 +10,7 @@ public class UnrealFightingEngine : ModuleRules PublicIncludePaths.AddRange( new string[] { - // ... add public include paths required here ... + "UnrealFightingEngine" } ); diff --git a/Source/UnrealFightingEngine/Private/UnrealFightingEngineBPLibrary.cpp b/Source/UnrealFightingEngine/UnrealFightingEngineBPLibrary.cpp similarity index 89% rename from Source/UnrealFightingEngine/Private/UnrealFightingEngineBPLibrary.cpp rename to Source/UnrealFightingEngine/UnrealFightingEngineBPLibrary.cpp index cca6b9c..f924e4a 100644 --- a/Source/UnrealFightingEngine/Private/UnrealFightingEngineBPLibrary.cpp +++ b/Source/UnrealFightingEngine/UnrealFightingEngineBPLibrary.cpp @@ -1,7 +1,7 @@ // Copyright Epic Games, Inc. All Rights Reserved. #include "UnrealFightingEngineBPLibrary.h" -#include "UnrealFightingEngine.h" +#include "UnrealFightingEngineModule.h" UUnrealFightingEngineBPLibrary::UUnrealFightingEngineBPLibrary(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) diff --git a/Source/UnrealFightingEngine/Public/UnrealFightingEngineBPLibrary.h b/Source/UnrealFightingEngine/UnrealFightingEngineBPLibrary.h similarity index 100% rename from Source/UnrealFightingEngine/Public/UnrealFightingEngineBPLibrary.h rename to Source/UnrealFightingEngine/UnrealFightingEngineBPLibrary.h diff --git a/Source/UnrealFightingEngine/Private/UnrealFightingEngine.cpp b/Source/UnrealFightingEngine/UnrealFightingEngineModule.cpp similarity index 93% rename from Source/UnrealFightingEngine/Private/UnrealFightingEngine.cpp rename to Source/UnrealFightingEngine/UnrealFightingEngineModule.cpp index 86106a8..fc66106 100644 --- a/Source/UnrealFightingEngine/Private/UnrealFightingEngine.cpp +++ b/Source/UnrealFightingEngine/UnrealFightingEngineModule.cpp @@ -1,6 +1,6 @@ // Copyright Epic Games, Inc. All Rights Reserved. -#include "UnrealFightingEngine.h" +#include "UnrealFightingEngineModule.h" #define LOCTEXT_NAMESPACE "FUnrealFightingEngineModule" diff --git a/Source/UnrealFightingEngine/Public/UnrealFightingEngine.h b/Source/UnrealFightingEngine/UnrealFightingEngineModule.h similarity index 100% rename from Source/UnrealFightingEngine/Public/UnrealFightingEngine.h rename to Source/UnrealFightingEngine/UnrealFightingEngineModule.h From d869d0105f68fed56fab6fa862fbb7e9cb2c02ec Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Sat, 3 Jun 2023 18:38:35 -0400 Subject: [PATCH 02/38] Start implementing states --- .../IFESystemInterface.cpp | 7 +- .../UnrealFightingEngine/IFESystemInterface.h | 4 +- Source/UnrealFightingEngine/State/FEState.cpp | 55 +++++++++++- Source/UnrealFightingEngine/State/FEState.h | 90 ++++++++++++++++++- .../State/FEStateMachineComponent.cpp | 3 +- .../State/FEStateMachineComponent.h | 2 +- 6 files changed, 145 insertions(+), 16 deletions(-) diff --git a/Source/UnrealFightingEngine/IFESystemInterface.cpp b/Source/UnrealFightingEngine/IFESystemInterface.cpp index c715879..41c7cb9 100644 --- a/Source/UnrealFightingEngine/IFESystemInterface.cpp +++ b/Source/UnrealFightingEngine/IFESystemInterface.cpp @@ -1,9 +1,4 @@ -// Project Sword & Gun Copyright Kevin Poretti +// Unreal Fighting Engine by Kevin Poretti #include "IFESystemInterface.h" - -// Add default functionality here for any IIFESystemInterface functions that are not pure virtual. -void IIFESystemInterface::FixedTick(float OneFrame) -{ -} diff --git a/Source/UnrealFightingEngine/IFESystemInterface.h b/Source/UnrealFightingEngine/IFESystemInterface.h index 78573e7..0ad0f96 100644 --- a/Source/UnrealFightingEngine/IFESystemInterface.h +++ b/Source/UnrealFightingEngine/IFESystemInterface.h @@ -1,4 +1,4 @@ -// Project Sword & Gun Copyright Kevin Poretti +// Unreal Fighting Engine by Kevin Poretti #pragma once @@ -29,5 +29,5 @@ public: * * @param OneFrame the time that elapses during one fixed tick */ - virtual void FixedTick(float OneFrame); + virtual void FixedTick(float OneFrame) = 0; }; diff --git a/Source/UnrealFightingEngine/State/FEState.cpp b/Source/UnrealFightingEngine/State/FEState.cpp index 7e49f67..e70c55c 100644 --- a/Source/UnrealFightingEngine/State/FEState.cpp +++ b/Source/UnrealFightingEngine/State/FEState.cpp @@ -1,5 +1,56 @@ -// Project Sword & Gun Copyright Kevin Poretti - +// Unreal Fighting Engine by Kevin Poretti #include "FEState.h" +#include "FEStateMachineComponent.h" + +void UFEState::InitActorInfo(AActor* InOwner, AActor* InAvatar) +{ + Owner = InOwner; + Avatar = InAvatar; +} + +void UFEState::Enter() +{ + TicksInState = 0; + + OnEnter(); +} + +void UFEState::Exit() +{ + OnExit(); +} + +void UFEState::FixedTick(float OneFrame) +{ + TicksInState++; + + OnFixedTick(OneFrame); +} + + +void UFEState::OnEnter_Implementation() +{ +} + + +void UFEState::OnExit_Implementation() +{ +} + + +void UFEState::OnFixedTick_Implementation(float OneFrame) +{ +} + +UWorld* UFEState::GetWorld() const +{ + UFEStateMachineComponent* SMC = Cast(GetOuter()); + if(SMC) + { + return SMC->GetWorld(); + } + + return nullptr; +} diff --git a/Source/UnrealFightingEngine/State/FEState.h b/Source/UnrealFightingEngine/State/FEState.h index 8fd888f..395c143 100644 --- a/Source/UnrealFightingEngine/State/FEState.h +++ b/Source/UnrealFightingEngine/State/FEState.h @@ -1,4 +1,4 @@ -// Project Sword & Gun Copyright Kevin Poretti +// Unreal Fighting Engine by Kevin Poretti #pragma once @@ -7,11 +7,95 @@ #include "FEState.generated.h" /** - * + * 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. */ UCLASS() class UNREALFIGHTINGENGINE_API UFEState : public UObject { GENERATED_BODY() - + +public: + + /** + * Initializes pointers to what owns this state and what avatar this state represents. + */ + virtual void InitActorInfo(AActor* InOwner, AActor* InAvatar); + + /** Name of this state */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="UFE|State") + FName Name; + + /** Conditions that need to be met in order for this state to be transitioned into */ + UPROPERTY(EditAnywhere) + TArray EntryConditions; + + /** What is this state's category. Used for determining what types of state can prematurely cancel this one. */ + UPROPERTY(EditAnywhere) + uint8 StateType; + + /** How many ticks have elapsed since this state was entered */ + UPROPERTY(BlueprintReadOnly, Category="UFE|State") + int32 TicksInState; + + /** + * Called whenever this state is transitioned into. + * + * Resets TicksInState and calls appropriate Blueprint hooks + */ + void Enter(); + + /** + * Called whenever this state is transitioned out of into a new state. + */ + void Exit(); + + /** + * Called whenever this state is active and the game logic ticks. + * + * Increments TicksInState and calls appropriate Blueprint hooks. + * + * @param OneFrame the time that elapses during one fixed tick + */ + void FixedTick(float OneFrame); + + /** + * Blueprint hook that is called whenever this state is transitioned into + */ + UFUNCTION(BlueprintNativeEvent, Category="UFE|State|Events") + void OnEnter(); + + /** + * Blueprint hook that is called whenever this state is transitioned out of into a new state + */ + UFUNCTION(BlueprintNativeEvent, Category="UFE|State|Events") + void OnExit(); + + /** + * Blueprint hook that is called whenever this state is active and the game logic ticks + * + * @param OneFrame the time that elapses during one fixed tick + */ + UFUNCTION(BlueprintNativeEvent, Category="UFE|State|Events") + void OnFixedTick(float OneFrame); + + // UObject interface + virtual UWorld* GetWorld() const override; + // End of UObject interface + +protected: + + /** + * Actor that owns this state. + * This will typically be a player controller that possesses the avatar. + */ + UPROPERTY(BlueprintReadOnly) + AActor* Owner; + + /** + * The avatar is an actor that this state represents. + * This will typically be a pawn or character. + */ + UPROPERTY(BlueprintReadOnly) + AActor* Avatar; }; diff --git a/Source/UnrealFightingEngine/State/FEStateMachineComponent.cpp b/Source/UnrealFightingEngine/State/FEStateMachineComponent.cpp index 85bec85..7a8b740 100644 --- a/Source/UnrealFightingEngine/State/FEStateMachineComponent.cpp +++ b/Source/UnrealFightingEngine/State/FEStateMachineComponent.cpp @@ -1,5 +1,4 @@ -// Project Sword & Gun Copyright Kevin Poretti - +// Unreal Fighting Engine by Kevin Poretti #include "FEStateMachineComponent.h" diff --git a/Source/UnrealFightingEngine/State/FEStateMachineComponent.h b/Source/UnrealFightingEngine/State/FEStateMachineComponent.h index d017375..2077de8 100644 --- a/Source/UnrealFightingEngine/State/FEStateMachineComponent.h +++ b/Source/UnrealFightingEngine/State/FEStateMachineComponent.h @@ -1,4 +1,4 @@ -// Project Sword & Gun Copyright Kevin Poretti +// Unreal Fighting Engine by Kevin Poretti #pragma once From a42e4c666744f7138a35490da4bee198eba382cc Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Sun, 4 Jun 2023 15:56:36 -0400 Subject: [PATCH 03/38] Start implementing state machine --- Source/UnrealFightingEngine/State/FEState.h | 6 +- .../State/FEStateMachineComponent.cpp | 123 ++++++++++++++++-- .../State/FEStateMachineComponent.h | 106 ++++++++++++++- 3 files changed, 219 insertions(+), 16 deletions(-) diff --git a/Source/UnrealFightingEngine/State/FEState.h b/Source/UnrealFightingEngine/State/FEState.h index 395c143..29b003b 100644 --- a/Source/UnrealFightingEngine/State/FEState.h +++ b/Source/UnrealFightingEngine/State/FEState.h @@ -2,8 +2,9 @@ #pragma once +// UE includes #include "CoreMinimal.h" -#include "UObject/NoExportTypes.h" + #include "FEState.generated.h" /** @@ -19,6 +20,9 @@ public: /** * Initializes pointers to what owns this state and what avatar this state represents. + * + * @param InOwner Actor that owns this state machine. + * @param InAvatar Actor that this state machine represents. */ virtual void InitActorInfo(AActor* InOwner, AActor* InAvatar); diff --git a/Source/UnrealFightingEngine/State/FEStateMachineComponent.cpp b/Source/UnrealFightingEngine/State/FEStateMachineComponent.cpp index 7a8b740..f83c169 100644 --- a/Source/UnrealFightingEngine/State/FEStateMachineComponent.cpp +++ b/Source/UnrealFightingEngine/State/FEStateMachineComponent.cpp @@ -1,28 +1,135 @@ // Unreal Fighting Engine by Kevin Poretti +// FE includes +#include "FEState.h" + #include "FEStateMachineComponent.h" -// Sets default values for this component's properties UFEStateMachineComponent::UFEStateMachineComponent() { - // Set this component to be initialized when the game starts, and to be ticked every frame. You can turn these features - // off to improve performance if you don't need them. + // Don't use Unreal's tick instead use a fixed tick PrimaryComponentTick.bCanEverTick = false; - - // ... } -// Called when the game starts +void UFEStateMachineComponent::Initialize() +{ + for(const TSubclassOf& CurrState : DefaultStates) + { + UFEState* TempState = AddState(CurrState); + if(!CurrentState) // first state to be created is the entry into this state machine + { + CurrentState = TempState; + CurrentState->Enter(); + } + } +} + + +void UFEStateMachineComponent::InitActorInfo(AActor* InOwner, AActor* InAvatar) +{ + Owner = InOwner; + Avatar = InAvatar; +} + + +UFEState* UFEStateMachineComponent::AddState(TSubclassOf StateClassToAdd) +{ + UFEState* TempState = NewObject(this, StateClassToAdd); + if(TempState) + { + States.Add(TempState); + TempState->InitActorInfo(Owner, Avatar); + return TempState; + } + + return nullptr; +} + + +void UFEStateMachineComponent::AddStates(const TArray>& StateClassesToAdd) +{ + for(const TSubclassOf& CurrState : StateClassesToAdd) + { + AddState(CurrState); + } +} + + +void UFEStateMachineComponent::RemoveState(FName StateToRemove) +{ + UE_LOG(LogTemp, Error, TEXT("UFEStateMachineComponent::RemoveState is not yet implemented")); +} + +void UFEStateMachineComponent::SwitchStates(UFEState* NewState) +{ + check(NewState); + + CurrentState->Exit(); + CurrentState = NewState; + CurrentState->Enter(); +} + + +FName UFEStateMachineComponent::GetCurrentStateName() const +{ + return CurrentState ? CurrentState->Name : NAME_None; +} + + +UFEState* UFEStateMachineComponent::FindStateWithName(FName StateName) +{ + for (UFEState* 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 UFEStateMachineComponent::BeginPlay() { Super::BeginPlay(); - // ... - + Initialize(); } + void UFEStateMachineComponent::FixedTick(float OneFrame) { + // Should we switch states? + + for(UFEState* CurrState : States) + { + // Check if the state is enabled + + // Check state entry 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 + + // Lastly just check if the state we're about to transition into isn't the current state. + // It is OK to transition if state's "CanTransitionToSelf" is true + + // SwitchStates(NewState); + // return; + } + + // CurrentState should never be null + // TODO: Should probably assert or whatever UE's equivalent is + check(CurrentState); + + // Tick current state + CurrentState->FixedTick(OneFrame); + + // Debug } diff --git a/Source/UnrealFightingEngine/State/FEStateMachineComponent.h b/Source/UnrealFightingEngine/State/FEStateMachineComponent.h index 2077de8..5cb2428 100644 --- a/Source/UnrealFightingEngine/State/FEStateMachineComponent.h +++ b/Source/UnrealFightingEngine/State/FEStateMachineComponent.h @@ -2,25 +2,117 @@ #pragma once +// FE includes +#include "UnrealFightingEngine/IFESystemInterface.h" + +// UE includes #include "CoreMinimal.h" #include "Components/ActorComponent.h" -#include "UnrealFightingEngine/IFESystemInterface.h" + #include "FEStateMachineComponent.generated.h" - +/** + * A state machine is a component that evaluates and controls the transitions for state objects that + * are a part of this state machine. + * + * This component also calls the appropriate state logic when a state is changed or the component ticks. + */ UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) class UNREALFIGHTINGENGINE_API UFEStateMachineComponent : public UActorComponent, public IIFESystemInterface { GENERATED_BODY() public: - // Sets default values for this component's properties UFEStateMachineComponent(); -protected: - // Called when the game starts - virtual void BeginPlay() override; + /** + * Creates and adds default states and enters the first state + */ + void Initialize(); -public: + /** + * Initializes pointers to what owns this state machine and what avatar this state represents. + * + * These pointers will also be passed to the states owned by this state machine. + * + * @param InOwner Actor that owns this state machine. + * @param InAvatar Actor that this state machine represents. + */ + virtual void InitActorInfo(AActor* InOwner, AActor* InAvatar); + + /** + * Creates an instance of the state class and adds newly created state to this state machine. + * + * @param StateClassToAdd State class type to be added to this state machine + * + * @return A pointer to the state that was added or nullptr if there was an issue adding or creating the state + */ + UFEState* AddState(TSubclassOf StateClassToAdd); + + /** + * Creates an instance of the state classes and adds newly created states to this state machine. + * + * @param StateClassesToAdd Array of state class types to be added to this state machine + */ + void AddStates(const TArray>& StateClassesToAdd); + + /** + * Destroys the state with corresponding name and removes it from this state machine. + */ + void RemoveState(FName StateToRemove); + + /** + * Transitions from CurrentState to the new state passed to this function + * + * Triggers the Exit callback on the CurrentState and the Enter callback on the new state + */ + void SwitchStates(UFEState* NewState); + + /** + * Returns the name of the current state + */ + UFUNCTION(BlueprintPure) + FName GetCurrentStateName() const; + + // IIFESystemInterface interface virtual void FixedTick(float OneFrame) override; + // End of IIFESystemInterface interface + +protected: + /** + * Actor that owns this state machine. + * This will typically be a player controller that possesses the avatar. + */ + UPROPERTY(BlueprintReadOnly) + 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) + AActor* Avatar; + + /** + * States classes to create and add to this state machine when the game starts + */ + UPROPERTY(EditDefaultsOnly, Category="UFE|State Machine") + TArray> DefaultStates; + + /** Current active state for this state machine */ + UPROPERTY(BlueprintReadOnly) + UFEState* CurrentState; + + // States that have been added + UPROPERTY(BlueprintReadOnly) + TArray States; + + /** + * Returns the state with corresponding name + */ + UFEState* FindStateWithName(FName StateName); + + // UActorComponent interface + virtual void BeginPlay() override; + // End of UActorComponent interface }; From 3090798d20e1325b3be855bb31da27d545ee2029 Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Mon, 5 Jun 2023 16:17:36 -0400 Subject: [PATCH 04/38] Fix some warnings --- Source/UnrealFightingEngine/IFESystemInterface.h | 1 + Source/UnrealFightingEngine/State/FEState.cpp | 2 ++ Source/UnrealFightingEngine/State/FEStateMachineComponent.cpp | 4 ++-- Source/UnrealFightingEngine/UnrealFightingEngine.Build.cs | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Source/UnrealFightingEngine/IFESystemInterface.h b/Source/UnrealFightingEngine/IFESystemInterface.h index 0ad0f96..f40dbc9 100644 --- a/Source/UnrealFightingEngine/IFESystemInterface.h +++ b/Source/UnrealFightingEngine/IFESystemInterface.h @@ -4,6 +4,7 @@ #include "CoreMinimal.h" #include "UObject/Interface.h" + #include "IFESystemInterface.generated.h" // This class does not need to be modified. diff --git a/Source/UnrealFightingEngine/State/FEState.cpp b/Source/UnrealFightingEngine/State/FEState.cpp index e70c55c..dc4f4f9 100644 --- a/Source/UnrealFightingEngine/State/FEState.cpp +++ b/Source/UnrealFightingEngine/State/FEState.cpp @@ -1,6 +1,8 @@ // Unreal Fighting Engine by Kevin Poretti #include "FEState.h" + +// FE includes #include "FEStateMachineComponent.h" diff --git a/Source/UnrealFightingEngine/State/FEStateMachineComponent.cpp b/Source/UnrealFightingEngine/State/FEStateMachineComponent.cpp index f83c169..8cac64a 100644 --- a/Source/UnrealFightingEngine/State/FEStateMachineComponent.cpp +++ b/Source/UnrealFightingEngine/State/FEStateMachineComponent.cpp @@ -1,10 +1,10 @@ // Unreal Fighting Engine by Kevin Poretti +#include "FEStateMachineComponent.h" + // FE includes #include "FEState.h" -#include "FEStateMachineComponent.h" - UFEStateMachineComponent::UFEStateMachineComponent() { // Don't use Unreal's tick instead use a fixed tick diff --git a/Source/UnrealFightingEngine/UnrealFightingEngine.Build.cs b/Source/UnrealFightingEngine/UnrealFightingEngine.Build.cs index 4cb47fa..49e3e54 100644 --- a/Source/UnrealFightingEngine/UnrealFightingEngine.Build.cs +++ b/Source/UnrealFightingEngine/UnrealFightingEngine.Build.cs @@ -10,7 +10,7 @@ public class UnrealFightingEngine : ModuleRules PublicIncludePaths.AddRange( new string[] { - "UnrealFightingEngine" + // ... add public include paths required here ... } ); From ecd8b1ca3c26a0163e80bb799dd52fa9bccb3231 Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Mon, 5 Jun 2023 17:02:11 -0400 Subject: [PATCH 05/38] Start of input buffer and player controller --- .../UnrealFightingEngine/IFESystemInterface.h | 4 +- .../Input/FEInputBufferComponent.cpp | 8 ++++ .../Input/FEInputBufferComponent.h | 18 +++++++++ .../Input/FEPlayerController.cpp | 20 ++++++++++ .../Input/FEPlayerController.h | 40 +++++++++++++++++++ Source/UnrealFightingEngine/State/FEState.cpp | 13 +++--- Source/UnrealFightingEngine/State/FEState.h | 4 +- .../State/FEStateMachineComponent.cpp | 2 +- .../State/FEStateMachineComponent.h | 6 +-- 9 files changed, 102 insertions(+), 13 deletions(-) create mode 100644 Source/UnrealFightingEngine/Input/FEInputBufferComponent.cpp create mode 100644 Source/UnrealFightingEngine/Input/FEInputBufferComponent.h create mode 100644 Source/UnrealFightingEngine/Input/FEPlayerController.cpp create mode 100644 Source/UnrealFightingEngine/Input/FEPlayerController.h diff --git a/Source/UnrealFightingEngine/IFESystemInterface.h b/Source/UnrealFightingEngine/IFESystemInterface.h index f40dbc9..c0153c7 100644 --- a/Source/UnrealFightingEngine/IFESystemInterface.h +++ b/Source/UnrealFightingEngine/IFESystemInterface.h @@ -9,7 +9,7 @@ // This class does not need to be modified. UINTERFACE(MinimalAPI) -class UIFESystemInterface : public UInterface +class UFESystemInterface : public UInterface { GENERATED_BODY() }; @@ -20,7 +20,7 @@ class UIFESystemInterface : public UInterface * This ensures all gameplay effecting objects can be assumed to have certain properties that enable serialization, networking and * some form of determinism. */ -class UNREALFIGHTINGENGINE_API IIFESystemInterface +class UNREALFIGHTINGENGINE_API IFESystemInterface { GENERATED_BODY() diff --git a/Source/UnrealFightingEngine/Input/FEInputBufferComponent.cpp b/Source/UnrealFightingEngine/Input/FEInputBufferComponent.cpp new file mode 100644 index 0000000..bc28f2d --- /dev/null +++ b/Source/UnrealFightingEngine/Input/FEInputBufferComponent.cpp @@ -0,0 +1,8 @@ +// Unreal Fighting Engine by Kevin Poretti + +#include "FEInputBufferComponent.h" + +UFEInputBufferComponent::UFEInputBufferComponent() +{ + PrimaryComponentTick.bCanEverTick = false; +} diff --git a/Source/UnrealFightingEngine/Input/FEInputBufferComponent.h b/Source/UnrealFightingEngine/Input/FEInputBufferComponent.h new file mode 100644 index 0000000..30e925a --- /dev/null +++ b/Source/UnrealFightingEngine/Input/FEInputBufferComponent.h @@ -0,0 +1,18 @@ +// Unreal Fighting Engine by Kevin Poretti + +#pragma once + +// UE includes +#include "CoreMinimal.h" +#include "Components/ActorComponent.h" + +#include "FEInputBufferComponent.generated.h" + +UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) +class UNREALFIGHTINGENGINE_API UFEInputBufferComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + UFEInputBufferComponent(); +}; diff --git a/Source/UnrealFightingEngine/Input/FEPlayerController.cpp b/Source/UnrealFightingEngine/Input/FEPlayerController.cpp new file mode 100644 index 0000000..ea1dba5 --- /dev/null +++ b/Source/UnrealFightingEngine/Input/FEPlayerController.cpp @@ -0,0 +1,20 @@ +// Unreal Fighting Engine by Kevin Poretti + +#include "FEPlayerController.h" + +// FE includes +#include "FEInputBufferComponent.h" + +AFEPlayerController::AFEPlayerController() +{ + InputBuffer = CreateDefaultSubobject(TEXT("InputBuffer")); +} + +void AFEPlayerController::SendInputsToRemote() const +{ +} + +void AFEPlayerController::SetupInputComponent() +{ + Super::SetupInputComponent(); +} diff --git a/Source/UnrealFightingEngine/Input/FEPlayerController.h b/Source/UnrealFightingEngine/Input/FEPlayerController.h new file mode 100644 index 0000000..ee6d711 --- /dev/null +++ b/Source/UnrealFightingEngine/Input/FEPlayerController.h @@ -0,0 +1,40 @@ +// Unreal Fighting Engine by Kevin Poretti + +#pragma once + +// UE includes +#include "CoreMinimal.h" +#include "GameFramework/PlayerController.h" + +#include "FEPlayerController.generated.h" + +/** + * A class that collects player inputs, stores them in an input buffer, and sends a rolling window of + * unacknowledged inputs to a remote client or server for processing. + */ +UCLASS() +class UNREALFIGHTINGENGINE_API AFEPlayerController : public APlayerController +{ + GENERATED_BODY() + +public: + AFEPlayerController(); + + /** + * Sends all unacknowledged inputs to the remote + */ + void SendInputsToRemote() const; + + // APlayerController interface + virtual void SetupInputComponent() override; + // End of APlayerController interface + +protected: + /** Input Buffer component */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "UFE|Input", meta = (AllowPrivateAccess = "true")) + UFEInputBufferComponent* InputBuffer; + + int32 Inputs; + + TArray UnacknowledgedInputs; +}; diff --git a/Source/UnrealFightingEngine/State/FEState.cpp b/Source/UnrealFightingEngine/State/FEState.cpp index dc4f4f9..f52c68f 100644 --- a/Source/UnrealFightingEngine/State/FEState.cpp +++ b/Source/UnrealFightingEngine/State/FEState.cpp @@ -5,13 +5,13 @@ // FE includes #include "FEStateMachineComponent.h" - void UFEState::InitActorInfo(AActor* InOwner, AActor* InAvatar) { Owner = InOwner; Avatar = InAvatar; } + void UFEState::Enter() { TicksInState = 0; @@ -19,16 +19,18 @@ void UFEState::Enter() OnEnter(); } + void UFEState::Exit() { OnExit(); } -void UFEState::FixedTick(float OneFrame) + +void UFEState::Update(float OneFrame) { TicksInState++; - OnFixedTick(OneFrame); + OnUpdate(OneFrame); } @@ -42,10 +44,11 @@ void UFEState::OnExit_Implementation() } -void UFEState::OnFixedTick_Implementation(float OneFrame) +void UFEState::OnUpdate_Implementation(float OneFrame) { } + UWorld* UFEState::GetWorld() const { UFEStateMachineComponent* SMC = Cast(GetOuter()); @@ -55,4 +58,4 @@ UWorld* UFEState::GetWorld() const } return nullptr; -} +} \ No newline at end of file diff --git a/Source/UnrealFightingEngine/State/FEState.h b/Source/UnrealFightingEngine/State/FEState.h index 29b003b..2db6c2e 100644 --- a/Source/UnrealFightingEngine/State/FEState.h +++ b/Source/UnrealFightingEngine/State/FEState.h @@ -61,7 +61,7 @@ public: * * @param OneFrame the time that elapses during one fixed tick */ - void FixedTick(float OneFrame); + void Update(float OneFrame); /** * Blueprint hook that is called whenever this state is transitioned into @@ -81,7 +81,7 @@ public: * @param OneFrame the time that elapses during one fixed tick */ UFUNCTION(BlueprintNativeEvent, Category="UFE|State|Events") - void OnFixedTick(float OneFrame); + void OnUpdate(float OneFrame); // UObject interface virtual UWorld* GetWorld() const override; diff --git a/Source/UnrealFightingEngine/State/FEStateMachineComponent.cpp b/Source/UnrealFightingEngine/State/FEStateMachineComponent.cpp index 8cac64a..93fae14 100644 --- a/Source/UnrealFightingEngine/State/FEStateMachineComponent.cpp +++ b/Source/UnrealFightingEngine/State/FEStateMachineComponent.cpp @@ -128,7 +128,7 @@ void UFEStateMachineComponent::FixedTick(float OneFrame) check(CurrentState); // Tick current state - CurrentState->FixedTick(OneFrame); + CurrentState->Update(OneFrame); // Debug } diff --git a/Source/UnrealFightingEngine/State/FEStateMachineComponent.h b/Source/UnrealFightingEngine/State/FEStateMachineComponent.h index 5cb2428..8ca6ab4 100644 --- a/Source/UnrealFightingEngine/State/FEStateMachineComponent.h +++ b/Source/UnrealFightingEngine/State/FEStateMachineComponent.h @@ -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) ) -class UNREALFIGHTINGENGINE_API UFEStateMachineComponent : public UActorComponent, public IIFESystemInterface +class UNREALFIGHTINGENGINE_API UFEStateMachineComponent : public UActorComponent, public IFESystemInterface { GENERATED_BODY() @@ -74,9 +74,9 @@ public: UFUNCTION(BlueprintPure) FName GetCurrentStateName() const; - // IIFESystemInterface interface + // IFESystemInterface interface virtual void FixedTick(float OneFrame) override; - // End of IIFESystemInterface interface + // End of IFESystemInterface interface protected: /** From 29e84cec80d612863b2e78296cbbb5278bc656b6 Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Sat, 10 Jun 2023 13:57:51 -0400 Subject: [PATCH 06/38] Include enhanced input --- Source/UnrealFightingEngine/Input/FEPlayerController.h | 4 ++++ Source/UnrealFightingEngine/UnrealFightingEngine.Build.cs | 5 ++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Source/UnrealFightingEngine/Input/FEPlayerController.h b/Source/UnrealFightingEngine/Input/FEPlayerController.h index ee6d711..d946da7 100644 --- a/Source/UnrealFightingEngine/Input/FEPlayerController.h +++ b/Source/UnrealFightingEngine/Input/FEPlayerController.h @@ -30,6 +30,10 @@ public: // End of APlayerController interface protected: + /** MappingContext */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UFE|Input", meta = (AllowPrivateAccess = "true")) + class UInputMappingContext* DefaultMappingContext; + /** Input Buffer component */ UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "UFE|Input", meta = (AllowPrivateAccess = "true")) UFEInputBufferComponent* InputBuffer; diff --git a/Source/UnrealFightingEngine/UnrealFightingEngine.Build.cs b/Source/UnrealFightingEngine/UnrealFightingEngine.Build.cs index 49e3e54..29ab61e 100644 --- a/Source/UnrealFightingEngine/UnrealFightingEngine.Build.cs +++ b/Source/UnrealFightingEngine/UnrealFightingEngine.Build.cs @@ -1,4 +1,4 @@ -// Some copyright should be here... +// Unreal Fighting Engine by Kevin Poretti using UnrealBuildTool; @@ -26,6 +26,7 @@ public class UnrealFightingEngine : ModuleRules new string[] { "Core", + "EnhancedInput", // ... add other public dependencies that you statically link with here ... } ); @@ -36,8 +37,6 @@ public class UnrealFightingEngine : ModuleRules { "CoreUObject", "Engine", - "Slate", - "SlateCore", // ... add private dependencies that you statically link with here ... } ); From 0265ce246cbd6212d9e26f5d0ab9498dfab3255c Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Sat, 10 Jun 2023 13:57:51 -0400 Subject: [PATCH 07/38] Include enhanced input --- Source/UnrealFightingEngine/Input/FEPlayerController.h | 4 ++++ Source/UnrealFightingEngine/UnrealFightingEngine.Build.cs | 5 ++--- UnrealFightingEngine.uplugin | 6 ++++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Source/UnrealFightingEngine/Input/FEPlayerController.h b/Source/UnrealFightingEngine/Input/FEPlayerController.h index ee6d711..d946da7 100644 --- a/Source/UnrealFightingEngine/Input/FEPlayerController.h +++ b/Source/UnrealFightingEngine/Input/FEPlayerController.h @@ -30,6 +30,10 @@ public: // End of APlayerController interface protected: + /** MappingContext */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UFE|Input", meta = (AllowPrivateAccess = "true")) + class UInputMappingContext* DefaultMappingContext; + /** Input Buffer component */ UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "UFE|Input", meta = (AllowPrivateAccess = "true")) UFEInputBufferComponent* InputBuffer; diff --git a/Source/UnrealFightingEngine/UnrealFightingEngine.Build.cs b/Source/UnrealFightingEngine/UnrealFightingEngine.Build.cs index 49e3e54..29ab61e 100644 --- a/Source/UnrealFightingEngine/UnrealFightingEngine.Build.cs +++ b/Source/UnrealFightingEngine/UnrealFightingEngine.Build.cs @@ -1,4 +1,4 @@ -// Some copyright should be here... +// Unreal Fighting Engine by Kevin Poretti using UnrealBuildTool; @@ -26,6 +26,7 @@ public class UnrealFightingEngine : ModuleRules new string[] { "Core", + "EnhancedInput", // ... add other public dependencies that you statically link with here ... } ); @@ -36,8 +37,6 @@ public class UnrealFightingEngine : ModuleRules { "CoreUObject", "Engine", - "Slate", - "SlateCore", // ... add private dependencies that you statically link with here ... } ); diff --git a/UnrealFightingEngine.uplugin b/UnrealFightingEngine.uplugin index 0b3ea6d..67b6d89 100644 --- a/UnrealFightingEngine.uplugin +++ b/UnrealFightingEngine.uplugin @@ -20,5 +20,11 @@ "Type": "Runtime", "LoadingPhase": "PreLoadingScreen" } + ], + "Plugins": [ + { + "Name": "EnhancedInput", + "Enabled": true + } ] } \ No newline at end of file From e38379586c13a5689b421c222f219d25c08c8203 Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Sat, 10 Jun 2023 14:20:41 -0400 Subject: [PATCH 08/38] Rename to Unreal Fighting Framework --- README.md | 2 +- .../IFESystemInterface.cpp | 4 -- .../Input/FEInputBufferComponent.cpp | 8 --- .../Input/FEInputBufferComponent.h | 18 ------ .../Input/FEPlayerController.cpp | 20 ------ Source/UnrealFightingEngine/State/FEState.cpp | 61 ------------------- .../UnrealFightingEngineBPLibrary.cpp | 16 ----- .../IFFSystemInterface.cpp | 4 ++ .../IFFSystemInterface.h} | 9 +-- .../Input/FFInputBufferComponent.cpp | 8 +++ .../Input/FFInputBufferComponent.h | 18 ++++++ .../Input/FFPlayerController.cpp | 20 ++++++ .../Input/FFPlayerController.h} | 14 ++--- .../UnrealFightingFramework/State/FFState.cpp | 61 +++++++++++++++++++ .../State/FFState.h} | 16 ++--- .../State/FFStateMachineComponent.cpp} | 44 ++++++------- .../State/FFStateMachineComponent.h} | 32 +++++----- .../UnrealFightingFramework.Build.cs} | 6 +- .../UnrealFightingFrameworkBPLibrary.cpp | 16 +++++ .../UnrealFightingFrameworkBPLibrary.h} | 8 +-- .../UnrealFightingFrameworkModule.cpp} | 10 +-- .../UnrealFightingFrameworkModule.h} | 2 +- ...uplugin => UnrealFightingFramework.uplugin | 4 +- 23 files changed, 201 insertions(+), 200 deletions(-) delete mode 100644 Source/UnrealFightingEngine/IFESystemInterface.cpp delete mode 100644 Source/UnrealFightingEngine/Input/FEInputBufferComponent.cpp delete mode 100644 Source/UnrealFightingEngine/Input/FEInputBufferComponent.h delete mode 100644 Source/UnrealFightingEngine/Input/FEPlayerController.cpp delete mode 100644 Source/UnrealFightingEngine/State/FEState.cpp delete mode 100644 Source/UnrealFightingEngine/UnrealFightingEngineBPLibrary.cpp create mode 100644 Source/UnrealFightingFramework/IFFSystemInterface.cpp rename Source/{UnrealFightingEngine/IFESystemInterface.h => UnrealFightingFramework/IFFSystemInterface.h} (77%) create mode 100644 Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp create mode 100644 Source/UnrealFightingFramework/Input/FFInputBufferComponent.h create mode 100644 Source/UnrealFightingFramework/Input/FFPlayerController.cpp rename Source/{UnrealFightingEngine/Input/FEPlayerController.h => UnrealFightingFramework/Input/FFPlayerController.h} (68%) create mode 100644 Source/UnrealFightingFramework/State/FFState.cpp rename Source/{UnrealFightingEngine/State/FEState.h => UnrealFightingFramework/State/FFState.h} (84%) rename Source/{UnrealFightingEngine/State/FEStateMachineComponent.cpp => UnrealFightingFramework/State/FFStateMachineComponent.cpp} (61%) rename Source/{UnrealFightingEngine/State/FEStateMachineComponent.h => UnrealFightingFramework/State/FFStateMachineComponent.h} (78%) rename Source/{UnrealFightingEngine/UnrealFightingEngine.Build.cs => UnrealFightingFramework/UnrealFightingFramework.Build.cs} (83%) create mode 100644 Source/UnrealFightingFramework/UnrealFightingFrameworkBPLibrary.cpp rename Source/{UnrealFightingEngine/UnrealFightingEngineBPLibrary.h => UnrealFightingFramework/UnrealFightingFrameworkBPLibrary.h} (81%) rename Source/{UnrealFightingEngine/UnrealFightingEngineModule.cpp => UnrealFightingFramework/UnrealFightingFrameworkModule.cpp} (58%) rename Source/{UnrealFightingEngine/UnrealFightingEngineModule.h => UnrealFightingFramework/UnrealFightingFrameworkModule.h} (79%) rename UnrealFightingEngine.uplugin => UnrealFightingFramework.uplugin (88%) diff --git a/README.md b/README.md index 601c0b4..1daa9ed 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# UnrealFightingEngine +# UnrealFightingFramework This library provides actors, components, and other general data structures that are useful for developing character action or fighting games. diff --git a/Source/UnrealFightingEngine/IFESystemInterface.cpp b/Source/UnrealFightingEngine/IFESystemInterface.cpp deleted file mode 100644 index 41c7cb9..0000000 --- a/Source/UnrealFightingEngine/IFESystemInterface.cpp +++ /dev/null @@ -1,4 +0,0 @@ -// Unreal Fighting Engine by Kevin Poretti - - -#include "IFESystemInterface.h" diff --git a/Source/UnrealFightingEngine/Input/FEInputBufferComponent.cpp b/Source/UnrealFightingEngine/Input/FEInputBufferComponent.cpp deleted file mode 100644 index bc28f2d..0000000 --- a/Source/UnrealFightingEngine/Input/FEInputBufferComponent.cpp +++ /dev/null @@ -1,8 +0,0 @@ -// Unreal Fighting Engine by Kevin Poretti - -#include "FEInputBufferComponent.h" - -UFEInputBufferComponent::UFEInputBufferComponent() -{ - PrimaryComponentTick.bCanEverTick = false; -} diff --git a/Source/UnrealFightingEngine/Input/FEInputBufferComponent.h b/Source/UnrealFightingEngine/Input/FEInputBufferComponent.h deleted file mode 100644 index 30e925a..0000000 --- a/Source/UnrealFightingEngine/Input/FEInputBufferComponent.h +++ /dev/null @@ -1,18 +0,0 @@ -// Unreal Fighting Engine by Kevin Poretti - -#pragma once - -// UE includes -#include "CoreMinimal.h" -#include "Components/ActorComponent.h" - -#include "FEInputBufferComponent.generated.h" - -UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) -class UNREALFIGHTINGENGINE_API UFEInputBufferComponent : public UActorComponent -{ - GENERATED_BODY() - -public: - UFEInputBufferComponent(); -}; diff --git a/Source/UnrealFightingEngine/Input/FEPlayerController.cpp b/Source/UnrealFightingEngine/Input/FEPlayerController.cpp deleted file mode 100644 index ea1dba5..0000000 --- a/Source/UnrealFightingEngine/Input/FEPlayerController.cpp +++ /dev/null @@ -1,20 +0,0 @@ -// Unreal Fighting Engine by Kevin Poretti - -#include "FEPlayerController.h" - -// FE includes -#include "FEInputBufferComponent.h" - -AFEPlayerController::AFEPlayerController() -{ - InputBuffer = CreateDefaultSubobject(TEXT("InputBuffer")); -} - -void AFEPlayerController::SendInputsToRemote() const -{ -} - -void AFEPlayerController::SetupInputComponent() -{ - Super::SetupInputComponent(); -} diff --git a/Source/UnrealFightingEngine/State/FEState.cpp b/Source/UnrealFightingEngine/State/FEState.cpp deleted file mode 100644 index f52c68f..0000000 --- a/Source/UnrealFightingEngine/State/FEState.cpp +++ /dev/null @@ -1,61 +0,0 @@ -// Unreal Fighting Engine by Kevin Poretti - -#include "FEState.h" - -// FE includes -#include "FEStateMachineComponent.h" - -void UFEState::InitActorInfo(AActor* InOwner, AActor* InAvatar) -{ - Owner = InOwner; - Avatar = InAvatar; -} - - -void UFEState::Enter() -{ - TicksInState = 0; - - OnEnter(); -} - - -void UFEState::Exit() -{ - OnExit(); -} - - -void UFEState::Update(float OneFrame) -{ - TicksInState++; - - OnUpdate(OneFrame); -} - - -void UFEState::OnEnter_Implementation() -{ -} - - -void UFEState::OnExit_Implementation() -{ -} - - -void UFEState::OnUpdate_Implementation(float OneFrame) -{ -} - - -UWorld* UFEState::GetWorld() const -{ - UFEStateMachineComponent* SMC = Cast(GetOuter()); - if(SMC) - { - return SMC->GetWorld(); - } - - return nullptr; -} \ No newline at end of file diff --git a/Source/UnrealFightingEngine/UnrealFightingEngineBPLibrary.cpp b/Source/UnrealFightingEngine/UnrealFightingEngineBPLibrary.cpp deleted file mode 100644 index f924e4a..0000000 --- a/Source/UnrealFightingEngine/UnrealFightingEngineBPLibrary.cpp +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#include "UnrealFightingEngineBPLibrary.h" -#include "UnrealFightingEngineModule.h" - -UUnrealFightingEngineBPLibrary::UUnrealFightingEngineBPLibrary(const FObjectInitializer& ObjectInitializer) -: Super(ObjectInitializer) -{ - -} - -float UUnrealFightingEngineBPLibrary::UnrealFightingEngineSampleFunction(float Param) -{ - return -1; -} - diff --git a/Source/UnrealFightingFramework/IFFSystemInterface.cpp b/Source/UnrealFightingFramework/IFFSystemInterface.cpp new file mode 100644 index 0000000..7be46d6 --- /dev/null +++ b/Source/UnrealFightingFramework/IFFSystemInterface.cpp @@ -0,0 +1,4 @@ +// Unreal Fighting Framework by Kevin Poretti + + +#include "IFFSystemInterface.h" diff --git a/Source/UnrealFightingEngine/IFESystemInterface.h b/Source/UnrealFightingFramework/IFFSystemInterface.h similarity index 77% rename from Source/UnrealFightingEngine/IFESystemInterface.h rename to Source/UnrealFightingFramework/IFFSystemInterface.h index c0153c7..f08cef1 100644 --- a/Source/UnrealFightingEngine/IFESystemInterface.h +++ b/Source/UnrealFightingFramework/IFFSystemInterface.h @@ -1,15 +1,16 @@ -// Unreal Fighting Engine by Kevin Poretti +// Unreal Fighting Framework by Kevin Poretti #pragma once +// UE includes #include "CoreMinimal.h" #include "UObject/Interface.h" -#include "IFESystemInterface.generated.h" +#include "IFFSystemInterface.generated.h" // This class does not need to be modified. UINTERFACE(MinimalAPI) -class UFESystemInterface : public UInterface +class UFFSystemInterface : public UInterface { GENERATED_BODY() }; @@ -20,7 +21,7 @@ class UFESystemInterface : public UInterface * This ensures all gameplay effecting objects can be assumed to have certain properties that enable serialization, networking and * some form of determinism. */ -class UNREALFIGHTINGENGINE_API IFESystemInterface +class UNREALFIGHTINGFRAMEWORK_API IFFSystemInterface { GENERATED_BODY() diff --git a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp new file mode 100644 index 0000000..604ffbe --- /dev/null +++ b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp @@ -0,0 +1,8 @@ +// Unreal Fighting Framework by Kevin Poretti + +#include "FFInputBufferComponent.h" + +UFFInputBufferComponent::UFFInputBufferComponent() +{ + PrimaryComponentTick.bCanEverTick = false; +} diff --git a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h new file mode 100644 index 0000000..05ca9d5 --- /dev/null +++ b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h @@ -0,0 +1,18 @@ +// Unreal Fighting Framework by Kevin Poretti + +#pragma once + +// UE includes +#include "CoreMinimal.h" +#include "Components/ActorComponent.h" + +#include "FFInputBufferComponent.generated.h" + +UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) +class UNREALFIGHTINGFRAMEWORK_API UFFInputBufferComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + UFFInputBufferComponent(); +}; diff --git a/Source/UnrealFightingFramework/Input/FFPlayerController.cpp b/Source/UnrealFightingFramework/Input/FFPlayerController.cpp new file mode 100644 index 0000000..69327b3 --- /dev/null +++ b/Source/UnrealFightingFramework/Input/FFPlayerController.cpp @@ -0,0 +1,20 @@ +// Unreal Fighting Framework by Kevin Poretti + +#include "FFPlayerController.h" + +// FF includes +#include "FFInputBufferComponent.h" + +AFFPlayerController::AFFPlayerController() +{ + InputBuffer = CreateDefaultSubobject(TEXT("InputBuffer")); +} + +void AFFPlayerController::SendInputsToRemote() const +{ +} + +void AFFPlayerController::SetupInputComponent() +{ + Super::SetupInputComponent(); +} diff --git a/Source/UnrealFightingEngine/Input/FEPlayerController.h b/Source/UnrealFightingFramework/Input/FFPlayerController.h similarity index 68% rename from Source/UnrealFightingEngine/Input/FEPlayerController.h rename to Source/UnrealFightingFramework/Input/FFPlayerController.h index d946da7..8420133 100644 --- a/Source/UnrealFightingEngine/Input/FEPlayerController.h +++ b/Source/UnrealFightingFramework/Input/FFPlayerController.h @@ -1,4 +1,4 @@ -// Unreal Fighting Engine by Kevin Poretti +// Unreal Fighting Framework by Kevin Poretti #pragma once @@ -6,19 +6,19 @@ #include "CoreMinimal.h" #include "GameFramework/PlayerController.h" -#include "FEPlayerController.generated.h" +#include "FFPlayerController.generated.h" /** * A class that collects player inputs, stores them in an input buffer, and sends a rolling window of * unacknowledged inputs to a remote client or server for processing. */ UCLASS() -class UNREALFIGHTINGENGINE_API AFEPlayerController : public APlayerController +class UNREALFIGHTINGFRAMEWORK_API AFFPlayerController : public APlayerController { GENERATED_BODY() public: - AFEPlayerController(); + AFFPlayerController(); /** * Sends all unacknowledged inputs to the remote @@ -31,12 +31,12 @@ public: protected: /** MappingContext */ - UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UFE|Input", meta = (AllowPrivateAccess = "true")) + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UFF|Input", meta = (AllowPrivateAccess = "true")) class UInputMappingContext* DefaultMappingContext; /** Input Buffer component */ - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "UFE|Input", meta = (AllowPrivateAccess = "true")) - UFEInputBufferComponent* InputBuffer; + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "UFF|Input", meta = (AllowPrivateAccess = "true")) + UFFInputBufferComponent* InputBuffer; int32 Inputs; diff --git a/Source/UnrealFightingFramework/State/FFState.cpp b/Source/UnrealFightingFramework/State/FFState.cpp new file mode 100644 index 0000000..7240cbc --- /dev/null +++ b/Source/UnrealFightingFramework/State/FFState.cpp @@ -0,0 +1,61 @@ +// Unreal Fighting Framework by Kevin Poretti + +#include "FFState.h" + +// FF includes +#include "FFStateMachineComponent.h" + +void UFFState::InitActorInfo(AActor* InOwner, AActor* InAvatar) +{ + Owner = InOwner; + Avatar = InAvatar; +} + + +void UFFState::Enter() +{ + TicksInState = 0; + + OnEnter(); +} + + +void UFFState::Exit() +{ + OnExit(); +} + + +void UFFState::Update(float OneFrame) +{ + TicksInState++; + + OnUpdate(OneFrame); +} + + +void UFFState::OnEnter_Implementation() +{ +} + + +void UFFState::OnExit_Implementation() +{ +} + + +void UFFState::OnUpdate_Implementation(float OneFrame) +{ +} + + +UWorld* UFFState::GetWorld() const +{ + UFFStateMachineComponent* SMC = Cast(GetOuter()); + if(SMC) + { + return SMC->GetWorld(); + } + + return nullptr; +} \ No newline at end of file diff --git a/Source/UnrealFightingEngine/State/FEState.h b/Source/UnrealFightingFramework/State/FFState.h similarity index 84% rename from Source/UnrealFightingEngine/State/FEState.h rename to Source/UnrealFightingFramework/State/FFState.h index 2db6c2e..bc0073c 100644 --- a/Source/UnrealFightingEngine/State/FEState.h +++ b/Source/UnrealFightingFramework/State/FFState.h @@ -1,18 +1,18 @@ -// Unreal Fighting Engine by Kevin Poretti +// Unreal Fighting Framework by Kevin Poretti #pragma once // UE includes #include "CoreMinimal.h" -#include "FEState.generated.h" +#include "FFState.generated.h" /** * 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. */ UCLASS() -class UNREALFIGHTINGENGINE_API UFEState : public UObject +class UNREALFIGHTINGFRAMEWORK_API UFFState : public UObject { GENERATED_BODY() @@ -27,7 +27,7 @@ public: virtual void InitActorInfo(AActor* InOwner, AActor* InAvatar); /** Name of this state */ - UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="UFE|State") + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="UFF|State") FName Name; /** Conditions that need to be met in order for this state to be transitioned into */ @@ -39,7 +39,7 @@ public: uint8 StateType; /** How many ticks have elapsed since this state was entered */ - UPROPERTY(BlueprintReadOnly, Category="UFE|State") + UPROPERTY(BlueprintReadOnly, Category="UFF|State") int32 TicksInState; /** @@ -66,13 +66,13 @@ public: /** * Blueprint hook that is called whenever this state is transitioned into */ - UFUNCTION(BlueprintNativeEvent, Category="UFE|State|Events") + UFUNCTION(BlueprintNativeEvent, Category="UFF|State|Events") void OnEnter(); /** * Blueprint hook that is called whenever this state is transitioned out of into a new state */ - UFUNCTION(BlueprintNativeEvent, Category="UFE|State|Events") + UFUNCTION(BlueprintNativeEvent, Category="UFF|State|Events") void OnExit(); /** @@ -80,7 +80,7 @@ public: * * @param OneFrame the time that elapses during one fixed tick */ - UFUNCTION(BlueprintNativeEvent, Category="UFE|State|Events") + UFUNCTION(BlueprintNativeEvent, Category="UFF|State|Events") void OnUpdate(float OneFrame); // UObject interface diff --git a/Source/UnrealFightingEngine/State/FEStateMachineComponent.cpp b/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp similarity index 61% rename from Source/UnrealFightingEngine/State/FEStateMachineComponent.cpp rename to Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp index 93fae14..9c74dd9 100644 --- a/Source/UnrealFightingEngine/State/FEStateMachineComponent.cpp +++ b/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp @@ -1,22 +1,22 @@ -// Unreal Fighting Engine by Kevin Poretti +// Unreal Fighting Framework by Kevin Poretti -#include "FEStateMachineComponent.h" +#include "FFStateMachineComponent.h" -// FE includes -#include "FEState.h" +// FF includes +#include "FFState.h" -UFEStateMachineComponent::UFEStateMachineComponent() +UFFStateMachineComponent::UFFStateMachineComponent() { // Don't use Unreal's tick instead use a fixed tick PrimaryComponentTick.bCanEverTick = false; } -void UFEStateMachineComponent::Initialize() +void UFFStateMachineComponent::Initialize() { - for(const TSubclassOf& CurrState : DefaultStates) + for(const TSubclassOf& CurrState : DefaultStates) { - UFEState* TempState = AddState(CurrState); + UFFState* TempState = AddState(CurrState); if(!CurrentState) // first state to be created is the entry into this state machine { CurrentState = TempState; @@ -26,16 +26,16 @@ void UFEStateMachineComponent::Initialize() } -void UFEStateMachineComponent::InitActorInfo(AActor* InOwner, AActor* InAvatar) +void UFFStateMachineComponent::InitActorInfo(AActor* InOwner, AActor* InAvatar) { Owner = InOwner; Avatar = InAvatar; } -UFEState* UFEStateMachineComponent::AddState(TSubclassOf StateClassToAdd) +UFFState* UFFStateMachineComponent::AddState(TSubclassOf StateClassToAdd) { - UFEState* TempState = NewObject(this, StateClassToAdd); + UFFState* TempState = NewObject(this, StateClassToAdd); if(TempState) { States.Add(TempState); @@ -47,21 +47,21 @@ UFEState* UFEStateMachineComponent::AddState(TSubclassOf StateClassToA } -void UFEStateMachineComponent::AddStates(const TArray>& StateClassesToAdd) +void UFFStateMachineComponent::AddStates(const TArray>& StateClassesToAdd) { - for(const TSubclassOf& CurrState : StateClassesToAdd) + for(const TSubclassOf& CurrState : StateClassesToAdd) { AddState(CurrState); } } -void UFEStateMachineComponent::RemoveState(FName StateToRemove) +void UFFStateMachineComponent::RemoveState(FName StateToRemove) { - UE_LOG(LogTemp, Error, TEXT("UFEStateMachineComponent::RemoveState is not yet implemented")); + UE_LOG(LogTemp, Error, TEXT("UFFStateMachineComponent::RemoveState is not yet implemented")); } -void UFEStateMachineComponent::SwitchStates(UFEState* NewState) +void UFFStateMachineComponent::SwitchStates(UFFState* NewState) { check(NewState); @@ -71,15 +71,15 @@ void UFEStateMachineComponent::SwitchStates(UFEState* NewState) } -FName UFEStateMachineComponent::GetCurrentStateName() const +FName UFFStateMachineComponent::GetCurrentStateName() const { return CurrentState ? CurrentState->Name : NAME_None; } -UFEState* UFEStateMachineComponent::FindStateWithName(FName StateName) +UFFState* UFFStateMachineComponent::FindStateWithName(FName StateName) { - for (UFEState* CurrState : States) + for (UFFState* CurrState : States) { if(CurrState ->Name == StateName) { @@ -94,7 +94,7 @@ UFEState* UFEStateMachineComponent::FindStateWithName(FName StateName) } -void UFEStateMachineComponent::BeginPlay() +void UFFStateMachineComponent::BeginPlay() { Super::BeginPlay(); @@ -102,11 +102,11 @@ void UFEStateMachineComponent::BeginPlay() } -void UFEStateMachineComponent::FixedTick(float OneFrame) +void UFFStateMachineComponent::FixedTick(float OneFrame) { // Should we switch states? - for(UFEState* CurrState : States) + for(UFFState* CurrState : States) { // Check if the state is enabled diff --git a/Source/UnrealFightingEngine/State/FEStateMachineComponent.h b/Source/UnrealFightingFramework/State/FFStateMachineComponent.h similarity index 78% rename from Source/UnrealFightingEngine/State/FEStateMachineComponent.h rename to Source/UnrealFightingFramework/State/FFStateMachineComponent.h index 8ca6ab4..2261c88 100644 --- a/Source/UnrealFightingEngine/State/FEStateMachineComponent.h +++ b/Source/UnrealFightingFramework/State/FFStateMachineComponent.h @@ -1,15 +1,15 @@ -// Unreal Fighting Engine by Kevin Poretti +// Unreal Fighting Framework by Kevin Poretti #pragma once -// FE includes -#include "UnrealFightingEngine/IFESystemInterface.h" +// FF includes +#include "UnrealFightingFramework/IFFSystemInterface.h" // UE includes #include "CoreMinimal.h" #include "Components/ActorComponent.h" -#include "FEStateMachineComponent.generated.h" +#include "FFStateMachineComponent.generated.h" /** * A state machine is a component that evaluates and controls the transitions for state objects that @@ -18,12 +18,12 @@ * This component also calls the appropriate state logic when a state is changed or the component ticks. */ UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) -class UNREALFIGHTINGENGINE_API UFEStateMachineComponent : public UActorComponent, public IFESystemInterface +class UNREALFIGHTINGFRAMEWORK_API UFFStateMachineComponent : public UActorComponent, public IFFSystemInterface { GENERATED_BODY() public: - UFEStateMachineComponent(); + UFFStateMachineComponent(); /** * Creates and adds default states and enters the first state @@ -47,14 +47,14 @@ public: * * @return A pointer to the state that was added or nullptr if there was an issue adding or creating the state */ - UFEState* AddState(TSubclassOf StateClassToAdd); + UFFState* AddState(TSubclassOf StateClassToAdd); /** * Creates an instance of the state classes and adds newly created states to this state machine. * * @param StateClassesToAdd Array of state class types to be added to this state machine */ - void AddStates(const TArray>& StateClassesToAdd); + void AddStates(const TArray>& StateClassesToAdd); /** * Destroys the state with corresponding name and removes it from this state machine. @@ -66,7 +66,7 @@ public: * * Triggers the Exit callback on the CurrentState and the Enter callback on the new state */ - void SwitchStates(UFEState* NewState); + void SwitchStates(UFFState* NewState); /** * Returns the name of the current state @@ -74,9 +74,9 @@ public: UFUNCTION(BlueprintPure) FName GetCurrentStateName() const; - // IFESystemInterface interface + // IFFSystemInterface interface virtual void FixedTick(float OneFrame) override; - // End of IFESystemInterface interface + // End of IFFSystemInterface interface protected: /** @@ -96,21 +96,21 @@ protected: /** * States classes to create and add to this state machine when the game starts */ - UPROPERTY(EditDefaultsOnly, Category="UFE|State Machine") - TArray> DefaultStates; + UPROPERTY(EditDefaultsOnly, Category="UFF|State Machine") + TArray> DefaultStates; /** Current active state for this state machine */ UPROPERTY(BlueprintReadOnly) - UFEState* CurrentState; + UFFState* CurrentState; // States that have been added UPROPERTY(BlueprintReadOnly) - TArray States; + TArray States; /** * Returns the state with corresponding name */ - UFEState* FindStateWithName(FName StateName); + UFFState* FindStateWithName(FName StateName); // UActorComponent interface virtual void BeginPlay() override; diff --git a/Source/UnrealFightingEngine/UnrealFightingEngine.Build.cs b/Source/UnrealFightingFramework/UnrealFightingFramework.Build.cs similarity index 83% rename from Source/UnrealFightingEngine/UnrealFightingEngine.Build.cs rename to Source/UnrealFightingFramework/UnrealFightingFramework.Build.cs index 29ab61e..92e65a6 100644 --- a/Source/UnrealFightingEngine/UnrealFightingEngine.Build.cs +++ b/Source/UnrealFightingFramework/UnrealFightingFramework.Build.cs @@ -1,10 +1,10 @@ -// Unreal Fighting Engine by Kevin Poretti +// Unreal Fighting Framework by Kevin Poretti using UnrealBuildTool; -public class UnrealFightingEngine : ModuleRules +public class UnrealFightingFramework : ModuleRules { - public UnrealFightingEngine(ReadOnlyTargetRules Target) : base(Target) + public UnrealFightingFramework(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; diff --git a/Source/UnrealFightingFramework/UnrealFightingFrameworkBPLibrary.cpp b/Source/UnrealFightingFramework/UnrealFightingFrameworkBPLibrary.cpp new file mode 100644 index 0000000..df9ca6d --- /dev/null +++ b/Source/UnrealFightingFramework/UnrealFightingFrameworkBPLibrary.cpp @@ -0,0 +1,16 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "UnrealFightingFrameworkBPLibrary.h" +#include "UnrealFightingFrameworkModule.h" + +UUnrealFightingFrameworkBPLibrary::UUnrealFightingFrameworkBPLibrary(const FObjectInitializer& ObjectInitializer) +: Super(ObjectInitializer) +{ + +} + +float UUnrealFightingFrameworkBPLibrary::UnrealFightingFrameworkSampleFunction(float Param) +{ + return -1; +} + diff --git a/Source/UnrealFightingEngine/UnrealFightingEngineBPLibrary.h b/Source/UnrealFightingFramework/UnrealFightingFrameworkBPLibrary.h similarity index 81% rename from Source/UnrealFightingEngine/UnrealFightingEngineBPLibrary.h rename to Source/UnrealFightingFramework/UnrealFightingFrameworkBPLibrary.h index f524d8b..7d54fae 100644 --- a/Source/UnrealFightingEngine/UnrealFightingEngineBPLibrary.h +++ b/Source/UnrealFightingFramework/UnrealFightingFrameworkBPLibrary.h @@ -3,7 +3,7 @@ #pragma once #include "Kismet/BlueprintFunctionLibrary.h" -#include "UnrealFightingEngineBPLibrary.generated.h" +#include "UnrealFightingFrameworkBPLibrary.generated.h" /* * Function library class. @@ -23,10 +23,10 @@ * https://wiki.unrealengine.com/Custom_Blueprint_Node_Creation */ UCLASS() -class UUnrealFightingEngineBPLibrary : public UBlueprintFunctionLibrary +class UUnrealFightingFrameworkBPLibrary : public UBlueprintFunctionLibrary { GENERATED_UCLASS_BODY() - UFUNCTION(BlueprintCallable, meta = (DisplayName = "Execute Sample function", Keywords = "UnrealFightingEngine sample test testing"), Category = "UnrealFightingEngineTesting") - static float UnrealFightingEngineSampleFunction(float Param); + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Execute Sample function", Keywords = "UnrealFightingFramework sample test testing"), Category = "UnrealFightingFrameworkTesting") + static float UnrealFightingFrameworkSampleFunction(float Param); }; diff --git a/Source/UnrealFightingEngine/UnrealFightingEngineModule.cpp b/Source/UnrealFightingFramework/UnrealFightingFrameworkModule.cpp similarity index 58% rename from Source/UnrealFightingEngine/UnrealFightingEngineModule.cpp rename to Source/UnrealFightingFramework/UnrealFightingFrameworkModule.cpp index fc66106..c74be02 100644 --- a/Source/UnrealFightingEngine/UnrealFightingEngineModule.cpp +++ b/Source/UnrealFightingFramework/UnrealFightingFrameworkModule.cpp @@ -1,16 +1,16 @@ // Copyright Epic Games, Inc. All Rights Reserved. -#include "UnrealFightingEngineModule.h" +#include "UnrealFightingFrameworkModule.h" -#define LOCTEXT_NAMESPACE "FUnrealFightingEngineModule" +#define LOCTEXT_NAMESPACE "FUnrealFightingFrameworkModule" -void FUnrealFightingEngineModule::StartupModule() +void FUnrealFightingFrameworkModule::StartupModule() { // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module } -void FUnrealFightingEngineModule::ShutdownModule() +void FUnrealFightingFrameworkModule::ShutdownModule() { // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, // we call this function before unloading the module. @@ -19,4 +19,4 @@ void FUnrealFightingEngineModule::ShutdownModule() #undef LOCTEXT_NAMESPACE -IMPLEMENT_MODULE(FUnrealFightingEngineModule, UnrealFightingEngine) \ No newline at end of file +IMPLEMENT_MODULE(FUnrealFightingFrameworkModule, UnrealFightingFramework) \ No newline at end of file diff --git a/Source/UnrealFightingEngine/UnrealFightingEngineModule.h b/Source/UnrealFightingFramework/UnrealFightingFrameworkModule.h similarity index 79% rename from Source/UnrealFightingEngine/UnrealFightingEngineModule.h rename to Source/UnrealFightingFramework/UnrealFightingFrameworkModule.h index c763149..d65f51e 100644 --- a/Source/UnrealFightingEngine/UnrealFightingEngineModule.h +++ b/Source/UnrealFightingFramework/UnrealFightingFrameworkModule.h @@ -4,7 +4,7 @@ #include "Modules/ModuleManager.h" -class FUnrealFightingEngineModule : public IModuleInterface +class FUnrealFightingFrameworkModule : public IModuleInterface { public: diff --git a/UnrealFightingEngine.uplugin b/UnrealFightingFramework.uplugin similarity index 88% rename from UnrealFightingEngine.uplugin rename to UnrealFightingFramework.uplugin index 67b6d89..6bb148b 100644 --- a/UnrealFightingEngine.uplugin +++ b/UnrealFightingFramework.uplugin @@ -2,7 +2,7 @@ "FileVersion": 3, "Version": 1, "VersionName": "1.0", - "FriendlyName": "UnrealFightingEngine", + "FriendlyName": "UnrealFightingFramework", "Description": "This library provides actors, components, and other general data structures that are useful for developing character action or fighting games.", "Category": "Other", "CreatedBy": "Kevin Poretti", @@ -16,7 +16,7 @@ "Installed": false, "Modules": [ { - "Name": "UnrealFightingEngine", + "Name": "UnrealFightingFramework", "Type": "Runtime", "LoadingPhase": "PreLoadingScreen" } From f31f0437e9169e1cf6804c89a6f9ae3bfef067f2 Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Sat, 10 Jun 2023 19:23:21 -0400 Subject: [PATCH 09/38] Setup input mapping in player controller --- .../Input/FFPlayerController.cpp | 16 ++++++++++++++++ .../Input/FFPlayerController.h | 8 ++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/Source/UnrealFightingFramework/Input/FFPlayerController.cpp b/Source/UnrealFightingFramework/Input/FFPlayerController.cpp index 69327b3..a1a75e7 100644 --- a/Source/UnrealFightingFramework/Input/FFPlayerController.cpp +++ b/Source/UnrealFightingFramework/Input/FFPlayerController.cpp @@ -5,16 +5,32 @@ // FF includes #include "FFInputBufferComponent.h" +// UE includes +#include "EnhancedInputSubsystems.h" + AFFPlayerController::AFFPlayerController() { InputBuffer = CreateDefaultSubobject(TEXT("InputBuffer")); } + void AFFPlayerController::SendInputsToRemote() const { } + void AFFPlayerController::SetupInputComponent() { Super::SetupInputComponent(); + + if (ULocalPlayer* LocalPlayer = Cast(Player)) + { + if (UEnhancedInputLocalPlayerSubsystem* InputSystem = LocalPlayer->GetSubsystem()) + { + if (!DefaultInputMapping.IsNull()) + { + InputSystem->AddMappingContext(DefaultInputMapping.LoadSynchronous(), 0); + } + } + } } diff --git a/Source/UnrealFightingFramework/Input/FFPlayerController.h b/Source/UnrealFightingFramework/Input/FFPlayerController.h index 8420133..0c4bba3 100644 --- a/Source/UnrealFightingFramework/Input/FFPlayerController.h +++ b/Source/UnrealFightingFramework/Input/FFPlayerController.h @@ -2,9 +2,13 @@ #pragma once +// FF includes +#include "FFInputBufferComponent.h" + // UE includes #include "CoreMinimal.h" #include "GameFramework/PlayerController.h" +#include "InputMappingContext.h" #include "FFPlayerController.generated.h" @@ -23,7 +27,7 @@ public: /** * Sends all unacknowledged inputs to the remote */ - void SendInputsToRemote() const; + virtual void SendInputsToRemote() const; // APlayerController interface virtual void SetupInputComponent() override; @@ -32,7 +36,7 @@ public: protected: /** MappingContext */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UFF|Input", meta = (AllowPrivateAccess = "true")) - class UInputMappingContext* DefaultMappingContext; + TSoftObjectPtr DefaultInputMapping; /** Input Buffer component */ UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "UFF|Input", meta = (AllowPrivateAccess = "true")) From ec6daa06fb67fdbad2eb2d62de1908216a96156e Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Sun, 11 Jun 2023 17:45:58 -0400 Subject: [PATCH 10/38] Define functions and members for integrating player controller with input buffer --- .../Input/FFInputBufferComponent.cpp | 14 ++++++++ .../Input/FFInputBufferComponent.h | 20 +++++++++++ .../Input/FFPlayerController.cpp | 9 +++++ .../Input/FFPlayerController.h | 34 +++++++++++++++++-- .../UnrealFightingFramework.Build.cs | 4 +-- .../Utils/TCircleBuffer.cpp | 11 ++++++ .../Utils/TCircleBuffer.h | 15 ++++++++ 7 files changed, 102 insertions(+), 5 deletions(-) create mode 100644 Source/UnrealFightingFramework/Utils/TCircleBuffer.cpp create mode 100644 Source/UnrealFightingFramework/Utils/TCircleBuffer.h diff --git a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp index 604ffbe..e4e7c97 100644 --- a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp +++ b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp @@ -6,3 +6,17 @@ UFFInputBufferComponent::UFFInputBufferComponent() { PrimaryComponentTick.bCanEverTick = false; } + +void UFFInputBufferComponent::Initialize(int32 BufferSize) +{ + Buffer.Reserve(BufferSize); +} + +void UFFInputBufferComponent::AddInput(const FFFInputState& InputState) +{ +} + +bool UFFInputBufferComponent::CheckInputSequence(const FFFInputSequence& InputSequence) +{ + return true; +} diff --git a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h index 05ca9d5..b425dbd 100644 --- a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h +++ b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h @@ -5,9 +5,19 @@ // UE includes #include "CoreMinimal.h" #include "Components/ActorComponent.h" +#include "Containers/RingBuffer.h" #include "FFInputBufferComponent.generated.h" +struct FFFInputState; + +USTRUCT() +struct FFFInputSequence +{ + GENERATED_BODY() + +}; + UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) class UNREALFIGHTINGFRAMEWORK_API UFFInputBufferComponent : public UActorComponent { @@ -15,4 +25,14 @@ class UNREALFIGHTINGFRAMEWORK_API UFFInputBufferComponent : public UActorCompone public: UFFInputBufferComponent(); + + void Initialize(int32 BufferSize = 120); + + void AddInput(const FFFInputState& InputState); + + bool CheckInputSequence(const FFFInputSequence& InputSequence); + +protected: + /** The underlying buffer data structure for holding past input states */ + TRingBuffer Buffer; }; diff --git a/Source/UnrealFightingFramework/Input/FFPlayerController.cpp b/Source/UnrealFightingFramework/Input/FFPlayerController.cpp index a1a75e7..d55b53e 100644 --- a/Source/UnrealFightingFramework/Input/FFPlayerController.cpp +++ b/Source/UnrealFightingFramework/Input/FFPlayerController.cpp @@ -19,6 +19,15 @@ void AFFPlayerController::SendInputsToRemote() const } +void AFFPlayerController::FixedTick(float OneFrame) +{ + UnacknowledgedInputs.Add(CurrInput); + InputBuffer->AddInput(CurrInput); + + SendInputsToRemote(); +} + + void AFFPlayerController::SetupInputComponent() { Super::SetupInputComponent(); diff --git a/Source/UnrealFightingFramework/Input/FFPlayerController.h b/Source/UnrealFightingFramework/Input/FFPlayerController.h index 0c4bba3..bf74650 100644 --- a/Source/UnrealFightingFramework/Input/FFPlayerController.h +++ b/Source/UnrealFightingFramework/Input/FFPlayerController.h @@ -4,20 +4,35 @@ // FF includes #include "FFInputBufferComponent.h" +#include "UnrealFightingFramework/IFFSystemInterface.h" // UE includes #include "CoreMinimal.h" #include "GameFramework/PlayerController.h" #include "InputMappingContext.h" +#include "Containers/RingBuffer.h" #include "FFPlayerController.generated.h" +/** + * Struct representing the state of a player's inputs for one frame + */ +USTRUCT() +struct FFFInputState +{ + GENERATED_BODY() + + FVector2D MoveAxes; + FVector2D LookAxes; + int32 Buttons; +}; + /** * A class that collects player inputs, stores them in an input buffer, and sends a rolling window of * unacknowledged inputs to a remote client or server for processing. */ UCLASS() -class UNREALFIGHTINGFRAMEWORK_API AFFPlayerController : public APlayerController +class UNREALFIGHTINGFRAMEWORK_API AFFPlayerController : public APlayerController, public IFFSystemInterface { GENERATED_BODY() @@ -29,6 +44,10 @@ public: */ virtual void SendInputsToRemote() const; + // IFFSystemInterface interface + virtual void FixedTick(float OneFrame) override; + // End of IFFSystemInterface + // APlayerController interface virtual void SetupInputComponent() override; // End of APlayerController interface @@ -42,7 +61,16 @@ protected: UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "UFF|Input", meta = (AllowPrivateAccess = "true")) UFFInputBufferComponent* InputBuffer; - int32 Inputs; + /** Current state of the player's inputs */ + FFFInputState CurrInput; - TArray UnacknowledgedInputs; + /** + * Rolling window of the player's past inputs that have yet to be + * acknowledged and simulated by the remote machine + * + * This ring buffer should be initialized to be the size of the past X frames + * you want the remote machine to re-simulate, where X is the oldest input you want to + * allow to be re-simulated. + */ + TRingBuffer UnacknowledgedInputs; }; diff --git a/Source/UnrealFightingFramework/UnrealFightingFramework.Build.cs b/Source/UnrealFightingFramework/UnrealFightingFramework.Build.cs index 92e65a6..a2e4e2d 100644 --- a/Source/UnrealFightingFramework/UnrealFightingFramework.Build.cs +++ b/Source/UnrealFightingFramework/UnrealFightingFramework.Build.cs @@ -10,14 +10,14 @@ public class UnrealFightingFramework : ModuleRules PublicIncludePaths.AddRange( new string[] { - // ... add public include paths required here ... + "UnrealFightingFramework" } ); PrivateIncludePaths.AddRange( new string[] { - // ... add other private include paths required here ... + "UnrealFightingFramework" } ); diff --git a/Source/UnrealFightingFramework/Utils/TCircleBuffer.cpp b/Source/UnrealFightingFramework/Utils/TCircleBuffer.cpp new file mode 100644 index 0000000..c6e0643 --- /dev/null +++ b/Source/UnrealFightingFramework/Utils/TCircleBuffer.cpp @@ -0,0 +1,11 @@ +// Unreal Fighting Framework by Kevin Poretti + +#include "Utils/TCircleBuffer.h" + +TCircleBuffer::TCircleBuffer() +{ +} + +TCircleBuffer::~TCircleBuffer() +{ +} diff --git a/Source/UnrealFightingFramework/Utils/TCircleBuffer.h b/Source/UnrealFightingFramework/Utils/TCircleBuffer.h new file mode 100644 index 0000000..a1debaa --- /dev/null +++ b/Source/UnrealFightingFramework/Utils/TCircleBuffer.h @@ -0,0 +1,15 @@ +// Unreal Fighting Framework by Kevin Poretti + +#pragma once + +#include "CoreMinimal.h" + +/** + * + */ +class UNREALFIGHTINGFRAMEWORK_API TCircleBuffer +{ +public: + TCircleBuffer(); + ~TCircleBuffer(); +}; From 2c5569b6d56a3e6dadaeeea222f34d505de53607 Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Mon, 26 Jun 2023 21:34:58 -0400 Subject: [PATCH 11/38] Untested circle buffer implementation --- .../Input/FFInputBufferComponent.cpp | 12 +- .../Input/FFInputBufferComponent.h | 21 ++- .../Input/FFPlayerController.cpp | 2 +- .../Input/FFPlayerController.h | 19 +-- .../UnrealFightingFramework.Build.cs | 5 +- .../Utils/TCircleBuffer.cpp | 11 -- .../Utils/TCircleBuffer.h | 143 +++++++++++++++++- 7 files changed, 168 insertions(+), 45 deletions(-) delete mode 100644 Source/UnrealFightingFramework/Utils/TCircleBuffer.cpp diff --git a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp index e4e7c97..f7d1aa4 100644 --- a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp +++ b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp @@ -7,16 +7,18 @@ UFFInputBufferComponent::UFFInputBufferComponent() PrimaryComponentTick.bCanEverTick = false; } -void UFFInputBufferComponent::Initialize(int32 BufferSize) -{ - Buffer.Reserve(BufferSize); -} - void UFFInputBufferComponent::AddInput(const FFFInputState& InputState) { + InputBuffer.ForcePush(InputState); } bool UFFInputBufferComponent::CheckInputSequence(const FFFInputSequence& InputSequence) { + for(int InpIdx = 0; InpIdx < InputBuffer.Num(); InpIdx++) + { + // read input sequence + FFFInputState CurrInput = InputBuffer[InpIdx]; + } + return true; } diff --git a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h index b425dbd..a2992f4 100644 --- a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h +++ b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h @@ -2,14 +2,27 @@ #pragma once +// FF includes +#include "Utils/TCircleBuffer.h" + // UE includes #include "CoreMinimal.h" #include "Components/ActorComponent.h" -#include "Containers/RingBuffer.h" #include "FFInputBufferComponent.generated.h" -struct FFFInputState; +/** + * Struct representing the state of a player's inputs for one frame + */ +USTRUCT() +struct FFFInputState +{ + GENERATED_BODY() + + FVector2D MoveAxes; + FVector2D LookAxes; + int32 Buttons; +}; USTRUCT() struct FFFInputSequence @@ -26,13 +39,11 @@ class UNREALFIGHTINGFRAMEWORK_API UFFInputBufferComponent : public UActorCompone public: UFFInputBufferComponent(); - void Initialize(int32 BufferSize = 120); - void AddInput(const FFFInputState& InputState); bool CheckInputSequence(const FFFInputSequence& InputSequence); protected: /** The underlying buffer data structure for holding past input states */ - TRingBuffer Buffer; + TCircleBuffer InputBuffer; }; diff --git a/Source/UnrealFightingFramework/Input/FFPlayerController.cpp b/Source/UnrealFightingFramework/Input/FFPlayerController.cpp index d55b53e..1b4b9a1 100644 --- a/Source/UnrealFightingFramework/Input/FFPlayerController.cpp +++ b/Source/UnrealFightingFramework/Input/FFPlayerController.cpp @@ -21,7 +21,7 @@ void AFFPlayerController::SendInputsToRemote() const void AFFPlayerController::FixedTick(float OneFrame) { - UnacknowledgedInputs.Add(CurrInput); + //UnacknowledgedInputs.Push(CurrInput); InputBuffer->AddInput(CurrInput); SendInputsToRemote(); diff --git a/Source/UnrealFightingFramework/Input/FFPlayerController.h b/Source/UnrealFightingFramework/Input/FFPlayerController.h index bf74650..99ba73a 100644 --- a/Source/UnrealFightingFramework/Input/FFPlayerController.h +++ b/Source/UnrealFightingFramework/Input/FFPlayerController.h @@ -4,29 +4,16 @@ // FF includes #include "FFInputBufferComponent.h" -#include "UnrealFightingFramework/IFFSystemInterface.h" +#include "IFFSystemInterface.h" +#include "Utils/TCircleBuffer.h" // UE includes #include "CoreMinimal.h" #include "GameFramework/PlayerController.h" #include "InputMappingContext.h" -#include "Containers/RingBuffer.h" #include "FFPlayerController.generated.h" -/** - * Struct representing the state of a player's inputs for one frame - */ -USTRUCT() -struct FFFInputState -{ - GENERATED_BODY() - - FVector2D MoveAxes; - FVector2D LookAxes; - int32 Buttons; -}; - /** * A class that collects player inputs, stores them in an input buffer, and sends a rolling window of * unacknowledged inputs to a remote client or server for processing. @@ -72,5 +59,5 @@ protected: * you want the remote machine to re-simulate, where X is the oldest input you want to * allow to be re-simulated. */ - TRingBuffer UnacknowledgedInputs; + TCircleBuffer UnacknowledgedInputs; }; diff --git a/Source/UnrealFightingFramework/UnrealFightingFramework.Build.cs b/Source/UnrealFightingFramework/UnrealFightingFramework.Build.cs index a2e4e2d..b6a13a9 100644 --- a/Source/UnrealFightingFramework/UnrealFightingFramework.Build.cs +++ b/Source/UnrealFightingFramework/UnrealFightingFramework.Build.cs @@ -1,5 +1,5 @@ // Unreal Fighting Framework by Kevin Poretti - +using System.IO; using UnrealBuildTool; public class UnrealFightingFramework : ModuleRules @@ -10,14 +10,13 @@ public class UnrealFightingFramework : ModuleRules PublicIncludePaths.AddRange( new string[] { - "UnrealFightingFramework" + Path.Combine(PluginDirectory,"Source/UnrealFightingFramework"), } ); PrivateIncludePaths.AddRange( new string[] { - "UnrealFightingFramework" } ); diff --git a/Source/UnrealFightingFramework/Utils/TCircleBuffer.cpp b/Source/UnrealFightingFramework/Utils/TCircleBuffer.cpp deleted file mode 100644 index c6e0643..0000000 --- a/Source/UnrealFightingFramework/Utils/TCircleBuffer.cpp +++ /dev/null @@ -1,11 +0,0 @@ -// Unreal Fighting Framework by Kevin Poretti - -#include "Utils/TCircleBuffer.h" - -TCircleBuffer::TCircleBuffer() -{ -} - -TCircleBuffer::~TCircleBuffer() -{ -} diff --git a/Source/UnrealFightingFramework/Utils/TCircleBuffer.h b/Source/UnrealFightingFramework/Utils/TCircleBuffer.h index a1debaa..1289eaf 100644 --- a/Source/UnrealFightingFramework/Utils/TCircleBuffer.h +++ b/Source/UnrealFightingFramework/Utils/TCircleBuffer.h @@ -5,11 +5,146 @@ #include "CoreMinimal.h" /** - * + * CircleBuffer is a templated array data structure allowing for FIFO, O(1) insertion and removal, + * and wraparound when iterating/accessing elements of the buffer. + * + * The underlying container type is a statically allocated array. You can use the second + * template parameter to specify the static size of the buffer. Defaults to 64. */ +template class UNREALFIGHTINGFRAMEWORK_API TCircleBuffer { public: - TCircleBuffer(); - ~TCircleBuffer(); -}; + TCircleBuffer() + : WriteIdx(0) + , ReadIdx(0) + { + } + + /** + * @brief Adds a new element to the buffer only if there is room. + * @param Element to add to the buffer + * @return true if there was room in the buffer and the element was added, false otherwise + */ + bool Push(const T& Element) + { + if(_Num == L) + { + // buffer full so can't add element + return false; + } + Buffer[WriteIdx] = Element; + WriteIdx = (WriteIdx + 1) % L; + _Num++; + return true; + } + + /** + * @brief Adds a new element to the buffer. + * This function always adds an element. If the buffer is full the oldest element will be overwritten. + * @param Element to add to the buffer + */ + void ForcePush(const T& Element) + { + if(_Num == L) + { + // buffer is full so we need to overwrite the oldest element + // and make the read index point to the next oldest element + ReadIdx = (ReadIdx + 1) % L; + } + Buffer[WriteIdx] = Element; + WriteIdx = (WriteIdx + 1) % L; + _Num = FMath::Min(_Num + 1, L); + } + + /** + * @brief Returns the oldest element in the buffer and removes that element from the buffer. + * @param Result out parameter to be filled in with the popped element + * @return true if there was an element in the buffer to pop, false otherwise + */ + bool Pop(T* Result) + { + if(IsEmpty()) + { + return false; + } + + *Result = Buffer[ReadIdx]; + ReadIdx = (ReadIdx + 1) % L; + _Num--; + return true; + } + + /** + * @brief Returns the newest element that was added to the buffer + * @param Result out parameter to be filled in with the most recently added element + * @return true if there was an element in the buffer to peak, false otherwise + */ + bool PeakLast(T* Result) + { + if(IsEmpty()) + { + return false; + } + + *Result = Buffer[((SSIZE_T)WriteIdx - 1) <= 0 ? L - 1 : (WriteIdx - 1)]; + return true; + } + + /** + * @brief Returns the oldest element that was added to the buffer + * @param Result out parameter to be filled in with the oldest element + * @return true if there was an element in the buffer to peak, false otherwise + */ + bool PeakFirst(T* Result) + { + if(IsEmpty()) + { + return false; + } + + *Result = Buffer[ReadIdx]; + return true; + } + + /** + * @brief Returns the element at an index supplied to the function. + * This function will account for write index offset and wraparound of the supplied index. + * i.e. Index 0 will be the oldest added element while index Num - 1 will be the newest added element + * @param Index of the element to return + * @return the element at the index supplied + */ + T& operator[](SIZE_T Index) + { + return Buffer[((SSIZE_T)WriteIdx - (SSIZE_T)Index) < 0 ? L - (Index - WriteIdx) : WriteIdx - Index]; + } + + FORCEINLINE bool IsEmpty() const + { + return _Num == 0; + } + + /** + * @brief Returns the max size of the underlying buffer + * @return Max size of the underlying buffer + */ + FORCEINLINE SIZE_T Max() const + { + return L; + } + + /** + * @brief Returns the current number of elements of the underlying buffer + * @return Max size of the underlying buffer + */ + FORCEINLINE SIZE_T Num() const + { + return _Num; + } + +private: + T Buffer[L]; + SIZE_T WriteIdx; + SIZE_T ReadIdx; + SIZE_T _Num; +}; \ No newline at end of file From eb735db17a40e04929e9d5b75531173cacc9d64a Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Wed, 28 Jun 2023 20:38:20 -0400 Subject: [PATCH 12/38] Curr inputs getter --- Source/UnrealFightingFramework/Input/FFPlayerController.cpp | 2 +- Source/UnrealFightingFramework/Input/FFPlayerController.h | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/UnrealFightingFramework/Input/FFPlayerController.cpp b/Source/UnrealFightingFramework/Input/FFPlayerController.cpp index 1b4b9a1..1c6a615 100644 --- a/Source/UnrealFightingFramework/Input/FFPlayerController.cpp +++ b/Source/UnrealFightingFramework/Input/FFPlayerController.cpp @@ -24,7 +24,7 @@ void AFFPlayerController::FixedTick(float OneFrame) //UnacknowledgedInputs.Push(CurrInput); InputBuffer->AddInput(CurrInput); - SendInputsToRemote(); + //SendInputsToRemote(); } diff --git a/Source/UnrealFightingFramework/Input/FFPlayerController.h b/Source/UnrealFightingFramework/Input/FFPlayerController.h index 99ba73a..be413ba 100644 --- a/Source/UnrealFightingFramework/Input/FFPlayerController.h +++ b/Source/UnrealFightingFramework/Input/FFPlayerController.h @@ -31,6 +31,8 @@ public: */ virtual void SendInputsToRemote() const; + virtual FFFInputState GetCurrInput() { return CurrInput; }; + // IFFSystemInterface interface virtual void FixedTick(float OneFrame) override; // End of IFFSystemInterface From a85a9a7005456647dc135bc1bc33ff1dae5d74c5 Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Fri, 30 Jun 2023 14:13:26 -0400 Subject: [PATCH 13/38] Interface for modifying raw inputs --- .../Input/FFInputBufferComponent.cpp | 2 +- .../Input/FFInputBufferComponent.h | 15 ++++++++++---- .../Input/FFPlayerController.cpp | 10 ++++++++-- .../Input/FFPlayerController.h | 20 +++++++++++++++++-- 4 files changed, 38 insertions(+), 9 deletions(-) diff --git a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp index f7d1aa4..a1f9b9c 100644 --- a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp +++ b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp @@ -12,7 +12,7 @@ void UFFInputBufferComponent::AddInput(const FFFInputState& InputState) InputBuffer.ForcePush(InputState); } -bool UFFInputBufferComponent::CheckInputSequence(const FFFInputSequence& InputSequence) +bool UFFInputBufferComponent::CheckInputSequence(const FFFInputCondition& InputCondition) { for(int InpIdx = 0; InpIdx < InputBuffer.Num(); InpIdx++) { diff --git a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h index a2992f4..c4c5109 100644 --- a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h +++ b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h @@ -21,14 +21,21 @@ struct FFFInputState FVector2D MoveAxes; FVector2D LookAxes; + + UPROPERTY(EditAnywhere, Meta = (Bitmask)) int32 Buttons; }; -USTRUCT() -struct FFFInputSequence +USTRUCT(BlueprintType) +struct FFFInputCondition { GENERATED_BODY() - + + UPROPERTY(EditAnywhere) + TArray Sequence; + + UPROPERTY(EditAnywhere) + int32 Lenience; }; UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) @@ -41,7 +48,7 @@ public: void AddInput(const FFFInputState& InputState); - bool CheckInputSequence(const FFFInputSequence& InputSequence); + bool CheckInputSequence(const FFFInputCondition& InputCondition); protected: /** The underlying buffer data structure for holding past input states */ diff --git a/Source/UnrealFightingFramework/Input/FFPlayerController.cpp b/Source/UnrealFightingFramework/Input/FFPlayerController.cpp index 1c6a615..57c5b7d 100644 --- a/Source/UnrealFightingFramework/Input/FFPlayerController.cpp +++ b/Source/UnrealFightingFramework/Input/FFPlayerController.cpp @@ -19,10 +19,16 @@ void AFFPlayerController::SendInputsToRemote() const } +void AFFPlayerController::ModifyRawInput() +{ + ModifiedInput = RawInput; +} + + void AFFPlayerController::FixedTick(float OneFrame) { - //UnacknowledgedInputs.Push(CurrInput); - InputBuffer->AddInput(CurrInput); + //UnacknowledgedInputs.Push(RawInput); + InputBuffer->AddInput(ModifiedInput); //SendInputsToRemote(); } diff --git a/Source/UnrealFightingFramework/Input/FFPlayerController.h b/Source/UnrealFightingFramework/Input/FFPlayerController.h index be413ba..ded1695 100644 --- a/Source/UnrealFightingFramework/Input/FFPlayerController.h +++ b/Source/UnrealFightingFramework/Input/FFPlayerController.h @@ -31,7 +31,20 @@ public: */ virtual void SendInputsToRemote() const; - virtual FFFInputState GetCurrInput() { return CurrInput; }; + /** + * @brief Gets the current input after cleaning/modifying the raw input state + * @return the current input + */ + virtual FFFInputState GetModifiedInput() const { return ModifiedInput; }; + + /** + * @brief Function called before inputs are passed to the game logic to update the game state. + * This is a chance for the application to mutate inputs before the game uses them to update game state. + * Examples would include treating opposite directional inputs being held cancelling each other out or setting + * a "neutral input" flag when no directional inputs are being held. + * For stick/axis values this can be clamping those values or normalizing the Move and Look direction vectors. + */ + virtual void ModifyRawInput(); // IFFSystemInterface interface virtual void FixedTick(float OneFrame) override; @@ -51,7 +64,10 @@ protected: UFFInputBufferComponent* InputBuffer; /** Current state of the player's inputs */ - FFFInputState CurrInput; + FFFInputState RawInput; + + /** Current state of the player's inputs after performing cleaning and modifications */ + FFFInputState ModifiedInput; /** * Rolling window of the player's past inputs that have yet to be From 9f16901de68ba028127a87c4d2ba4421b8cc974e Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Fri, 30 Jun 2023 19:38:34 -0400 Subject: [PATCH 14/38] Detect if an input sequence is in the buffer and valid --- .../Input/FFInputBufferComponent.cpp | 25 ++++++++++++++++--- .../Input/FFPlayerController.cpp | 1 - 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp index a1f9b9c..f767b1d 100644 --- a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp +++ b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp @@ -14,11 +14,30 @@ void UFFInputBufferComponent::AddInput(const FFFInputState& InputState) bool UFFInputBufferComponent::CheckInputSequence(const FFFInputCondition& InputCondition) { + int CondIdx = InputCondition.Sequence.Num() - 1; + int FramesSinceValidInput = 0; for(int InpIdx = 0; InpIdx < InputBuffer.Num(); InpIdx++) { - // read input sequence - FFFInputState CurrInput = InputBuffer[InpIdx]; + int32 CurrCondition = InputCondition.Sequence[CondIdx].Buttons; + int32 ThisInput = InputBuffer[InpIdx].Buttons; + if(ThisInput & CurrCondition) + { + CondIdx--; + FramesSinceValidInput = 0; + } + + // All conditions were met + if(CondIdx == -1) + { + return true; + } + + FramesSinceValidInput++; + if(FramesSinceValidInput > InputCondition.Lenience) + { + return false; + } } - return true; + return false; } diff --git a/Source/UnrealFightingFramework/Input/FFPlayerController.cpp b/Source/UnrealFightingFramework/Input/FFPlayerController.cpp index 57c5b7d..9d40713 100644 --- a/Source/UnrealFightingFramework/Input/FFPlayerController.cpp +++ b/Source/UnrealFightingFramework/Input/FFPlayerController.cpp @@ -29,7 +29,6 @@ void AFFPlayerController::FixedTick(float OneFrame) { //UnacknowledgedInputs.Push(RawInput); InputBuffer->AddInput(ModifiedInput); - //SendInputsToRemote(); } From b8fa3415037edc9cb96db9554d22cfa93c4f4afb Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Sat, 1 Jul 2023 16:18:09 -0400 Subject: [PATCH 15/38] Detect button press/release and disable inputs when they trigger a sequence --- .../Input/FFInputBufferComponent.cpp | 44 +++++++++++++++---- .../Input/FFInputBufferComponent.h | 32 ++++++++++++-- 2 files changed, 63 insertions(+), 13 deletions(-) diff --git a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp index f767b1d..2dde5cc 100644 --- a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp +++ b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp @@ -12,28 +12,54 @@ void UFFInputBufferComponent::AddInput(const FFFInputState& InputState) InputBuffer.ForcePush(InputState); } -bool UFFInputBufferComponent::CheckInputSequence(const FFFInputCondition& InputCondition) +bool UFFInputBufferComponent::CheckInputSequence(const FFFInputSequence& InputSequence) { - int CondIdx = InputCondition.Sequence.Num() - 1; + int CondIdx = InputSequence.Sequence.Num() - 1; int FramesSinceValidInput = 0; - for(int InpIdx = 0; InpIdx < InputBuffer.Num(); InpIdx++) + for(int InpIdx = 0; InpIdx < InputBuffer.Num() - 1; InpIdx++) { - int32 CurrCondition = InputCondition.Sequence[CondIdx].Buttons; - int32 ThisInput = InputBuffer[InpIdx].Buttons; - if(ThisInput & CurrCondition) + int32 RequiredButtons = InputSequence.Sequence[CondIdx].RequiredButtons; + EFFButtonState RequiredButtonState = InputSequence.Sequence[CondIdx].RequiredButtonState; + int32 PrevInput = InputBuffer[InpIdx + 1].Buttons; + int32 CurrInput = InputBuffer[InpIdx].Buttons; + int32 PrevDisable = InputBuffer[InpIdx + 1].DisabledButtons; + int32 CurrDisable = InputBuffer[InpIdx].DisabledButtons; + switch (RequiredButtonState) { - CondIdx--; - FramesSinceValidInput = 0; + case EFFButtonState::BTNS_Pressed: + if(!(PrevInput & RequiredButtons | PrevDisable) && + CurrInput & RequiredButtons & ~CurrDisable) + { + CondIdx--; + FramesSinceValidInput = 0; + } + break; + case EFFButtonState::BTNS_Released: + if(PrevInput & RequiredButtons & ~PrevDisable && + !(CurrInput & RequiredButtons | CurrDisable)) + { + CondIdx--; + FramesSinceValidInput = 0; + } + break; + // TODO: implement button held condition + /* + case EFFButtonState::BTNS_Held: + break; + */ } // All conditions were met if(CondIdx == -1) { + // disable inputs that triggered the sequence + InputBuffer[InpIdx].DisabledButtons |= InputBuffer[InpIdx].Buttons; + InputBuffer[InpIdx + 1].DisabledButtons |= InputBuffer[InpIdx + 1].Buttons; return true; } FramesSinceValidInput++; - if(FramesSinceValidInput > InputCondition.Lenience) + if(FramesSinceValidInput > InputSequence.Sequence[CondIdx].Lenience) { return false; } diff --git a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h index c4c5109..1363683 100644 --- a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h +++ b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h @@ -11,6 +11,15 @@ #include "FFInputBufferComponent.generated.h" +UENUM(BlueprintType) +enum class EFFButtonState : uint8 +{ + BTNS_Pressed UMETA(DisplayName="Pressed"), + BTNS_Released UMETA(DisplayName="Released"), + //BTNS_Held UMETA(DisplayName="Held"), + BTNS_MAX UMETA(Hidden) +}; + /** * Struct representing the state of a player's inputs for one frame */ @@ -24,6 +33,7 @@ struct FFFInputState UPROPERTY(EditAnywhere, Meta = (Bitmask)) int32 Buttons; + int32 DisabledButtons; }; USTRUCT(BlueprintType) @@ -31,13 +41,27 @@ struct FFFInputCondition { GENERATED_BODY() - UPROPERTY(EditAnywhere) - TArray Sequence; - + // Buttons required for this specific condition to be valid UPROPERTY(EditAnywhere) + int32 RequiredButtons; + + // The button state required for condition to be valid i.e. pressed or released + UPROPERTY(EditAnywhere) + EFFButtonState RequiredButtonState; + + UPROPERTY(EditAnywhere) int32 Lenience; }; +USTRUCT(BlueprintType) +struct FFFInputSequence +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere) + TArray Sequence; +}; + UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) class UNREALFIGHTINGFRAMEWORK_API UFFInputBufferComponent : public UActorComponent { @@ -48,7 +72,7 @@ public: void AddInput(const FFFInputState& InputState); - bool CheckInputSequence(const FFFInputCondition& InputCondition); + bool CheckInputSequence(const FFFInputSequence& InputSequence); protected: /** The underlying buffer data structure for holding past input states */ From 984fc0ad6af98e85c75dd5782f7d6784b10fec8a Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Sat, 1 Jul 2023 19:28:41 -0400 Subject: [PATCH 16/38] Fix the way the circle buffer was index and traversed --- .../Input/FFInputBufferComponent.cpp | 22 +++++++++---------- .../Input/FFInputBufferComponent.h | 6 ++--- .../Utils/TCircleBuffer.h | 3 ++- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp index 2dde5cc..ff1080a 100644 --- a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp +++ b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp @@ -7,22 +7,24 @@ UFFInputBufferComponent::UFFInputBufferComponent() PrimaryComponentTick.bCanEverTick = false; } + void UFFInputBufferComponent::AddInput(const FFFInputState& InputState) { InputBuffer.ForcePush(InputState); } + bool UFFInputBufferComponent::CheckInputSequence(const FFFInputSequence& InputSequence) { int CondIdx = InputSequence.Sequence.Num() - 1; - int FramesSinceValidInput = 0; - for(int InpIdx = 0; InpIdx < InputBuffer.Num() - 1; InpIdx++) + int ElapsedFrames = 0; + for(int InpIdx = InputBuffer.Num() - 1; InpIdx > 1; InpIdx--) { int32 RequiredButtons = InputSequence.Sequence[CondIdx].RequiredButtons; EFFButtonState RequiredButtonState = InputSequence.Sequence[CondIdx].RequiredButtonState; - int32 PrevInput = InputBuffer[InpIdx + 1].Buttons; + int32 PrevInput = InputBuffer[InpIdx - 1].Buttons; int32 CurrInput = InputBuffer[InpIdx].Buttons; - int32 PrevDisable = InputBuffer[InpIdx + 1].DisabledButtons; + int32 PrevDisable = InputBuffer[InpIdx - 1].DisabledButtons; int32 CurrDisable = InputBuffer[InpIdx].DisabledButtons; switch (RequiredButtonState) { @@ -31,7 +33,6 @@ bool UFFInputBufferComponent::CheckInputSequence(const FFFInputSequence& InputSe CurrInput & RequiredButtons & ~CurrDisable) { CondIdx--; - FramesSinceValidInput = 0; } break; case EFFButtonState::BTNS_Released: @@ -39,7 +40,6 @@ bool UFFInputBufferComponent::CheckInputSequence(const FFFInputSequence& InputSe !(CurrInput & RequiredButtons | CurrDisable)) { CondIdx--; - FramesSinceValidInput = 0; } break; // TODO: implement button held condition @@ -52,14 +52,14 @@ bool UFFInputBufferComponent::CheckInputSequence(const FFFInputSequence& InputSe // All conditions were met if(CondIdx == -1) { - // disable inputs that triggered the sequence - InputBuffer[InpIdx].DisabledButtons |= InputBuffer[InpIdx].Buttons; - InputBuffer[InpIdx + 1].DisabledButtons |= InputBuffer[InpIdx + 1].Buttons; + // disable most recent input + InputBuffer[InputBuffer.Num() - 1].DisabledButtons |= InputBuffer[InputBuffer.Num() - 1].Buttons; + InputBuffer[InputBuffer.Num() - 2].DisabledButtons |= InputBuffer[InputBuffer.Num() - 2].Buttons; return true; } - FramesSinceValidInput++; - if(FramesSinceValidInput > InputSequence.Sequence[CondIdx].Lenience) + ElapsedFrames++; + if(ElapsedFrames > InputSequence.MaxDuration) { return false; } diff --git a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h index 1363683..4ef704f 100644 --- a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h +++ b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h @@ -48,9 +48,6 @@ struct FFFInputCondition // The button state required for condition to be valid i.e. pressed or released UPROPERTY(EditAnywhere) EFFButtonState RequiredButtonState; - - UPROPERTY(EditAnywhere) - int32 Lenience; }; USTRUCT(BlueprintType) @@ -60,6 +57,9 @@ struct FFFInputSequence UPROPERTY(EditAnywhere) TArray Sequence; + + UPROPERTY(EditAnywhere) + int32 MaxDuration; }; UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) diff --git a/Source/UnrealFightingFramework/Utils/TCircleBuffer.h b/Source/UnrealFightingFramework/Utils/TCircleBuffer.h index 1289eaf..3d82b1a 100644 --- a/Source/UnrealFightingFramework/Utils/TCircleBuffer.h +++ b/Source/UnrealFightingFramework/Utils/TCircleBuffer.h @@ -18,6 +18,7 @@ public: TCircleBuffer() : WriteIdx(0) , ReadIdx(0) + , _Num(0) { } @@ -116,7 +117,7 @@ public: */ T& operator[](SIZE_T Index) { - return Buffer[((SSIZE_T)WriteIdx - (SSIZE_T)Index) < 0 ? L - (Index - WriteIdx) : WriteIdx - Index]; + return Buffer[(ReadIdx + Index) % L]; } FORCEINLINE bool IsEmpty() const From 85f0cbf804da4711cdb96f6ee046ecf0ec77b180 Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Sat, 1 Jul 2023 19:36:44 -0400 Subject: [PATCH 17/38] Implement the "held down" button state --- .../Input/FFInputBufferComponent.cpp | 6 ++++++ .../UnrealFightingFramework/Input/FFInputBufferComponent.h | 1 + 2 files changed, 7 insertions(+) diff --git a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp index ff1080a..5bf3e0b 100644 --- a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp +++ b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp @@ -42,6 +42,12 @@ bool UFFInputBufferComponent::CheckInputSequence(const FFFInputSequence& InputSe CondIdx--; } break; + case EFFButtonState::BTNS_Down: + if(CurrInput & RequiredButtons & ~CurrDisable) + { + CondIdx--; + } + break; // TODO: implement button held condition /* case EFFButtonState::BTNS_Held: diff --git a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h index 4ef704f..c8015dc 100644 --- a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h +++ b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h @@ -16,6 +16,7 @@ enum class EFFButtonState : uint8 { BTNS_Pressed UMETA(DisplayName="Pressed"), BTNS_Released UMETA(DisplayName="Released"), + BTNS_Down UMETA(DisplayName="Down"), //BTNS_Held UMETA(DisplayName="Held"), BTNS_MAX UMETA(Hidden) }; From 4cf3834b2d5514071034668db2aefc405c722347 Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Sat, 15 Jul 2023 21:36:46 -0400 Subject: [PATCH 18/38] Start of state system rework --- .../UnrealFightingFramework/State/FFState.cpp | 55 ++++++---- .../UnrealFightingFramework/State/FFState.h | 103 ++++++++++-------- .../State/FFStateContextInterface.cpp | 6 + .../State/FFStateContextInterface.h | 31 ++++++ .../State/FFStateMachineComponent.cpp | 43 +++++--- .../State/FFStateMachineComponent.h | 23 ++-- 6 files changed, 172 insertions(+), 89 deletions(-) create mode 100644 Source/UnrealFightingFramework/State/FFStateContextInterface.cpp create mode 100644 Source/UnrealFightingFramework/State/FFStateContextInterface.h diff --git a/Source/UnrealFightingFramework/State/FFState.cpp b/Source/UnrealFightingFramework/State/FFState.cpp index 7240cbc..a06214a 100644 --- a/Source/UnrealFightingFramework/State/FFState.cpp +++ b/Source/UnrealFightingFramework/State/FFState.cpp @@ -5,51 +5,60 @@ // FF includes #include "FFStateMachineComponent.h" -void UFFState::InitActorInfo(AActor* InOwner, AActor* InAvatar) + +void UFFStateBehavior::Enter(const FFFStateContext& InStateContext) { - Owner = InOwner; - Avatar = InAvatar; + OnEnter(InStateContext); } -void UFFState::Enter() +void UFFStateBehavior::Exit(const FFFStateContext& InStateContext) { - TicksInState = 0; - - OnEnter(); + OnExit(InStateContext); } -void UFFState::Exit() +void UFFStateBehavior::Update(float OneFrame, const FFFStateContext& InStateContext) { - OnExit(); + OnUpdate(OneFrame, InStateContext); } - -void UFFState::Update(float OneFrame) -{ - TicksInState++; - - OnUpdate(OneFrame); -} - - -void UFFState::OnEnter_Implementation() +void UFFStateBehavior::OnLanded_Implementation(const FFFStateContext& InStateContext) { } -void UFFState::OnExit_Implementation() +void UFFStateBehavior::OnEnter_Implementation(const FFFStateContext& InStateContext) { } -void UFFState::OnUpdate_Implementation(float OneFrame) +void UFFStateBehavior::OnExit_Implementation(const FFFStateContext& InStateContext) { } -UWorld* UFFState::GetWorld() const +void UFFStateBehavior::OnUpdate_Implementation(float OneFrame, const FFFStateContext& InStateContext) +{ +} + + +void UFFStateBehavior::OnHit_Implementation(const FFFStateContext& InStateContext) +{ +} + + +void UFFStateBehavior::OnBlock_Implementation(const FFFStateContext& InStateContext) +{ +} + + +void UFFStateBehavior::OnInputEvent_Implementation(const FFFStateContext& InStateContext) +{ +} + + +UWorld* UFFStateBehavior::GetWorld() const { UFFStateMachineComponent* SMC = Cast(GetOuter()); if(SMC) @@ -58,4 +67,4 @@ UWorld* UFFState::GetWorld() const } return nullptr; -} \ No newline at end of file +} diff --git a/Source/UnrealFightingFramework/State/FFState.h b/Source/UnrealFightingFramework/State/FFState.h index bc0073c..c851b80 100644 --- a/Source/UnrealFightingFramework/State/FFState.h +++ b/Source/UnrealFightingFramework/State/FFState.h @@ -7,26 +7,12 @@ #include "FFState.generated.h" -/** - * 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. - */ -UCLASS() -class UNREALFIGHTINGFRAMEWORK_API UFFState : public UObject +USTRUCT(BlueprintType) +struct FFFStateData { - GENERATED_BODY() + GENERATED_BODY() -public: - - /** - * Initializes pointers to what owns this state and what avatar this state represents. - * - * @param InOwner Actor that owns this state machine. - * @param InAvatar Actor that this state machine represents. - */ - virtual void InitActorInfo(AActor* InOwner, AActor* InAvatar); - - /** Name of this state */ + /** Name of this state */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="UFF|State") FName Name; @@ -38,21 +24,56 @@ public: UPROPERTY(EditAnywhere) uint8 StateType; - /** How many ticks have elapsed since this state was entered */ - UPROPERTY(BlueprintReadOnly, Category="UFF|State") - int32 TicksInState; +}; +USTRUCT(BlueprintType) +struct FFFStateContext +{ + GENERATED_BODY() + + /** Actor that owns the avatar. Typically a player controller. */ + 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. + */ + AActor* Avatar; + + /** + * Data associated with this state. + * For example this can be new movement values or data about the hitboxes if this state represents an attack. + */ + FFFStateData StateData; +}; + +/** + * 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. + */ +UCLASS() +class UNREALFIGHTINGFRAMEWORK_API UFFStateBehavior : public UObject +{ + GENERATED_BODY() + +public: + /** Name of this state behavior */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="UFF|State") + FName Name; + + // TODO: since state's are now purely behavioral can we remove these function calls? + // They are basically redundant with OnEnter, OnUpdate, OnExit, etc. /** * Called whenever this state is transitioned into. * * Resets TicksInState and calls appropriate Blueprint hooks */ - void Enter(); + void Enter(const FFFStateContext& InStateContext); /** * Called whenever this state is transitioned out of into a new state. */ - void Exit(); + void Exit(const FFFStateContext& InStateContext); /** * Called whenever this state is active and the game logic ticks. @@ -61,19 +82,19 @@ public: * * @param OneFrame the time that elapses during one fixed tick */ - void Update(float OneFrame); + void Update(float OneFrame, const FFFStateContext& InStateContext); /** * Blueprint hook that is called whenever this state is transitioned into */ UFUNCTION(BlueprintNativeEvent, Category="UFF|State|Events") - void OnEnter(); + void OnEnter(const FFFStateContext& InStateContext); /** * Blueprint hook that is called whenever this state is transitioned out of into a new state */ UFUNCTION(BlueprintNativeEvent, Category="UFF|State|Events") - void OnExit(); + void OnExit(const FFFStateContext& InStateContext); /** * Blueprint hook that is called whenever this state is active and the game logic ticks @@ -81,25 +102,21 @@ public: * @param OneFrame the time that elapses during one fixed tick */ UFUNCTION(BlueprintNativeEvent, Category="UFF|State|Events") - void OnUpdate(float OneFrame); + void OnUpdate(float OneFrame, const FFFStateContext& InStateContext); + + UFUNCTION(BlueprintNativeEvent, Category="UFF|State|Events") + void OnLanded(const FFFStateContext& InStateContext); + + UFUNCTION(BlueprintNativeEvent, Category="UFF|State|Events") + void OnHit(const FFFStateContext& InStateContext); + + UFUNCTION(BlueprintNativeEvent, Category="UFF|State|Events") + void OnBlock(const FFFStateContext& InStateContext); + + UFUNCTION(BlueprintNativeEvent, Category="UFF|State|Events") + void OnInputEvent(const FFFStateContext& InStateContext); // UObject interface virtual UWorld* GetWorld() const override; // End of UObject interface - -protected: - - /** - * Actor that owns this state. - * This will typically be a player controller that possesses the avatar. - */ - UPROPERTY(BlueprintReadOnly) - AActor* Owner; - - /** - * The avatar is an actor that this state represents. - * This will typically be a pawn or character. - */ - UPROPERTY(BlueprintReadOnly) - AActor* Avatar; }; diff --git a/Source/UnrealFightingFramework/State/FFStateContextInterface.cpp b/Source/UnrealFightingFramework/State/FFStateContextInterface.cpp new file mode 100644 index 0000000..045d032 --- /dev/null +++ b/Source/UnrealFightingFramework/State/FFStateContextInterface.cpp @@ -0,0 +1,6 @@ +// Unreal Fighting Framework by Kevin Poretti + + +#include "State/FFStateContextInterface.h" + +// Add default functionality here for any IFFStateContextInterface functions that are not pure virtual. diff --git a/Source/UnrealFightingFramework/State/FFStateContextInterface.h b/Source/UnrealFightingFramework/State/FFStateContextInterface.h new file mode 100644 index 0000000..a797aad --- /dev/null +++ b/Source/UnrealFightingFramework/State/FFStateContextInterface.h @@ -0,0 +1,31 @@ +// Unreal Fighting Framework by Kevin Poretti + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Interface.h" + +#include "FFStateContextInterface.generated.h" + +UINTERFACE(MinimalAPI) +class UFFStateContextInterface : public UInterface +{ + GENERATED_BODY() +}; + +/** + * + */ +class UNREALFIGHTINGFRAMEWORK_API IFFStateContextInterface +{ + GENERATED_BODY() + +public: + virtual bool CheckStateEnabled(uint8 StateType) = 0; + + virtual bool CheckStance(uint8 Stance) = 0; + + virtual bool CheckStateEntryConditions(const TArray& EntryConditions) = 0; + + virtual bool CheckInputSequences(const TArray& InputSequences) = 0; +}; diff --git a/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp b/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp index 9c74dd9..42d19aa 100644 --- a/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp +++ b/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp @@ -14,13 +14,13 @@ UFFStateMachineComponent::UFFStateMachineComponent() void UFFStateMachineComponent::Initialize() { - for(const TSubclassOf& CurrState : DefaultStates) + for(const TSubclassOf& CurrState : DefaultStates) { - UFFState* TempState = AddState(CurrState); + UFFStateBehavior* TempState = AddState(CurrState); if(!CurrentState) // first state to be created is the entry into this state machine { CurrentState = TempState; - CurrentState->Enter(); + CurrentState->Enter(GetCurrentStateContext()); } } } @@ -33,13 +33,12 @@ void UFFStateMachineComponent::InitActorInfo(AActor* InOwner, AActor* InAvatar) } -UFFState* UFFStateMachineComponent::AddState(TSubclassOf StateClassToAdd) +UFFStateBehavior* UFFStateMachineComponent::AddState(TSubclassOf StateClassToAdd) { - UFFState* TempState = NewObject(this, StateClassToAdd); + UFFStateBehavior* TempState = NewObject(this, StateClassToAdd); if(TempState) { States.Add(TempState); - TempState->InitActorInfo(Owner, Avatar); return TempState; } @@ -47,9 +46,9 @@ UFFState* UFFStateMachineComponent::AddState(TSubclassOf StateClassToA } -void UFFStateMachineComponent::AddStates(const TArray>& StateClassesToAdd) +void UFFStateMachineComponent::AddStates(const TArray>& StateClassesToAdd) { - for(const TSubclassOf& CurrState : StateClassesToAdd) + for(const TSubclassOf& CurrState : StateClassesToAdd) { AddState(CurrState); } @@ -61,13 +60,15 @@ void UFFStateMachineComponent::RemoveState(FName StateToRemove) UE_LOG(LogTemp, Error, TEXT("UFFStateMachineComponent::RemoveState is not yet implemented")); } -void UFFStateMachineComponent::SwitchStates(UFFState* NewState) +void UFFStateMachineComponent::SwitchStates(UFFStateBehavior* NewState) { check(NewState); - CurrentState->Exit(); + + CurrentState->Exit(GetCurrentStateContext()); CurrentState = NewState; - CurrentState->Enter(); + CurrentState->Enter(GetCurrentStateContext()); + TicksInState = 0; } @@ -77,11 +78,20 @@ FName UFFStateMachineComponent::GetCurrentStateName() const } -UFFState* UFFStateMachineComponent::FindStateWithName(FName StateName) +FFFStateContext UFFStateMachineComponent::GetCurrentStateContext() const { - for (UFFState* CurrState : States) + FFFStateContext CurrStateContext; + CurrStateContext.Owner = Owner; + CurrStateContext.Avatar = Avatar; + return CurrStateContext; +} + + +UFFStateBehavior* UFFStateMachineComponent::FindStateWithName(FName StateName) +{ + for (UFFStateBehavior* CurrState : States) { - if(CurrState ->Name == StateName) + if(CurrState->Name == StateName) { return CurrState; } @@ -106,7 +116,7 @@ void UFFStateMachineComponent::FixedTick(float OneFrame) { // Should we switch states? - for(UFFState* CurrState : States) + for(UFFStateBehavior* CurrState : States) { // Check if the state is enabled @@ -128,7 +138,8 @@ void UFFStateMachineComponent::FixedTick(float OneFrame) check(CurrentState); // Tick current state - CurrentState->Update(OneFrame); + TicksInState++; + CurrentState->Update(OneFrame, GetCurrentStateContext()); // Debug } diff --git a/Source/UnrealFightingFramework/State/FFStateMachineComponent.h b/Source/UnrealFightingFramework/State/FFStateMachineComponent.h index 2261c88..96f371d 100644 --- a/Source/UnrealFightingFramework/State/FFStateMachineComponent.h +++ b/Source/UnrealFightingFramework/State/FFStateMachineComponent.h @@ -47,14 +47,14 @@ public: * * @return A pointer to the state that was added or nullptr if there was an issue adding or creating the state */ - UFFState* AddState(TSubclassOf StateClassToAdd); + UFFStateBehavior* AddState(TSubclassOf StateClassToAdd); /** * Creates an instance of the state classes and adds newly created states to this state machine. * * @param StateClassesToAdd Array of state class types to be added to this state machine */ - void AddStates(const TArray>& StateClassesToAdd); + void AddStates(const TArray>& StateClassesToAdd); /** * Destroys the state with corresponding name and removes it from this state machine. @@ -66,7 +66,7 @@ public: * * Triggers the Exit callback on the CurrentState and the Enter callback on the new state */ - void SwitchStates(UFFState* NewState); + void SwitchStates(UFFStateBehavior* NewState); /** * Returns the name of the current state @@ -74,6 +74,11 @@ public: UFUNCTION(BlueprintPure) FName GetCurrentStateName() const; + /** + * + */ + FFFStateContext GetCurrentStateContext() const; + // IFFSystemInterface interface virtual void FixedTick(float OneFrame) override; // End of IFFSystemInterface interface @@ -93,24 +98,28 @@ protected: UPROPERTY(BlueprintReadOnly) AActor* Avatar; + /** How many ticks have elapsed since the currently active state was entered */ + UPROPERTY(BlueprintReadOnly, Category="UFF|State") + int32 TicksInState; + /** * States classes to create and add to this state machine when the game starts */ UPROPERTY(EditDefaultsOnly, Category="UFF|State Machine") - TArray> DefaultStates; + TArray> DefaultStates; /** Current active state for this state machine */ UPROPERTY(BlueprintReadOnly) - UFFState* CurrentState; + UFFStateBehavior* CurrentState; // States that have been added UPROPERTY(BlueprintReadOnly) - TArray States; + TArray States; /** * Returns the state with corresponding name */ - UFFState* FindStateWithName(FName StateName); + UFFStateBehavior* FindStateWithName(FName StateName); // UActorComponent interface virtual void BeginPlay() override; From d7245957df27c2d26a254826051280d4674d3118 Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Sun, 16 Jul 2023 17:09:31 -0400 Subject: [PATCH 19/38] Implement rest of state class and do transition logic in state machine --- .../IFFSystemInterface.cpp | 4 - .../Input/FFPlayerController.cpp | 13 ++ .../Input/FFPlayerController.h | 7 +- .../UnrealFightingFramework/State/FFState.cpp | 98 ++++++++---- .../UnrealFightingFramework/State/FFState.h | 139 ++++++++++++------ .../State/FFStateContextInterface.cpp | 6 - .../State/FFStateData.h | 25 ++++ .../State/FFStateMachineComponent.cpp | 80 +++++----- .../State/FFStateMachineComponent.h | 25 ++-- ...tInterface.h => IFFStateAvatarInterface.h} | 12 +- .../State/IFFStateOwnerInterface.h | 25 ++++ 11 files changed, 299 insertions(+), 135 deletions(-) delete mode 100644 Source/UnrealFightingFramework/IFFSystemInterface.cpp delete mode 100644 Source/UnrealFightingFramework/State/FFStateContextInterface.cpp create mode 100644 Source/UnrealFightingFramework/State/FFStateData.h rename Source/UnrealFightingFramework/State/{FFStateContextInterface.h => IFFStateAvatarInterface.h} (53%) create mode 100644 Source/UnrealFightingFramework/State/IFFStateOwnerInterface.h diff --git a/Source/UnrealFightingFramework/IFFSystemInterface.cpp b/Source/UnrealFightingFramework/IFFSystemInterface.cpp deleted file mode 100644 index 7be46d6..0000000 --- a/Source/UnrealFightingFramework/IFFSystemInterface.cpp +++ /dev/null @@ -1,4 +0,0 @@ -// Unreal Fighting Framework by Kevin Poretti - - -#include "IFFSystemInterface.h" diff --git a/Source/UnrealFightingFramework/Input/FFPlayerController.cpp b/Source/UnrealFightingFramework/Input/FFPlayerController.cpp index 9d40713..0401d1f 100644 --- a/Source/UnrealFightingFramework/Input/FFPlayerController.cpp +++ b/Source/UnrealFightingFramework/Input/FFPlayerController.cpp @@ -33,6 +33,19 @@ void AFFPlayerController::FixedTick(float OneFrame) } +bool AFFPlayerController::CheckInputSequences(const TArray& InputSequences) +{ + for(const FFFInputSequence& ThisInputSequence : InputSequences) + { + if(InputBuffer->CheckInputSequence(ThisInputSequence)) + { + return true; + } + } + return false; +} + + void AFFPlayerController::SetupInputComponent() { Super::SetupInputComponent(); diff --git a/Source/UnrealFightingFramework/Input/FFPlayerController.h b/Source/UnrealFightingFramework/Input/FFPlayerController.h index ded1695..b509246 100644 --- a/Source/UnrealFightingFramework/Input/FFPlayerController.h +++ b/Source/UnrealFightingFramework/Input/FFPlayerController.h @@ -6,6 +6,7 @@ #include "FFInputBufferComponent.h" #include "IFFSystemInterface.h" #include "Utils/TCircleBuffer.h" +#include "State/IFFStateOwnerInterface.h" // UE includes #include "CoreMinimal.h" @@ -19,7 +20,7 @@ * unacknowledged inputs to a remote client or server for processing. */ UCLASS() -class UNREALFIGHTINGFRAMEWORK_API AFFPlayerController : public APlayerController, public IFFSystemInterface +class UNREALFIGHTINGFRAMEWORK_API AFFPlayerController : public APlayerController, public IFFSystemInterface, public IFFStateOwnerInterface { GENERATED_BODY() @@ -50,6 +51,10 @@ public: virtual void FixedTick(float OneFrame) override; // End of IFFSystemInterface + // IFFStateOwnerInterface + virtual bool CheckInputSequences(const TArray& InputSequences) override; + // End of IFFStateOwnerInterface + // APlayerController interface virtual void SetupInputComponent() override; // End of APlayerController interface diff --git a/Source/UnrealFightingFramework/State/FFState.cpp b/Source/UnrealFightingFramework/State/FFState.cpp index a06214a..a8491aa 100644 --- a/Source/UnrealFightingFramework/State/FFState.cpp +++ b/Source/UnrealFightingFramework/State/FFState.cpp @@ -4,61 +4,97 @@ // FF includes #include "FFStateMachineComponent.h" +#include "IFFStateAvatarInterface.h" +#include "IFFStateOwnerInterface.h" +// UE includes +#include "Components/SkeletalMeshComponent.h" -void UFFStateBehavior::Enter(const FFFStateContext& InStateContext) +void UFFState::Enter(const FFFStateContext& InStateContext) { + if(InStateContext.Avatar) + { + USkeletalMeshComponent* SMC = InStateContext.Avatar->GetComponentByClass(); + if(SMC && MontageToPlay) + { + SMC->GetAnimInstance()->Montage_Play(MontageToPlay); + } + } + OnEnter(InStateContext); } -void UFFStateBehavior::Exit(const FFFStateContext& InStateContext) +void UFFState::Exit(const FFFStateContext& InStateContext) { + if(InStateContext.Avatar) + { + USkeletalMeshComponent* SMC = InStateContext.Avatar->GetComponentByClass(); + if(SMC && MontageToPlay) + { + FAlphaBlendArgs BlendOutArgs = MontageToPlay->GetBlendOutArgs(); + SMC->GetAnimInstance()->Montage_Stop(BlendOutArgs.BlendTime, MontageToPlay); + } + } + OnExit(InStateContext); } -void UFFStateBehavior::Update(float OneFrame, const FFFStateContext& InStateContext) +void UFFState::Update(float OneFrame, const FFFStateContext& InStateContext) { OnUpdate(OneFrame, InStateContext); } -void UFFStateBehavior::OnLanded_Implementation(const FFFStateContext& InStateContext) + +bool UFFState::CanTransition_Implementation(const FFFStateContext& InStateContext) { + /** + * Check to see if avatar implements StateAvatarInterface + * 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 + * so return true otherwise return false + */ + IFFStateAvatarInterface* SAI = Cast(InStateContext.Avatar); + if(SAI) + { + if(!(SAI->CheckStance(StanceRequired) && SAI->CheckStateEnabled(StateType) && SAI->CheckStateEntryConditions(EntryConditions))) + { + return false; + } + } + + IFFStateOwnerInterface* SOI = Cast(InStateContext.Owner); + if(SOI) + { + if(!SOI->CheckInputSequences(InputSequences)) + { + return false; + } + } + + return true; } -void UFFStateBehavior::OnEnter_Implementation(const FFFStateContext& InStateContext) +void UFFState::GotoSubState(FName InSubStateLabel) { + UFFStateMachineComponent* SMC = Cast(GetOuter()); + if(SMC) + { + SMC->SetSubStateLabel(InSubStateLabel); + } } -void UFFStateBehavior::OnExit_Implementation(const FFFStateContext& InStateContext) -{ -} - - -void UFFStateBehavior::OnUpdate_Implementation(float OneFrame, const FFFStateContext& InStateContext) -{ -} - - -void UFFStateBehavior::OnHit_Implementation(const FFFStateContext& InStateContext) -{ -} - - -void UFFStateBehavior::OnBlock_Implementation(const FFFStateContext& InStateContext) -{ -} - - -void UFFStateBehavior::OnInputEvent_Implementation(const FFFStateContext& InStateContext) -{ -} - - -UWorld* UFFStateBehavior::GetWorld() const +UWorld* UFFState::GetWorld() const { UFFStateMachineComponent* SMC = Cast(GetOuter()); if(SMC) diff --git a/Source/UnrealFightingFramework/State/FFState.h b/Source/UnrealFightingFramework/State/FFState.h index c851b80..d02282f 100644 --- a/Source/UnrealFightingFramework/State/FFState.h +++ b/Source/UnrealFightingFramework/State/FFState.h @@ -2,29 +2,14 @@ #pragma once +// FF includes +#include "Input/FFInputBufferComponent.h" + // UE includes #include "CoreMinimal.h" #include "FFState.generated.h" -USTRUCT(BlueprintType) -struct FFFStateData -{ - GENERATED_BODY() - - /** Name of this state */ - UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="UFF|State") - FName Name; - - /** Conditions that need to be met in order for this state to be transitioned into */ - UPROPERTY(EditAnywhere) - TArray EntryConditions; - - /** What is this state's category. Used for determining what types of state can prematurely cancel this one. */ - UPROPERTY(EditAnywhere) - uint8 StateType; - -}; USTRUCT(BlueprintType) struct FFFStateContext @@ -40,11 +25,13 @@ struct FFFStateContext */ AActor* Avatar; - /** - * Data associated with this state. - * For example this can be new movement values or data about the hitboxes if this state represents an attack. - */ - FFFStateData StateData; + /** + * 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; }; /** @@ -52,68 +39,132 @@ struct FFFStateContext * and logic to run when the state is entered, exited, and active. */ UCLASS() -class UNREALFIGHTINGFRAMEWORK_API UFFStateBehavior : public UObject +class UNREALFIGHTINGFRAMEWORK_API UFFState : public UObject { GENERATED_BODY() public: + // State parameters (should be read-only) /** Name of this state behavior */ - UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="UFF|State") + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties") FName Name; - // TODO: since state's are now purely behavioral can we remove these function calls? - // They are basically redundant with OnEnter, OnUpdate, OnExit, etc. /** + * How long this state will be active before finishing if this state is not cancelled out of + * by other means. + */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties") + int64 StateDuration; + + /** + * What stance the object this state represents must be in. + * For example this is usually an enumerated value that signifies if a character needs to be + * crouching, standing, or airborne for this state to be eligible for transitioning. + */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties") + uint8 StanceRequired; + + /** + * What is this state's category. + * Used for determining what types of state can prematurely cancel this one. + */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties") + uint8 StateType; + + /** Conditions that need to be met in order for this state to be transitioned into */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties") + TArray EntryConditions; + + /** + * Input sequences that needs to be present in the controlling player's input buffer for this + * state to be eligible for transitioning. + */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties") + TArray InputSequences; + + /** + * If true the state can transition from itself into itself without having to go through + * another state like Idle + */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties") + bool bCanTransitionToSelf = false; + + /** + * Animation to begin playing when this state is entered + */ + 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 + * all state entry conditions were met AND + * at least one input sequence is present in the Owner's input buffer + */ + 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. * - * Resets TicksInState and calls appropriate Blueprint hooks + * Calls appropriate Blueprint hooks. */ - void Enter(const FFFStateContext& InStateContext); + virtual void Enter(const FFFStateContext& InStateContext); - /** + /** * Called whenever this state is transitioned out of into a new state. + * + * Calls appropriate Blueprint hooks. */ - void Exit(const FFFStateContext& InStateContext); + virtual void Exit(const FFFStateContext& InStateContext); - /** + /** * Called whenever this state is active and the game logic ticks. * - * Increments TicksInState and calls appropriate Blueprint hooks. + * Calls appropriate Blueprint hooks. * * @param OneFrame the time that elapses during one fixed tick */ - void Update(float OneFrame, const FFFStateContext& InStateContext); + virtual void Update(float OneFrame, const FFFStateContext& InStateContext); /** - * Blueprint hook that is called whenever this state is transitioned into + * Blueprint hook for whenever this state is transitioned into */ - UFUNCTION(BlueprintNativeEvent, Category="UFF|State|Events") + UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events") void OnEnter(const FFFStateContext& InStateContext); /** - * Blueprint hook that is called whenever this state is transitioned out of into a new state + * Blueprint hook for whenever this state is transitioned out of into a new state */ - UFUNCTION(BlueprintNativeEvent, Category="UFF|State|Events") + UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events") void OnExit(const FFFStateContext& InStateContext); /** - * Blueprint hook that is called whenever this state is active and the game logic ticks + * Blueprint hook for whenever this state is active and the game logic ticks. * * @param OneFrame the time that elapses during one fixed tick */ - UFUNCTION(BlueprintNativeEvent, Category="UFF|State|Events") + UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events") void OnUpdate(float OneFrame, const FFFStateContext& InStateContext); - UFUNCTION(BlueprintNativeEvent, Category="UFF|State|Events") + UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events") void OnLanded(const FFFStateContext& InStateContext); - UFUNCTION(BlueprintNativeEvent, Category="UFF|State|Events") + UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events") void OnHit(const FFFStateContext& InStateContext); - UFUNCTION(BlueprintNativeEvent, Category="UFF|State|Events") + UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events") void OnBlock(const FFFStateContext& InStateContext); - UFUNCTION(BlueprintNativeEvent, Category="UFF|State|Events") + UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events") void OnInputEvent(const FFFStateContext& InStateContext); // UObject interface diff --git a/Source/UnrealFightingFramework/State/FFStateContextInterface.cpp b/Source/UnrealFightingFramework/State/FFStateContextInterface.cpp deleted file mode 100644 index 045d032..0000000 --- a/Source/UnrealFightingFramework/State/FFStateContextInterface.cpp +++ /dev/null @@ -1,6 +0,0 @@ -// Unreal Fighting Framework by Kevin Poretti - - -#include "State/FFStateContextInterface.h" - -// Add default functionality here for any IFFStateContextInterface functions that are not pure virtual. diff --git a/Source/UnrealFightingFramework/State/FFStateData.h b/Source/UnrealFightingFramework/State/FFStateData.h new file mode 100644 index 0000000..6452943 --- /dev/null +++ b/Source/UnrealFightingFramework/State/FFStateData.h @@ -0,0 +1,25 @@ +// Unreal Fighting Framework by Kevin Poretti + +#pragma once + +// FF includes +#include "State/FFState.h" + +// UE includes +#include "CoreMinimal.h" +#include "Engine/DataAsset.h" + +#include "FFStateData.generated.h" + +/** + * + */ +UCLASS() +class UNREALFIGHTINGFRAMEWORK_API UFFStateData : public UDataAsset +{ + GENERATED_BODY() + +public: + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) + TArray> States; +}; diff --git a/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp b/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp index 42d19aa..5b8c77c 100644 --- a/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp +++ b/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp @@ -14,9 +14,9 @@ UFFStateMachineComponent::UFFStateMachineComponent() void UFFStateMachineComponent::Initialize() { - for(const TSubclassOf& CurrState : DefaultStates) + for(const TSubclassOf& CurrState : DefaultStates) { - UFFStateBehavior* TempState = AddState(CurrState); + UFFState* TempState = AddState(CurrState); if(!CurrentState) // first state to be created is the entry into this state machine { CurrentState = TempState; @@ -33,9 +33,9 @@ void UFFStateMachineComponent::InitActorInfo(AActor* InOwner, AActor* InAvatar) } -UFFStateBehavior* UFFStateMachineComponent::AddState(TSubclassOf StateClassToAdd) +UFFState* UFFStateMachineComponent::AddState(TSubclassOf StateClassToAdd) { - UFFStateBehavior* TempState = NewObject(this, StateClassToAdd); + UFFState* TempState = NewObject(this, StateClassToAdd); if(TempState) { States.Add(TempState); @@ -46,9 +46,9 @@ UFFStateBehavior* UFFStateMachineComponent::AddState(TSubclassOf>& StateClassesToAdd) +void UFFStateMachineComponent::AddStates(const TArray>& StateClassesToAdd) { - for(const TSubclassOf& CurrState : StateClassesToAdd) + for(const TSubclassOf& CurrState : StateClassesToAdd) { AddState(CurrState); } @@ -60,21 +60,24 @@ void UFFStateMachineComponent::RemoveState(FName StateToRemove) UE_LOG(LogTemp, Error, TEXT("UFFStateMachineComponent::RemoveState is not yet implemented")); } -void UFFStateMachineComponent::SwitchStates(UFFStateBehavior* NewState) +void UFFStateMachineComponent::SwitchStates(UFFState* NewState) { + check(CurrentState); check(NewState); - CurrentState->Exit(GetCurrentStateContext()); CurrentState = NewState; CurrentState->Enter(GetCurrentStateContext()); TicksInState = 0; + CurrentSubStateLabel = NAME_None; } FName UFFStateMachineComponent::GetCurrentStateName() const { - return CurrentState ? CurrentState->Name : NAME_None; + check(CurrentState) + + return CurrentState->Name; } @@ -83,13 +86,21 @@ FFFStateContext UFFStateMachineComponent::GetCurrentStateContext() const FFFStateContext CurrStateContext; CurrStateContext.Owner = Owner; CurrStateContext.Avatar = Avatar; + CurrStateContext.Parent = this; + CurrStateContext.TicksInState = TicksInState; return CurrStateContext; } -UFFStateBehavior* UFFStateMachineComponent::FindStateWithName(FName StateName) +void UFFStateMachineComponent::SetSubStateLabel(FName InSubStateLabel) { - for (UFFStateBehavior* CurrState : States) + CurrentSubStateLabel = InSubStateLabel; +} + + +UFFState* UFFStateMachineComponent::FindStateWithName(FName StateName) +{ + for (UFFState* CurrState : States) { if(CurrState->Name == StateName) { @@ -114,32 +125,35 @@ void UFFStateMachineComponent::BeginPlay() void UFFStateMachineComponent::FixedTick(float OneFrame) { - // Should we switch states? - - for(UFFStateBehavior* CurrState : States) - { - // Check if the state is enabled - - // Check state entry 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 - - // Lastly just check if the state we're about to transition into isn't the current state. - // It is OK to transition if state's "CanTransitionToSelf" is true - - // SwitchStates(NewState); - // return; - } - // CurrentState should never be null // TODO: Should probably assert or whatever UE's equivalent is check(CurrentState); - // Tick current state - TicksInState++; - CurrentState->Update(OneFrame, GetCurrentStateContext()); + // Should we switch states? + UFFState* StateToTransitionTo = nullptr; + for(UFFState* ThisState : States) + { + // found a state + if(ThisState->CanTransition(GetCurrentStateContext())) + { + StateToTransitionTo = ThisState; + break; + } + } + + // Lastly just check if the state we're about to transition into isn't the current state. + // It is OK to transition if state's "CanTransitionToSelf" is true + if(StateToTransitionTo && + (CurrentState->Name != StateToTransitionTo->Name || StateToTransitionTo->bCanTransitionToSelf)) + { + SwitchStates(StateToTransitionTo); + } + else + { + // Tick current state + TicksInState++; + CurrentState->Update(OneFrame, GetCurrentStateContext()); + } // Debug } diff --git a/Source/UnrealFightingFramework/State/FFStateMachineComponent.h b/Source/UnrealFightingFramework/State/FFStateMachineComponent.h index 96f371d..9597c54 100644 --- a/Source/UnrealFightingFramework/State/FFStateMachineComponent.h +++ b/Source/UnrealFightingFramework/State/FFStateMachineComponent.h @@ -3,7 +3,8 @@ #pragma once // FF includes -#include "UnrealFightingFramework/IFFSystemInterface.h" +#include "IFFSystemInterface.h" +#include "FFState.h" // UE includes #include "CoreMinimal.h" @@ -47,14 +48,14 @@ public: * * @return A pointer to the state that was added or nullptr if there was an issue adding or creating the state */ - UFFStateBehavior* AddState(TSubclassOf StateClassToAdd); + UFFState* AddState(TSubclassOf StateClassToAdd); /** * Creates an instance of the state classes and adds newly created states to this state machine. * * @param StateClassesToAdd Array of state class types to be added to this state machine */ - void AddStates(const TArray>& StateClassesToAdd); + void AddStates(const TArray>& StateClassesToAdd); /** * Destroys the state with corresponding name and removes it from this state machine. @@ -66,7 +67,7 @@ public: * * Triggers the Exit callback on the CurrentState and the Enter callback on the new state */ - void SwitchStates(UFFStateBehavior* NewState); + void SwitchStates(UFFState* NewState); /** * Returns the name of the current state @@ -79,6 +80,8 @@ public: */ FFFStateContext GetCurrentStateContext() const; + void SetSubStateLabel(FName InSubStateLabel); + // IFFSystemInterface interface virtual void FixedTick(float OneFrame) override; // End of IFFSystemInterface interface @@ -100,26 +103,30 @@ protected: /** How many ticks have elapsed since the currently active state was entered */ UPROPERTY(BlueprintReadOnly, Category="UFF|State") - int32 TicksInState; + int64 TicksInState; /** * States classes to create and add to this state machine when the game starts */ UPROPERTY(EditDefaultsOnly, Category="UFF|State Machine") - TArray> DefaultStates; + TArray> DefaultStates; /** Current active state for this state machine */ UPROPERTY(BlueprintReadOnly) - UFFStateBehavior* CurrentState; + 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) - TArray States; + TArray States; /** * Returns the state with corresponding name */ - UFFStateBehavior* FindStateWithName(FName StateName); + UFFState* FindStateWithName(FName StateName); // UActorComponent interface virtual void BeginPlay() override; diff --git a/Source/UnrealFightingFramework/State/FFStateContextInterface.h b/Source/UnrealFightingFramework/State/IFFStateAvatarInterface.h similarity index 53% rename from Source/UnrealFightingFramework/State/FFStateContextInterface.h rename to Source/UnrealFightingFramework/State/IFFStateAvatarInterface.h index a797aad..7b96379 100644 --- a/Source/UnrealFightingFramework/State/FFStateContextInterface.h +++ b/Source/UnrealFightingFramework/State/IFFStateAvatarInterface.h @@ -5,10 +5,10 @@ #include "CoreMinimal.h" #include "UObject/Interface.h" -#include "FFStateContextInterface.generated.h" +#include "IFFStateAvatarInterface.generated.h" UINTERFACE(MinimalAPI) -class UFFStateContextInterface : public UInterface +class UFFStateAvatarInterface : public UInterface { GENERATED_BODY() }; @@ -16,16 +16,14 @@ class UFFStateContextInterface : public UInterface /** * */ -class UNREALFIGHTINGFRAMEWORK_API IFFStateContextInterface +class UNREALFIGHTINGFRAMEWORK_API IFFStateAvatarInterface { GENERATED_BODY() public: - virtual bool CheckStateEnabled(uint8 StateType) = 0; - virtual bool CheckStance(uint8 Stance) = 0; - virtual bool CheckStateEntryConditions(const TArray& EntryConditions) = 0; + virtual bool CheckStateEnabled(uint8 StateType) = 0; - virtual bool CheckInputSequences(const TArray& InputSequences) = 0; + virtual bool CheckStateEntryConditions(const TArray& EntryConditions) = 0; }; diff --git a/Source/UnrealFightingFramework/State/IFFStateOwnerInterface.h b/Source/UnrealFightingFramework/State/IFFStateOwnerInterface.h new file mode 100644 index 0000000..8004cf2 --- /dev/null +++ b/Source/UnrealFightingFramework/State/IFFStateOwnerInterface.h @@ -0,0 +1,25 @@ +// Unreal Fighting Framework by Kevin Poretti + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Interface.h" + +#include "IFFStateOwnerInterface.generated.h" + +UINTERFACE(MinimalAPI) +class UFFStateOwnerInterface : public UInterface +{ + GENERATED_BODY() +}; + +/** + * + */ +class UNREALFIGHTINGFRAMEWORK_API IFFStateOwnerInterface +{ + GENERATED_BODY() + +public: + virtual bool CheckInputSequences(const TArray& InputSequences) = 0; +}; From afc34be5545f06c11bbb0259b4695aee676b10b9 Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Fri, 21 Jul 2023 20:35:23 -0400 Subject: [PATCH 20/38] Initialize input buffer structs properly --- .../Input/FFInputBufferComponent.h | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h index c8015dc..c9121f4 100644 --- a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h +++ b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h @@ -35,6 +35,14 @@ struct FFFInputState UPROPERTY(EditAnywhere, Meta = (Bitmask)) int32 Buttons; int32 DisabledButtons; + + FFFInputState() + : MoveAxes(FVector2D::ZeroVector) + , LookAxes(FVector2D::ZeroVector) + , Buttons(0) + , DisabledButtons(0) + { + } }; USTRUCT(BlueprintType) @@ -49,6 +57,12 @@ struct FFFInputCondition // The button state required for condition to be valid i.e. pressed or released UPROPERTY(EditAnywhere) EFFButtonState RequiredButtonState; + + FFFInputCondition() + : RequiredButtons(0) + , RequiredButtonState(EFFButtonState::BTNS_Pressed) + { + } }; USTRUCT(BlueprintType) @@ -61,6 +75,11 @@ struct FFFInputSequence UPROPERTY(EditAnywhere) int32 MaxDuration; + + FFFInputSequence() + : MaxDuration(0) + { + } }; UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) From c652716895d2d495c2a324cf84c3a35165f0a847 Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Fri, 21 Jul 2023 23:25:45 -0400 Subject: [PATCH 21/38] Change state property types so they can be edited as enums --- Source/UnrealFightingFramework/State/FFState.h | 8 ++++---- .../State/IFFStateAvatarInterface.h | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Source/UnrealFightingFramework/State/FFState.h b/Source/UnrealFightingFramework/State/FFState.h index d02282f..63541f9 100644 --- a/Source/UnrealFightingFramework/State/FFState.h +++ b/Source/UnrealFightingFramework/State/FFState.h @@ -38,7 +38,7 @@ struct FFFStateContext * 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. */ -UCLASS() +UCLASS(Blueprintable) class UNREALFIGHTINGFRAMEWORK_API UFFState : public UObject { GENERATED_BODY() @@ -62,18 +62,18 @@ public: * crouching, standing, or airborne for this state to be eligible for transitioning. */ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties") - uint8 StanceRequired; + int32 StanceRequired; /** * What is this state's category. * Used for determining what types of state can prematurely cancel this one. */ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties") - uint8 StateType; + int32 StateType; /** Conditions that need to be met in order for this state to be transitioned into */ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties") - TArray EntryConditions; + TArray EntryConditions; /** * Input sequences that needs to be present in the controlling player's input buffer for this diff --git a/Source/UnrealFightingFramework/State/IFFStateAvatarInterface.h b/Source/UnrealFightingFramework/State/IFFStateAvatarInterface.h index 7b96379..ed332e6 100644 --- a/Source/UnrealFightingFramework/State/IFFStateAvatarInterface.h +++ b/Source/UnrealFightingFramework/State/IFFStateAvatarInterface.h @@ -21,9 +21,9 @@ class UNREALFIGHTINGFRAMEWORK_API IFFStateAvatarInterface GENERATED_BODY() public: - virtual bool CheckStance(uint8 Stance) = 0; + virtual bool CheckStance(int32 Stance) = 0; - virtual bool CheckStateEnabled(uint8 StateType) = 0; + virtual bool CheckStateEnabled(int32 StateType) = 0; - virtual bool CheckStateEntryConditions(const TArray& EntryConditions) = 0; + virtual bool CheckStateEntryConditions(const TArray& EntryConditions) = 0; }; From 3938ac54b55bad99cb5bb16ef710ae792af41edf Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Sat, 22 Jul 2023 22:10:00 -0400 Subject: [PATCH 22/38] Expose state context members to blueprint and add Finish function to state --- .../Input/FFInputBufferComponent.h | 2 +- .../UnrealFightingFramework/State/FFState.cpp | 21 ++-- .../UnrealFightingFramework/State/FFState.h | 32 ++++-- .../State/FFStateMachineComponent.cpp | 103 ++++++++++++------ .../State/FFStateMachineComponent.h | 44 +++++--- 5 files changed, 130 insertions(+), 72 deletions(-) diff --git a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h index c9121f4..0a720d0 100644 --- a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h +++ b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h @@ -82,7 +82,7 @@ struct FFFInputSequence } }; -UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) +UCLASS( ClassGroup=(UnrealFightingFramework), meta=(BlueprintSpawnableComponent) ) class UNREALFIGHTINGFRAMEWORK_API UFFInputBufferComponent : public UActorComponent { GENERATED_BODY() diff --git a/Source/UnrealFightingFramework/State/FFState.cpp b/Source/UnrealFightingFramework/State/FFState.cpp index a8491aa..6e8e2ad 100644 --- a/Source/UnrealFightingFramework/State/FFState.cpp +++ b/Source/UnrealFightingFramework/State/FFState.cpp @@ -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(GetOuter()); - if(SMC) - { - SMC->SetSubStateLabel(InSubStateLabel); - } -} - - UWorld* UFFState::GetWorld() const { UFFStateMachineComponent* SMC = Cast(GetOuter()); diff --git a/Source/UnrealFightingFramework/State/FFState.h b/Source/UnrealFightingFramework/State/FFState.h index 63541f9..78c29b8 100644 --- a/Source/UnrealFightingFramework/State/FFState.h +++ b/Source/UnrealFightingFramework/State/FFState.h @@ -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 */ diff --git a/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp b/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp index 5b8c77c..2deab97 100644 --- a/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp +++ b/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp @@ -14,15 +14,18 @@ UFFStateMachineComponent::UFFStateMachineComponent() void UFFStateMachineComponent::Initialize() { + UFFState* EntryStateInstance = AddState(EntryState); + for(const TSubclassOf& 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 StateClassToA UFFState* TempState = NewObject(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; +} \ No newline at end of file diff --git a/Source/UnrealFightingFramework/State/FFStateMachineComponent.h b/Source/UnrealFightingFramework/State/FFStateMachineComponent.h index 9597c54..4b553a3 100644 --- a/Source/UnrealFightingFramework/State/FFStateMachineComponent.h +++ b/Source/UnrealFightingFramework/State/FFStateMachineComponent.h @@ -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 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> 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 States; /** From 8420f654e8ce7e3f24e8bbcbc08798c1515e7f4e Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Tue, 25 Jul 2023 22:25:01 -0400 Subject: [PATCH 23/38] Debug SM, expose StateData to blueprint, make state duration optional --- .../UnrealFightingFramework/State/FFState.cpp | 2 +- .../UnrealFightingFramework/State/FFState.h | 8 +++ .../State/FFStateData.h | 7 ++- .../State/FFStateMachineComponent.cpp | 58 ++++++++++++++----- .../State/FFStateMachineComponent.h | 20 ++----- 5 files changed, 60 insertions(+), 35 deletions(-) diff --git a/Source/UnrealFightingFramework/State/FFState.cpp b/Source/UnrealFightingFramework/State/FFState.cpp index 6e8e2ad..96a3a32 100644 --- a/Source/UnrealFightingFramework/State/FFState.cpp +++ b/Source/UnrealFightingFramework/State/FFState.cpp @@ -45,7 +45,7 @@ void UFFState::Update(float OneFrame, const FFFStateContext& InStateContext) { OnUpdate(OneFrame, InStateContext); - if(InStateContext.Parent->GetTicksInState() >= StateDuration) + if(bStateHasDuration && InStateContext.Parent->GetTicksInState() >= StateDuration) { Finish(InStateContext); } diff --git a/Source/UnrealFightingFramework/State/FFState.h b/Source/UnrealFightingFramework/State/FFState.h index 78c29b8..53edfea 100644 --- a/Source/UnrealFightingFramework/State/FFState.h +++ b/Source/UnrealFightingFramework/State/FFState.h @@ -49,6 +49,14 @@ public: UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties") FName Name; + /** + * True if this state has some duration where it should return to the entry state + * if this state is not cancelled out of by other means. + */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties") + bool bStateHasDuration; + + // TODO: this should only be editable if bStateHasDuration is true /** * How long this state will be active before finishing if this state is not cancelled out of * by other means. diff --git a/Source/UnrealFightingFramework/State/FFStateData.h b/Source/UnrealFightingFramework/State/FFStateData.h index 6452943..d2dcbc1 100644 --- a/Source/UnrealFightingFramework/State/FFStateData.h +++ b/Source/UnrealFightingFramework/State/FFStateData.h @@ -14,12 +14,15 @@ /** * */ -UCLASS() +UCLASS(BlueprintType) class UNREALFIGHTINGFRAMEWORK_API UFFStateData : public UDataAsset { GENERATED_BODY() public: + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) + TSubclassOf EntryState; + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) - TArray> States; + TArray> OtherStates; }; diff --git a/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp b/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp index 2deab97..ab225f8 100644 --- a/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp +++ b/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp @@ -5,18 +5,30 @@ // FF includes #include "FFState.h" +#if !UE_BUILD_SHIPPING +static int32 StateMachineDebug = 0; +FAutoConsoleVariableRef CVARStateMachineDebug(TEXT("ff.StateMachine.ShowDebug"), + StateMachineDebug, + TEXT("Print state machine information for character"), + ECVF_Cheat); +#endif + UFFStateMachineComponent::UFFStateMachineComponent() { // Don't use Unreal's tick instead use a fixed tick - PrimaryComponentTick.bCanEverTick = false; +#if !UE_BUILD_SHIPPING + PrimaryComponentTick.bCanEverTick = true; +#else + PrimaryComponentTick.bCanEverTick = false; +#endif } -void UFFStateMachineComponent::Initialize() +void UFFStateMachineComponent::Initialize(TSubclassOf EntryState, const TArray>& InitialStates) { UFFState* EntryStateInstance = AddState(EntryState); - for(const TSubclassOf& CurrState : DefaultStates) + for(const TSubclassOf& CurrState : InitialStates) { AddState(CurrState); } @@ -134,7 +146,12 @@ void UFFStateMachineComponent::FixedTick(float OneFrame) { // CurrentState should never be null // TODO: Should probably assert or whatever UE's equivalent is - check(CurrentState); + //check(CurrentState); + // TODO: yet another reason I want FULL CONTROL over when my game objects are created and initialized + if(!CurrentState) + { + return; + } // Should we switch states? UFFState* StateToTransitionTo = nullptr; @@ -161,16 +178,6 @@ void UFFStateMachineComponent::FixedTick(float OneFrame) TicksInState++; CurrentState->Update(OneFrame, GetCurrentStateContext()); } - - // Debug -} - - -void UFFStateMachineComponent::BeginPlay() -{ - Super::BeginPlay(); - - Initialize(); } @@ -185,7 +192,26 @@ UFFState* UFFStateMachineComponent::FindStateWithName(FName StateName) } UE_LOG(LogTemp, Warning, - TEXT("Could not find state in state machine with name %s on %s"), *StateName.ToString(), *Owner->GetName()); + TEXT("Could not find state in state machine with name %s"), *StateName.ToString()); return nullptr; -} \ No newline at end of file +} + +#if !UE_BUILD_SHIPPING +void UFFStateMachineComponent::TickComponent(float DeltaTime, ELevelTick TickType, + FActorComponentTickFunction* ThisTickFunction) +{ + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + // Debug + if(StateMachineDebug) + { + FString SMDebugString = "---State Machine Info---\n"; + SMDebugString.Append(FString::Printf(TEXT("Current State: %s\n"), *CurrentState->Name.ToString())); + SMDebugString.Append(FString::Printf(TEXT("Current SubState Label: %s\n"), *CurrentSubStateLabel.ToString())); + SMDebugString.Append(FString::Printf(TEXT("Ticks In State: %lld\n"), TicksInState)); + + GEngine->AddOnScreenDebugMessage(-1, 0.0f, FColor::Green, SMDebugString); + } +} +#endif diff --git a/Source/UnrealFightingFramework/State/FFStateMachineComponent.h b/Source/UnrealFightingFramework/State/FFStateMachineComponent.h index 4b553a3..0c8f6fa 100644 --- a/Source/UnrealFightingFramework/State/FFStateMachineComponent.h +++ b/Source/UnrealFightingFramework/State/FFStateMachineComponent.h @@ -29,7 +29,7 @@ public: /** * Creates and adds default states and enters the first state */ - void Initialize(); + void Initialize(TSubclassOf EntryState, const TArray>& InitialStates); /** * Initializes pointers to what owns this state machine and what avatar this state represents. @@ -116,20 +116,6 @@ protected: /** How many ticks have elapsed since the currently active state was entered */ int64 TicksInState; - /** - * 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 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> DefaultStates; - /** Current active state for this state machine */ UPROPERTY() UFFState* CurrentState; @@ -146,7 +132,9 @@ protected: */ UFFState* FindStateWithName(FName StateName); +#if !UE_BUILD_SHIPPING // UActorComponent interface - virtual void BeginPlay() override; + virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; // End of UActorComponent interface +#endif }; From e032ddaaa5c8e76cac70be74bde3be0232d503d8 Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Sat, 29 Jul 2023 21:18:34 -0400 Subject: [PATCH 24/38] Move state macros to plugin and make CanTransition virtual function --- Content/BPML_StateMacros.uasset | Bin 0 -> 65713 bytes .../Input/FFInputBufferComponent.cpp | 1 + .../UnrealFightingFramework/State/FFState.cpp | 72 ++++++++++-------- .../UnrealFightingFramework/State/FFState.h | 9 ++- .../State/FFStateMachineComponent.cpp | 17 ++++- .../State/FFStateMachineComponent.h | 13 +++- 6 files changed, 70 insertions(+), 42 deletions(-) create mode 100644 Content/BPML_StateMacros.uasset diff --git a/Content/BPML_StateMacros.uasset b/Content/BPML_StateMacros.uasset new file mode 100644 index 0000000000000000000000000000000000000000..be47fa1dc9839c0700e26f085253240c6282f3c8 GIT binary patch literal 65713 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 zX0VH3`X0V2U(InStateContext.Avatar); + if(SAI) + { + if(!(SAI->CheckStance(StanceRequired) && SAI->CheckStateEnabled(StateType) && SAI->CheckStateEntryConditions(EntryConditions))) + { + return false; + } + } + + IFFStateOwnerInterface* SOI = Cast(InStateContext.Owner); + if(SOI) + { + if(!SOI->CheckInputSequences(InputSequences)) + { + return false; + } + } + + return OnCanTransition(InStateContext); +} + + void UFFState::Enter(const FFFStateContext& InStateContext) { if(InStateContext.Avatar) @@ -54,43 +91,12 @@ void UFFState::Update(float OneFrame, const FFFStateContext& InStateContext) void UFFState::Finish(const FFFStateContext& InStateContext) { - InStateContext.Parent->SwitchToEntryState(); + InStateContext.Parent->GoToEntryState(); } -bool UFFState::CanTransition_Implementation(const FFFStateContext& InStateContext) +bool UFFState::OnCanTransition_Implementation(const FFFStateContext& InStateContext) { - /** - * Check to see if avatar implements StateAvatarInterface - * 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 - * so return true otherwise return false - */ - IFFStateAvatarInterface* SAI = Cast(InStateContext.Avatar); - if(SAI) - { - if(!(SAI->CheckStance(StanceRequired) && SAI->CheckStateEnabled(StateType) && SAI->CheckStateEntryConditions(EntryConditions))) - { - return false; - } - } - - IFFStateOwnerInterface* SOI = Cast(InStateContext.Owner); - if(SOI) - { - if(!SOI->CheckInputSequences(InputSequences)) - { - return false; - } - } - return true; } diff --git a/Source/UnrealFightingFramework/State/FFState.h b/Source/UnrealFightingFramework/State/FFState.h index 53edfea..bd73626 100644 --- a/Source/UnrealFightingFramework/State/FFState.h +++ b/Source/UnrealFightingFramework/State/FFState.h @@ -109,8 +109,7 @@ public: * all state entry conditions were met AND * at least one input sequence is present in the Owner's input buffer */ - UFUNCTION(BlueprintNativeEvent, Category="UFF|State") - bool CanTransition(const FFFStateContext& InStateContext); + virtual bool CanTransition(const FFFStateContext& InStateContext); /** * Called whenever this state is transitioned into. @@ -151,6 +150,12 @@ public: UFUNCTION(BlueprintCallable) virtual void Finish(const FFFStateContext& InStateContext); + /** + * Blueprint hook for overriding the CanTransition logic + */ + UFUNCTION(BlueprintNativeEvent, Category="UFF|State|Events") + bool OnCanTransition(const FFFStateContext& InStateContext); + /** * Blueprint hook for whenever this state is transitioned into */ diff --git a/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp b/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp index ab225f8..4af0eb3 100644 --- a/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp +++ b/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp @@ -85,7 +85,16 @@ void UFFStateMachineComponent::RemoveState(FName StateToRemove) UE_LOG(LogTemp, Error, TEXT("UFFStateMachineComponent::RemoveState is not yet implemented")); } -void UFFStateMachineComponent::SwitchStates(UFFState* NewState) +void UFFStateMachineComponent::GoToState(FName NewStateName) +{ + UFFState* NewState = FindStateWithName(NewStateName); + if(NewState) + { + GoToState(NewState); + } +} + +void UFFStateMachineComponent::GoToState(UFFState* NewState) { check(CurrentState); check(NewState); @@ -97,12 +106,12 @@ void UFFStateMachineComponent::SwitchStates(UFFState* NewState) CurrentSubStateLabel = NAME_None; } -void UFFStateMachineComponent::SwitchToEntryState() +void UFFStateMachineComponent::GoToEntryState() { // can't have an entry state if there are no states check(States.Num() > 0); - SwitchStates(States[0]); + GoToState(States[0]); } @@ -170,7 +179,7 @@ void UFFStateMachineComponent::FixedTick(float OneFrame) if(StateToTransitionTo && (CurrentState->Name != StateToTransitionTo->Name || StateToTransitionTo->bCanTransitionToSelf)) { - SwitchStates(StateToTransitionTo); + GoToState(StateToTransitionTo); } else { diff --git a/Source/UnrealFightingFramework/State/FFStateMachineComponent.h b/Source/UnrealFightingFramework/State/FFStateMachineComponent.h index 0c8f6fa..82066d9 100644 --- a/Source/UnrealFightingFramework/State/FFStateMachineComponent.h +++ b/Source/UnrealFightingFramework/State/FFStateMachineComponent.h @@ -62,19 +62,26 @@ public: */ void RemoveState(FName StateToRemove); + /** + * Transitions from CurrentState to the new state with the name passed to this function + * + * Triggers the Exit callback on the CurrentState and the Enter callback on the new state + */ + void GoToState(FName NewStateName); + /** * Transitions from CurrentState to the new state passed to this function * * Triggers the Exit callback on the CurrentState and the Enter callback on the new state */ - void SwitchStates(UFFState* NewState); + void GoToState(UFFState* NewState); /** * Transitions from CurrentState to the default entry state */ - void SwitchToEntryState(); + void GoToEntryState(); - UFUNCTION(BlueprintPure) + UFUNCTION(BlueprintPure) FORCEINLINE int64 GetTicksInState() const; /** From 87c2aafd49aa49285714274128a32410addb9c4e Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Sat, 29 Jul 2023 21:54:21 -0400 Subject: [PATCH 25/38] Fix crash with debug msg and fix state self transition issue --- .../UnrealFightingFramework/Input/FFPlayerController.cpp | 6 ++++++ .../State/FFStateMachineComponent.cpp | 8 ++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Source/UnrealFightingFramework/Input/FFPlayerController.cpp b/Source/UnrealFightingFramework/Input/FFPlayerController.cpp index 0401d1f..7fe45cd 100644 --- a/Source/UnrealFightingFramework/Input/FFPlayerController.cpp +++ b/Source/UnrealFightingFramework/Input/FFPlayerController.cpp @@ -35,6 +35,12 @@ void AFFPlayerController::FixedTick(float OneFrame) bool AFFPlayerController::CheckInputSequences(const TArray& InputSequences) { + // no input conditions to check + if(InputSequences.IsEmpty()) + { + return true; + } + for(const FFFInputSequence& ThisInputSequence : InputSequences) { if(InputBuffer->CheckInputSequence(ThisInputSequence)) diff --git a/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp b/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp index 4af0eb3..286b4dd 100644 --- a/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp +++ b/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp @@ -167,7 +167,8 @@ void UFFStateMachineComponent::FixedTick(float OneFrame) for(UFFState* ThisState : States) { // found a state - if(ThisState->CanTransition(GetCurrentStateContext())) + if(ThisState->CanTransition(GetCurrentStateContext()) && + (CurrentState->Name != ThisState->Name || ThisState->bCanTransitionToSelf)) { StateToTransitionTo = ThisState; break; @@ -176,8 +177,7 @@ void UFFStateMachineComponent::FixedTick(float OneFrame) // Lastly just check if the state we're about to transition into isn't the current state. // It is OK to transition if state's "CanTransitionToSelf" is true - if(StateToTransitionTo && - (CurrentState->Name != StateToTransitionTo->Name || StateToTransitionTo->bCanTransitionToSelf)) + if(StateToTransitionTo) { GoToState(StateToTransitionTo); } @@ -213,7 +213,7 @@ void UFFStateMachineComponent::TickComponent(float DeltaTime, ELevelTick TickTyp Super::TickComponent(DeltaTime, TickType, ThisTickFunction); // Debug - if(StateMachineDebug) + if(StateMachineDebug && CurrentState) { FString SMDebugString = "---State Machine Info---\n"; SMDebugString.Append(FString::Printf(TEXT("Current State: %s\n"), *CurrentState->Name.ToString())); From d89face8816857f754d34eebb4430ea9687f5490 Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Tue, 1 Aug 2023 21:13:02 -0400 Subject: [PATCH 26/38] Remove state entry conditions --- Source/UnrealFightingFramework/State/FFState.cpp | 2 +- Source/UnrealFightingFramework/State/FFState.h | 4 ---- .../UnrealFightingFramework/State/IFFStateAvatarInterface.h | 2 -- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/Source/UnrealFightingFramework/State/FFState.cpp b/Source/UnrealFightingFramework/State/FFState.cpp index b1fbb4d..8e498dc 100644 --- a/Source/UnrealFightingFramework/State/FFState.cpp +++ b/Source/UnrealFightingFramework/State/FFState.cpp @@ -28,7 +28,7 @@ bool UFFState::CanTransition(const FFFStateContext& InStateContext) IFFStateAvatarInterface* SAI = Cast(InStateContext.Avatar); if(SAI) { - if(!(SAI->CheckStance(StanceRequired) && SAI->CheckStateEnabled(StateType) && SAI->CheckStateEntryConditions(EntryConditions))) + if(!(SAI->CheckStance(StanceRequired) && SAI->CheckStateEnabled(StateType))) { return false; } diff --git a/Source/UnrealFightingFramework/State/FFState.h b/Source/UnrealFightingFramework/State/FFState.h index bd73626..1efcdea 100644 --- a/Source/UnrealFightingFramework/State/FFState.h +++ b/Source/UnrealFightingFramework/State/FFState.h @@ -79,10 +79,6 @@ public: UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties") int32 StateType; - /** Conditions that need to be met in order for this state to be transitioned into */ - UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties") - TArray EntryConditions; - /** * Input sequences that needs to be present in the controlling player's input buffer for this * state to be eligible for transitioning. diff --git a/Source/UnrealFightingFramework/State/IFFStateAvatarInterface.h b/Source/UnrealFightingFramework/State/IFFStateAvatarInterface.h index ed332e6..94609f6 100644 --- a/Source/UnrealFightingFramework/State/IFFStateAvatarInterface.h +++ b/Source/UnrealFightingFramework/State/IFFStateAvatarInterface.h @@ -24,6 +24,4 @@ public: virtual bool CheckStance(int32 Stance) = 0; virtual bool CheckStateEnabled(int32 StateType) = 0; - - virtual bool CheckStateEntryConditions(const TArray& EntryConditions) = 0; }; From 08d52a913f5f88cefd35e8b1372207b6ab57bf07 Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Sat, 5 Aug 2023 14:58:08 -0400 Subject: [PATCH 27/38] 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; }; From 93ca460c8a2dfef6e8baeac38a1defaa1116939b Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Sun, 6 Aug 2023 14:49:57 -0400 Subject: [PATCH 28/38] Call init when state is created by state machine and pass it state context --- Source/UnrealFightingFramework/State/FFState.cpp | 13 +++---------- Source/UnrealFightingFramework/State/FFState.h | 8 +++----- .../State/FFStateMachineComponent.cpp | 1 + 3 files changed, 7 insertions(+), 15 deletions(-) diff --git a/Source/UnrealFightingFramework/State/FFState.cpp b/Source/UnrealFightingFramework/State/FFState.cpp index a41541e..d4129d1 100644 --- a/Source/UnrealFightingFramework/State/FFState.cpp +++ b/Source/UnrealFightingFramework/State/FFState.cpp @@ -10,9 +10,9 @@ // UE includes #include "Components/SkeletalMeshComponent.h" -void UFFState::Init() +void UFFState::Init(const FFFStateContext& InStateContext) { - OnInit(); + OnInit(InStateContext); } bool UFFState::CanTransition(const FFFStateContext& InStateContext) @@ -153,7 +153,7 @@ void UFFState::RegisterInputHandler(const FFFInputSequence& InRequiredSequence, } -void UFFState::OnInit_Implementation() +void UFFState::OnInit_Implementation(const FFFStateContext& InStateContext) { } @@ -164,13 +164,6 @@ bool UFFState::OnCanTransition_Implementation(const FFFStateContext& InStateCont } -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 0669863..13e9c9f 100644 --- a/Source/UnrealFightingFramework/State/FFState.h +++ b/Source/UnrealFightingFramework/State/FFState.h @@ -76,7 +76,7 @@ public: * How long this state will be active before finishing if this state is not cancelled out of * by other means. */ - UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties") + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category="UFF State Properties") int64 StateDuration; /** @@ -123,7 +123,7 @@ public: /** * Called when state is first created. */ - virtual void Init(); + virtual void Init(const FFFStateContext& InStateContext); /** * Returns true if Avatar's is in the correct stance AND @@ -189,7 +189,7 @@ public: const FFFInputSequence& InRequiredSequence, FFFInputEventDelegate InDelegate); UFUNCTION(BlueprintNativeEvent, Category="UFF|State|Events") - void OnInit(); + void OnInit(const FFFStateContext& InStateContext); /** * Blueprint hook for overriding the CanTransition logic @@ -232,8 +232,6 @@ public: void OnBlock(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 7649068..0b7c716 100644 --- a/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp +++ b/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp @@ -56,6 +56,7 @@ UFFState* UFFStateMachineComponent::AddState(TSubclassOf StateClassToA if(!FindStateWithName(TempState->Name)) { States.Add(TempState); + TempState->Init(GetCurrentStateContext()); return TempState; } From bd195c7ee86cff5a45d19a5b570e2badc5d049b5 Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Thu, 10 Aug 2023 22:27:40 -0400 Subject: [PATCH 29/38] Add callback for when movement mode on character is changed --- .../UnrealFightingFramework/State/FFState.cpp | 13 +++++++++++++ .../UnrealFightingFramework/State/FFState.h | 16 ++++++++++++++++ .../State/FFStateMachineComponent.cpp | 19 +++++++++++++++++++ .../State/FFStateMachineComponent.h | 3 +++ 4 files changed, 51 insertions(+) diff --git a/Source/UnrealFightingFramework/State/FFState.cpp b/Source/UnrealFightingFramework/State/FFState.cpp index d4129d1..95cd2bd 100644 --- a/Source/UnrealFightingFramework/State/FFState.cpp +++ b/Source/UnrealFightingFramework/State/FFState.cpp @@ -126,6 +126,19 @@ void UFFState::Landed(const FHitResult& Hit, const FFFStateContext& InStateConte } +void UFFState::MovementModeChanged(EMovementMode PrevMovementMode, uint8 PreviousCustomMode, + EMovementMode NewMovementMode, uint8 NewCustomMode, const FFFStateContext& InStateContext) +{ + OnMovementModeChanged(PrevMovementMode, PreviousCustomMode, + NewMovementMode, NewCustomMode, InStateContext); + + if(NewMovementMode != ReqMovementMode || ((ReqMovementMode == MOVE_Custom) && NewCustomMode != RequiredCustomMode)) + { + Finish(InStateContext); + } +} + + void UFFState::Hit(const FFFStateContext& InStateContext) { OnHit(InStateContext); diff --git a/Source/UnrealFightingFramework/State/FFState.h b/Source/UnrealFightingFramework/State/FFState.h index 13e9c9f..6fe99ce 100644 --- a/Source/UnrealFightingFramework/State/FFState.h +++ b/Source/UnrealFightingFramework/State/FFState.h @@ -108,6 +108,12 @@ public: UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties") bool bCanTransitionToSelf = false; + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties") + TEnumAsByte ReqMovementMode; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties") + uint8 RequiredCustomMode; + /** * Animation to begin playing when this state is entered */ @@ -159,6 +165,10 @@ public: // TODO: document virtual void Landed(const FHitResult& Hit, const FFFStateContext& InStateContext); + // TODO: document + virtual void MovementModeChanged(EMovementMode PrevMovementMode, uint8 PreviousCustomMode, + EMovementMode NewMovementMode, uint8 NewCustomMode, const FFFStateContext& InStateContext); + // TODO: document // TODO: pass in hitdata struct as well virtual void Hit(const FFFStateContext& InStateContext); @@ -188,6 +198,7 @@ public: virtual void RegisterInputHandler( const FFFInputSequence& InRequiredSequence, FFFInputEventDelegate InDelegate); + // TODO: document UFUNCTION(BlueprintNativeEvent, Category="UFF|State|Events") void OnInit(const FFFStateContext& InStateContext); @@ -221,6 +232,11 @@ public: UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events") void OnLanded(const FHitResult& Hit, const FFFStateContext& InStateContext); + // TODO: document + UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events") + void OnMovementModeChanged(EMovementMode PrevMovementMode, uint8 PreviousCustomMode, + EMovementMode NewMovementMode, uint8 NewCustomMode, const FFFStateContext& InStateContext); + // TODO: document // TODO: pass in hitdata struct as well UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events") diff --git a/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp b/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp index 0b7c716..54bf8cd 100644 --- a/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp +++ b/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp @@ -160,6 +160,25 @@ void UFFStateMachineComponent::Landed(const FHitResult& Hit) } +void UFFStateMachineComponent::MovementModeChanged(EMovementMode PrevMovementMode, uint8 PreviousCustomMode, + EMovementMode NewMovementMode, uint8 NewCustomMode) +{ + // TODO: this check should be valid but isn't right now. + // Movement mode changed seems to be called whenever the character is first created but before + // any states are created and entered. + // For now just check if CurrentState is null and don't call the MovementModeChanged callback if + // so + //check(CurrentState) + if(!CurrentState) + { + return; + } + + CurrentState->MovementModeChanged(PrevMovementMode, PreviousCustomMode, + NewMovementMode, NewCustomMode, 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 4030cfc..11aed2d 100644 --- a/Source/UnrealFightingFramework/State/FFStateMachineComponent.h +++ b/Source/UnrealFightingFramework/State/FFStateMachineComponent.h @@ -104,6 +104,9 @@ public: // Events virtual void Landed(const FHitResult& Hit); + virtual void MovementModeChanged(EMovementMode PrevMovementMode, uint8 PreviousCustomMode, + EMovementMode NewMovementMode, uint8 NewCustomMode); + // IFFSystemInterface interface virtual void FixedTick(float OneFrame) override; // End of IFFSystemInterface interface From 6c06611901756b19903d0a4fb15e45abd1be4f13 Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Sat, 26 Aug 2023 16:03:01 -0400 Subject: [PATCH 30/38] Only disable most recent input if transition into state is successful --- .../Input/FFInputBufferComponent.cpp | 9 ++++++--- .../Input/FFInputBufferComponent.h | 2 ++ .../Input/FFPlayerController.cpp | 5 +++++ .../Input/FFPlayerController.h | 2 ++ .../State/FFStateMachineComponent.cpp | 13 +++++++++++++ .../State/IFFStateOwnerInterface.h | 2 ++ 6 files changed, 30 insertions(+), 3 deletions(-) diff --git a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp index 375bd0e..a15e594 100644 --- a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp +++ b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp @@ -59,9 +59,6 @@ bool UFFInputBufferComponent::CheckInputSequence(const FFFInputSequence& InputSe // All conditions were met if(CondIdx == -1) { - // disable most recent input - InputBuffer[InputBuffer.Num() - 1].DisabledButtons |= InputBuffer[InputBuffer.Num() - 1].Buttons; - InputBuffer[InputBuffer.Num() - 2].DisabledButtons |= InputBuffer[InputBuffer.Num() - 2].Buttons; return true; } @@ -74,3 +71,9 @@ bool UFFInputBufferComponent::CheckInputSequence(const FFFInputSequence& InputSe return false; } + +void UFFInputBufferComponent::DisableMostRecentInput() +{ + InputBuffer[InputBuffer.Num() - 1].DisabledButtons |= InputBuffer[InputBuffer.Num() - 1].Buttons; + InputBuffer[InputBuffer.Num() - 2].DisabledButtons |= InputBuffer[InputBuffer.Num() - 2].Buttons; +} diff --git a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h index 35184d3..698e603 100644 --- a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h +++ b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.h @@ -94,6 +94,8 @@ public: bool CheckInputSequence(const FFFInputSequence& InputSequence); + void DisableMostRecentInput(); + protected: /** The underlying buffer data structure for holding past input states */ TCircleBuffer InputBuffer; diff --git a/Source/UnrealFightingFramework/Input/FFPlayerController.cpp b/Source/UnrealFightingFramework/Input/FFPlayerController.cpp index 9744321..c8fea47 100644 --- a/Source/UnrealFightingFramework/Input/FFPlayerController.cpp +++ b/Source/UnrealFightingFramework/Input/FFPlayerController.cpp @@ -56,6 +56,11 @@ bool AFFPlayerController::CheckInputSequences(const TArray& In return false; } +void AFFPlayerController::DisableMostRecentInput() +{ + InputBuffer->DisableMostRecentInput(); +} + void AFFPlayerController::SetupInputComponent() { diff --git a/Source/UnrealFightingFramework/Input/FFPlayerController.h b/Source/UnrealFightingFramework/Input/FFPlayerController.h index acf648b..bcc485b 100644 --- a/Source/UnrealFightingFramework/Input/FFPlayerController.h +++ b/Source/UnrealFightingFramework/Input/FFPlayerController.h @@ -55,6 +55,8 @@ public: virtual bool CheckInputSequence(const FFFInputSequence& InInputSequence) override; virtual bool CheckInputSequences(const TArray& InputSequences) override; + + virtual void DisableMostRecentInput() override; // End of IFFStateOwnerInterface // APlayerController interface diff --git a/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp b/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp index 54bf8cd..16a0993 100644 --- a/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp +++ b/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp @@ -4,6 +4,7 @@ // FF includes #include "FFState.h" +#include "Input/FFPlayerController.h" #if !UE_BUILD_SHIPPING static int32 StateMachineDebug = 0; @@ -100,6 +101,18 @@ void UFFStateMachineComponent::GoToState(UFFState* NewState) check(CurrentState); check(NewState); + // if state being transitioned into requires an input sequence we need to disable the most + // recent input in the player controller input buffer to prevent unwanted "double firing" of + // actions that have short durations but very lenient required input sequences + if(NewState->InputSequences.Num() > 0) + { + IFFStateOwnerInterface* PC = Cast(Owner); + if(PC) + { + PC->DisableMostRecentInput(); + } + } + CurrentState->Exit(GetCurrentStateContext()); CurrentState = NewState; CurrentState->Enter(GetCurrentStateContext()); diff --git a/Source/UnrealFightingFramework/State/IFFStateOwnerInterface.h b/Source/UnrealFightingFramework/State/IFFStateOwnerInterface.h index d6de2c3..75631aa 100644 --- a/Source/UnrealFightingFramework/State/IFFStateOwnerInterface.h +++ b/Source/UnrealFightingFramework/State/IFFStateOwnerInterface.h @@ -24,4 +24,6 @@ public: virtual bool CheckInputSequence(const FFFInputSequence& InInputSequence) = 0; virtual bool CheckInputSequences(const TArray& InInputSequences) = 0; + + virtual void DisableMostRecentInput() = 0; }; From c654bfd8fb54ed6ca78fc93b3c0643010e872e1f Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Wed, 18 Oct 2023 22:44:04 -0400 Subject: [PATCH 31/38] Make sure owner and avatar implement interfaces in CanTransition --- Source/UnrealFightingFramework/State/FFState.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Source/UnrealFightingFramework/State/FFState.cpp b/Source/UnrealFightingFramework/State/FFState.cpp index 95cd2bd..231beac 100644 --- a/Source/UnrealFightingFramework/State/FFState.cpp +++ b/Source/UnrealFightingFramework/State/FFState.cpp @@ -37,6 +37,11 @@ bool UFFState::CanTransition(const FFFStateContext& InStateContext) return false; } } + else + { + UE_LOG(LogTemp, Error, TEXT("CanTransition :: Avatar of FFFStateContext does not implement IFFStateAvatarInterface")); + return false; + } IFFStateOwnerInterface* SOI = Cast(InStateContext.Owner); if(SOI) @@ -46,6 +51,11 @@ bool UFFState::CanTransition(const FFFStateContext& InStateContext) return false; } } + else + { + UE_LOG(LogTemp, Error, TEXT("CanTransition :: Owner of FFFStateContext does not implement IFFStateOwnerInterface")); + return false; + } return OnCanTransition(InStateContext); } From 09c817ef49275128d63b41efbef76961fb769bc0 Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Fri, 20 Oct 2023 15:34:41 -0400 Subject: [PATCH 32/38] Don't finish state if move mode changes but no move mode is required --- Content/BPML_StateMacros.uasset | Bin 65713 -> 82095 bytes .../Input/FFPlayerController.h | 2 ++ .../UnrealFightingFramework/State/FFState.cpp | 7 ++++++- .../State/IFFStateOwnerInterface.h | 4 +++- 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Content/BPML_StateMacros.uasset b/Content/BPML_StateMacros.uasset index be47fa1dc9839c0700e26f085253240c6282f3c8..a5b5ec8878ef3f3dfb91aa3fd1bc62851374585f 100644 GIT binary patch literal 82095 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 Ox;_8cQ1)6V|Nj6`F|;ZG literal 65713 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 InputSequences) override; virtual void DisableMostRecentInput() override; diff --git a/Source/UnrealFightingFramework/State/FFState.cpp b/Source/UnrealFightingFramework/State/FFState.cpp index 231beac..962480f 100644 --- a/Source/UnrealFightingFramework/State/FFState.cpp +++ b/Source/UnrealFightingFramework/State/FFState.cpp @@ -142,7 +142,12 @@ void UFFState::MovementModeChanged(EMovementMode PrevMovementMode, uint8 Previou OnMovementModeChanged(PrevMovementMode, PreviousCustomMode, NewMovementMode, NewCustomMode, InStateContext); - if(NewMovementMode != ReqMovementMode || ((ReqMovementMode == MOVE_Custom) && NewCustomMode != RequiredCustomMode)) + // TODO: Movement mode MOVE_None means movement is disabled but in this context it means no movement + // mode is specifically required to stay in this state i.e. changing from falling to walking + // will not exit out of this state. I think I want to use my own movement mode enum just so I + // can explicitly document this is what is meant by none + if((ReqMovementMode != EMovementMode::MOVE_None && NewMovementMode != ReqMovementMode) || + ((ReqMovementMode == MOVE_Custom) && NewCustomMode != RequiredCustomMode)) { Finish(InStateContext); } diff --git a/Source/UnrealFightingFramework/State/IFFStateOwnerInterface.h b/Source/UnrealFightingFramework/State/IFFStateOwnerInterface.h index 75631aa..5aeb2e4 100644 --- a/Source/UnrealFightingFramework/State/IFFStateOwnerInterface.h +++ b/Source/UnrealFightingFramework/State/IFFStateOwnerInterface.h @@ -7,7 +7,7 @@ #include "IFFStateOwnerInterface.generated.h" -UINTERFACE(MinimalAPI) +UINTERFACE(MinimalAPI, NotBlueprintable) class UFFStateOwnerInterface : public UInterface { GENERATED_BODY() @@ -21,8 +21,10 @@ class UNREALFIGHTINGFRAMEWORK_API IFFStateOwnerInterface GENERATED_BODY() public: + UFUNCTION(BlueprintCallable, Category="UFF State Owner Interface") virtual bool CheckInputSequence(const FFFInputSequence& InInputSequence) = 0; + UFUNCTION(BlueprintCallable, Category="UFF State Owner Interface") virtual bool CheckInputSequences(const TArray& InInputSequences) = 0; virtual void DisableMostRecentInput() = 0; From b3cee07adad83e0a1d687c808052c0c5e656eb9b Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Fri, 20 Oct 2023 19:50:05 -0400 Subject: [PATCH 33/38] Return bool flags on IsInFrameRange macro for start or end frame --- Content/BPML_StateMacros.uasset | Bin 82095 -> 92152 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/Content/BPML_StateMacros.uasset b/Content/BPML_StateMacros.uasset index a5b5ec8878ef3f3dfb91aa3fd1bc62851374585f..ac8e0d83595337c5c27c071d61413bb3fc7413a2 100644 GIT binary patch literal 92152 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 b_4Lbds(y58k9qGG9n?Rg|7x6y-_@z_t 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 Ox;_8cQ1)6V|Nj6`F|;ZG From c43aca536a98f3314abd4fdf1b67f5027d117718 Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Fri, 20 Oct 2023 22:58:41 -0400 Subject: [PATCH 34/38] Fix substate label issue and add IsAfterFrame and IsBeforeFrame macros --- Content/BPML_StateMacros.uasset | Bin 92152 -> 118896 bytes .../State/FFStateMachineComponent.cpp | 2 +- .../State/FFStateMachineComponent.h | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Content/BPML_StateMacros.uasset b/Content/BPML_StateMacros.uasset index ac8e0d83595337c5c27c071d61413bb3fc7413a2..092c1de36d56509c641b38d7f2593f76f95b5e3b 100644 GIT binary patch literal 118896 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 b_4Lbds(y58k9qGG9nExit(GetCurrentStateContext()); CurrentState = NewState; - CurrentState->Enter(GetCurrentStateContext()); TicksInState = 0; CurrentSubStateLabel = NAME_None; + CurrentState->Enter(GetCurrentStateContext()); } void UFFStateMachineComponent::GoToEntryState() diff --git a/Source/UnrealFightingFramework/State/FFStateMachineComponent.h b/Source/UnrealFightingFramework/State/FFStateMachineComponent.h index 11aed2d..38c990e 100644 --- a/Source/UnrealFightingFramework/State/FFStateMachineComponent.h +++ b/Source/UnrealFightingFramework/State/FFStateMachineComponent.h @@ -94,7 +94,7 @@ public: FORCEINLINE FName GetCurrentSubStateLabel() const; UFUNCTION(BlueprintCallable) - FORCEINLINE void SetSubStateLabel(FName InSubStateLabel); + void SetSubStateLabel(FName InSubStateLabel); /** * From 755ee3434334a2837ddd9a1742fdd68d71196946 Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Sat, 21 Oct 2023 18:51:04 -0400 Subject: [PATCH 35/38] Move fixed framerate logic from SNG to FF --- Content/BPML_StateMacros.uasset | Bin 118896 -> 118616 bytes .../GameplayFramework/FFGameState.cpp | 25 +++++++++++ .../GameplayFramework/FFGameState.h | 41 ++++++++++++++++++ .../State/FFStateMachineComponent.cpp | 15 ++----- .../State/FFStateMachineComponent.h | 19 +++++++- 5 files changed, 86 insertions(+), 14 deletions(-) create mode 100644 Source/UnrealFightingFramework/GameplayFramework/FFGameState.cpp create mode 100644 Source/UnrealFightingFramework/GameplayFramework/FFGameState.h diff --git a/Content/BPML_StateMacros.uasset b/Content/BPML_StateMacros.uasset index 092c1de36d56509c641b38d7f2593f76f95b5e3b..35fbad5e0ca0b19d98bc03daa46be187b3797785 100644 GIT binary patch delta 17134 zcmcgz33wDm_Mg%ONK8yZLP8)WK@t)|B;7MTeF!RjOdF066g)Uhgb;2J5)eE<0YMH0 zTPQ$5P>?8y$l-yUii*npcNe@CBBy^{iTkOj?8?6C>F&9DCQ-un_l2H*^{VQ<-#hA6 zbyK&p>G2g!7i>p03wOBVHHX7-2{|0s!u{gH%S_kwWQU_AJoHU)IF=SV9MI;NPdglo z;O`CiJ6d_X?~|97%37C_-6#7`82x5yDr03bHd>m3#9x*n}0;hqC2#O-`$5#7+q0y*My4kO2yz&<>M=csMTWzR}CLK zrlg{(d`Nj!xnmOh^gL~D)A>m;z)z?Op`M<$Mcdr68Qnzdcl~Em#~CwCH16NSbPpN7 zON=|Y+40S6Am0<=`KCJjC!Q6aF@SG&1Nok;mv5@WK{TFIFQb=lZUgwfgYnb`@;wFf z>sKVG$$(Ecfbai-Z$bn4stmrVktQYsPHEtP^BTbSEsXbYAfH$NVCN!?j|4L6PvZ@m zISAo<(1z~;gJudv6q#(pH_l<;zrbiDHxg*%vO~t7&lI{oK2sQ5zRupc^nXq5>=>Uh z_=v_Gjo&5Ospdu3)SSTOQ&6Tht~N?zlk-!njaCx#y^P;Er!d{s;8_ejlMFf}C`X*} zyXq9a<2970y_-@L`+`B6X3$!_-M) z&RH+*QgYkW=?0C+xz_moonQM5oAzhUdHX+?+BTIWACf7X!QTb)4_v}|WBf4f`9FHw z`=t52b0_?8Xo<7HX{VMGK)+W ztQu?a@cc8o=y;zxFK7WoSxk2ScJn@w^n0+|0iEew{;x0o#2vP|DR`?~040$k6G zL@hHrE_SP-(=l0ZT9DB(m85mkq!~u@ykeudK>NK-VysE7>0y#vHHBXOJq4rZ-d-k) zw(Z8mSd;vpJ{I}r9N&O!UyHm9$R_ul19fs;R)G%flYSD)#5!=zEw@;jz$0NHpcA{9 zEC&Z$EJRv6mZYHuOGoXGytr7iXP;pfc~HKOj7ON{R5BO=-+#U@Mg=qzKuB|cL*g68 zdYf z*_)ak&}=GcHm{x)&>TE>%Xvm~f$pq!^UqsgvBZW^(y>Tq>D8{F6`6pn|0WKl1`btA zLs(2rjuBqC-d}F95Z551nA$jvQI@?B!eVN4-Z0z#mkgGUsdjQ~c{!ljl)>zM?K*1$ zVlMv?T!sway%xe^juLIecjhLGg?NAxI~{~SR0!hF;Q z5`L6+a|iy~&kp(#mI2|kKVeEiPTF+nN{mk6N;HP~DVS-74O7%1zoE%U^NrsABc=E; zZSGgtI~pz3HqdPJ8d%7eZ2p#h_O+P1jBcgc6DRs1Nvl1PhDx>HPIO5vHd=J~%o?MM zR(dimwbE=NlXWp3Dz%4CQZZ&zl(y>o7Wme#C?To8$za?Q@t!!Ow_UHr*T$m(+8y6y zq?Cjkxs>!C#uJf-Tq)k0qg0!FD#x7U_^HGe z4Id)M8lvDdV-7Lvbc7*L4`TEtrFuv0L}A+)))>R9Wp4Am6Fq!dcIbYtAsv>V zGdm;|&q+_B-$+olw7Gs;7x5%rqSgMEi^_0T97=0vy0#vyWhO?(eZG;ZdTD!Fw2Zme zpcdmn>8NGQJ;q}NUJ-{{#q>2EN030s%w|2j?4lHfajB9Zv2Ic1JT6vpDyqv(Nt}o; z$Dv|Wtcm|RZ@Hsy0`dBQ3C5(M)_xN_mQ5xQIZ31#C*!2uBJJjA7wvIT0xP&ZvPWe^ zd^im~>z%-98rPb@kdQObQkTi{NUFyv z&@$t4sV>GXx_P(AxFlLoC|0CJd?*bqYE+M*C6$voMqw0+;b_*$(=sngg3HahoRZAD zuH0jY*@8S~M`5?vga5pA`|RI$9c4N%Sz&lg{%Y`;Z5P9POt)Ws9wTrrm7xU2Bgw2F zDV$1iyv%b>4@;{YL)GIk+S|YNyxPtpJ3WkBkpWsa$GRm&R25EiNsP)1ypxgHD|MD^ z)uVvPRF+{`Cnqzq+o{MDE6B7=DU`>}Hhz8qH6#d7nenI`E2%UmC{EGCgACxNv_$^} zZ^1_%Krl#~4^db*!FMBM`bie&qD4ss zi3O2$x><=4In~2RqT+Niyi3vQE=-D)SoU6nuDYL)d?LCShH-&0JshMF(W$C}$nYHH zq8a9j{e)qh0hqA3b5RsN znS!o*Wx}y6?U5j~DJKg>isTVQ85SFXp(#dUdHRaoL=`>Yb55|500x7?jH7AZDR?}b z)5Wm@*Z6M2!nzJ+kj$u1L3?OHR-H602^9FI(*xrCMK|#kv$(7^iVDsli(32K2*b0i3kn3$DL_E;vYSx_7w7RbzK6(enWA7RXGED7 zpcIjbd%7gDXi^XWSH9iR7aUv^gUszTo{{5okD8na?y-9bM1HSaG{C#qq-L?SB6>hH z$^)V?vfy^|oJ*qIv@C;N>fPdOYlk`~nOkAi*a~^{U9Vz1mEu({lu{rYkLE#=wS~OT zXfQW^`ra;jH=Kic_jV^?zZY0`;lp{ly0v*|puT+z+;%O=hZ?$eKT5~9Jda{*7^>Ps zj10niN>LjALqS<#O07W!=*gJdP3eeEQ0u{)u(r6c1Hui`I>yo(?{vY=JW$7tmX5c= zEI~RhFSJYw+kzDQw?dTSCv+?P5reoelIy$A$8Q2H`{6xq)WgC+m%bZ|+F(qf6g;sb zlIqoC<%~$yyW7Oaxa-!#U5b#oH7DVwmZ*H&-(dfC$bAD3XTWHNrU z2<7{0cyl3i?NdI!vZ5;1`@KSTNqN<<>>k7KA74Ixa(3zcBPs@0W#3sbv|@b4n86he zhi@;QN$GoW(6<8~;X!Vq?=AS`B=G*%W}+50fZRr-YQm5n*!BmF9s?AQ{ZN zMOF3qG1+&QkE*VS?K-BaqOxK<%f!Z(j2%)vYHW7Cn4Oy~wRfOS1|H;e!SWj&`O8i; zu3ped$V7IYm6r3A!BhbV^X8spG#pF%AsWunb$2Oq>+W*oMbst`0T6m&Bgi|A$crCX zgVM=6;{bW{@m9`qcKkWlaH03};e~F4j|q)}^hpcpZ1@!@dD0h`vd^s=xTSddkh)IAUo`GP88&n|S#Yaqj!q4ApX%FmCD3w| z9yVv@;r?A3#UfVX3>AHiXo{YUKNcHqggH`pBecOIWOR4EMj(mOFJ=>zkc3|cF95uY z8WkD?%M@L5Jr(X$!b%>L*`@{tYdaOK3!${f-0hZiaL0vEHw2`mUv7tWRCkK_GC1=H z^6=JQUHIbf%voi}iB6jy`|zdVMz;Q-7`6}QFupU-;a$<=7Fl0z9=Q|c;ZoRYppo7s zH={RdYq9uxD&4_(LI>>B12KkhD*?Vh;qZTy&hK= zT1FM!<&$moTTl;F-i273@h;wFBVZn0ungs(^p+$x;ugq4=%yLsq-}W(nacFAUQLam=pHtSB+e%sU zB1p3Ievsr298d%J0(24$EY*WiHtOa*9)%Z`;a2p;L-ZMwhS*l<`-XsF!p^h0!qh5y zD49Zk!;>ANRnkCay{G8XetGMJ=s&3VPoa#|6|5X;tKfB&k>)&CY0YV!*TkO;nG-)T z%!lNw!)zz&IXu!t1tYA9KD3kzoM_buWL0qg9AP`hw6`Jj<_rh_Uo$d{$p(!=vXP*4 zjqs`$p!5nUU~t@MpGMSZ+u7cJ8w`JbbeM_s>T%svaLqM&6Y#hf!HjFG5TASR=atVTO`h0uW#P#b&K=fc*qD!f`e@ympxu@;QhQ40{EWkC_nd^8|#L)|7u_NRnHvwdvw($r2_~SO#9!%voaK( z;+Z~pM$HTlPsS7Vz|(nFz3^ns2FDrg+*Piu=R6yy?EB)TiiKI^G$xpKRgkesNt9WH z#fL^0&Y9ywrg%*mWu8%OR!ndghH>cB*vA zO#n|Yt?}Ubv+2kJ%=T?U;?p`}ZJxHR&h#KkTwuHMLGbYy+M;mX0+drv5(lt4=l*MY z_n$WL&AY$ZBx}ynp$A^-2sj1P{uelr!#i?WX`7TNPzQuNlA59UysAb+Qpl->xr`^{ zUeBV5VVe_h{-S!_DrzwxwYUGO%w2VJ)i)0Jo{{qE+I>YJZ7{9zkcyb8{SR<$ihy6S z51Wi7wvGb39^W~D2~`4qerZH34!|++!^ziN zywo_p7r}RJ`nw83#|isUYuL1T9bVr~*_;Mkw^CO!V_2%C>@t*H-k>&wNU8lYyCF2g&kY=MSy+0LhWyaf0MgR1>_5sux#yuMHS zFnq~XY-#0#mG~C9?2hdHVGaFSuo*60kFo$+IM&Vxer3CJjjoHnCxx@E6#UB?c!M(v z9~tO(!tTRX-wVv`1{Lq&EpVi<^JUwx(z%AN1q+Pw3hl7907YK#*R4fy{^~ZwFxKwE zdmLzUp!IT%uGU*MwkFc+_A_uOhr7H7TEIp>1#f%B_Ef3cs|_?8yqSo?1?y}=0>8eF zIJ(~}mWJ6&4}obRN1b^4dY{Qou7@%ythIDKXTRomsN=IFwDEu0JjRxNHzXl%Itn#G z(&ytB|KGXQm&6qVavxunHyRE;j=pYdjVCwS1`M2#q~JlXf_?6fg(wAp8kmRllOWBV3cJ(p~~0z+Wxdem%DF-nTJ$(3Kdsf$L=2o}F( z8>n!eT88cqca#R_H+Gq9z&p@saHQoyKWyQ*+1X*&UKA4(E{EadcWonOaHIu8-?JTt zPuaaDvcch&?G_IB=sn;|)!V_BMu*FB(K~F_t_>k?*ohWeaqWw&rMtkOqa!80nvaM; zzJ!pEt9J)eJ_@1y^#yAPoI%4%TfN6N{tDj%d)8YSG<)AB5e5_my46p=Z)5cqPWeDrOkcfLd;q~0)~M2lwnokT=+E1V*ujcByYu#>`JD$|H+w~Fk69C6e0#FE zZ(9kTurCC9Qbl(B*fuve`J_Qu!#6Z3c+?6w)AaKty$Xw*?!r`FdLWVYMh_%A#qgs2 zKD^)A9}e#WAz?vuz2zX(1Ai_oz-Lnrp;u6Ey!#L;6wL~lgckW9oBq!+LC>H@Ks%|W KOYlvHk^Eoh+`OCs delta 17311 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 mriIfQ)J-t9XJnu?yaK3;!FhOw{! diff --git a/Source/UnrealFightingFramework/GameplayFramework/FFGameState.cpp b/Source/UnrealFightingFramework/GameplayFramework/FFGameState.cpp new file mode 100644 index 0000000..ebb0e9a --- /dev/null +++ b/Source/UnrealFightingFramework/GameplayFramework/FFGameState.cpp @@ -0,0 +1,25 @@ +// Project Sword & Gun Copyright Kevin Poretti + +#include "GameplayFramework/FFGameState.h" + +void AFFGameState::OnFixedTick(float OneTick) +{ +} + + +void AFFGameState::Tick(float DeltaSeconds) +{ + Super::Tick(DeltaSeconds); + + // Run anything game logic related at a fixed tick rate + // TODO: Interpolate between prev and current game state if there is time left in accumulator + AccumulatedTime += DeltaSeconds; + while(AccumulatedTime > ONE_TICK) + { + OnFixedTick(ONE_TICK); + + AccumulatedTime -= ONE_TICK; + + CurrentTick++; + } +} diff --git a/Source/UnrealFightingFramework/GameplayFramework/FFGameState.h b/Source/UnrealFightingFramework/GameplayFramework/FFGameState.h new file mode 100644 index 0000000..7e89f24 --- /dev/null +++ b/Source/UnrealFightingFramework/GameplayFramework/FFGameState.h @@ -0,0 +1,41 @@ +// Project Sword & Gun Copyright Kevin Poretti + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/GameState.h" +#include "FFGameState.generated.h" + +constexpr float ONE_TICK = 0.0083333333; +constexpr int64 TICKS_PER_SECOND = 120; + +/** + * + */ +UCLASS() +class UNREALFIGHTINGFRAMEWORK_API AFFGameState : public AGameState +{ + GENERATED_BODY() + +public: + + UFUNCTION(BlueprintPure) + int64 GetCurrentTick() const { return CurrentTick; } + + virtual void OnFixedTick(float OneTick); + + // Begin AActor interface + virtual void Tick(float DeltaSeconds) override; + // End AActor interface +private: + /** + * Amount of time accumulated from ticks. When Tick is called the delta time since the + * last Tick will be added to this variable. + * + * Once enough time has accumulated to simulate at least one frame (defined by the value ONE_TICK) then the + * game logic will update/tick. ONE_TICK's worth of time will be subtracted from the this variable + */ + float AccumulatedTime = 0; + + int64 CurrentTick = 0; +}; diff --git a/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp b/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp index b942bbf..679111c 100644 --- a/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp +++ b/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp @@ -4,9 +4,11 @@ // FF includes #include "FFState.h" +#include "GameplayFramework/FFGameState.h" #include "Input/FFPlayerController.h" #if !UE_BUILD_SHIPPING +class ASNGGameState; static int32 StateMachineDebug = 0; FAutoConsoleVariableRef CVARStateMachineDebug(TEXT("ff.StateMachine.ShowDebug"), StateMachineDebug, @@ -116,6 +118,7 @@ void UFFStateMachineComponent::GoToState(UFFState* NewState) CurrentState->Exit(GetCurrentStateContext()); CurrentState = NewState; TicksInState = 0; + TickStateWasEntered = GetWorld()->GetGameState()->GetCurrentTick(); CurrentSubStateLabel = NAME_None; CurrentState->Enter(GetCurrentStateContext()); } @@ -129,12 +132,6 @@ void UFFStateMachineComponent::GoToEntryState() } -int64 UFFStateMachineComponent::GetTicksInState() const -{ - return TicksInState; -} - - FName UFFStateMachineComponent::GetCurrentStateName() const { check(CurrentState) @@ -143,12 +140,6 @@ FName UFFStateMachineComponent::GetCurrentStateName() const } -FName UFFStateMachineComponent::GetCurrentSubStateLabel() const -{ - return CurrentSubStateLabel; -} - - void UFFStateMachineComponent::SetSubStateLabel(FName InSubStateLabel) { CurrentSubStateLabel = InSubStateLabel; diff --git a/Source/UnrealFightingFramework/State/FFStateMachineComponent.h b/Source/UnrealFightingFramework/State/FFStateMachineComponent.h index 38c990e..52b9c54 100644 --- a/Source/UnrealFightingFramework/State/FFStateMachineComponent.h +++ b/Source/UnrealFightingFramework/State/FFStateMachineComponent.h @@ -82,7 +82,15 @@ public: void GoToEntryState(); UFUNCTION(BlueprintPure) - FORCEINLINE int64 GetTicksInState() const; + FORCEINLINE int64 GetTicksInState() const { return TicksInState; } + + /** + * Returns the tick number which corresponds to when this state was entered into. + * + * The tick number represents the ticks that have elapsed since the game began. + */ + UFUNCTION(BlueprintPure) + FORCEINLINE int64 GetTickStateWasEntered() const { return TickStateWasEntered; } /** * Returns the name of the current state @@ -91,7 +99,7 @@ public: FORCEINLINE FName GetCurrentStateName() const; UFUNCTION(BlueprintPure) - FORCEINLINE FName GetCurrentSubStateLabel() const; + FORCEINLINE FName GetCurrentSubStateLabel() const { return CurrentSubStateLabel; } UFUNCTION(BlueprintCallable) void SetSubStateLabel(FName InSubStateLabel); @@ -129,6 +137,13 @@ protected: /** How many ticks have elapsed since the currently active state was entered */ int64 TicksInState; + /** + * The tick number which corresponds to when this state was entered into. + * + * The tick number represents the ticks that have elapsed since the game began. + */ + int64 TickStateWasEntered; + /** Current active state for this state machine */ UPROPERTY() UFFState* CurrentState; From 28963c78e9684f2878eae1a3041ff133020784b3 Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Sun, 22 Oct 2023 15:30:11 -0400 Subject: [PATCH 36/38] Add delegate for when state machine transitions to new state --- .../Input/FFInputBufferComponent.cpp | 7 +++++++ .../State/FFStateMachineComponent.cpp | 11 +++++++++-- .../State/FFStateMachineComponent.h | 14 ++++++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp index a15e594..f34fc0d 100644 --- a/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp +++ b/Source/UnrealFightingFramework/Input/FFInputBufferComponent.cpp @@ -16,6 +16,13 @@ void UFFInputBufferComponent::AddInput(const FFFInputState& InputState) bool UFFInputBufferComponent::CheckInputSequence(const FFFInputSequence& InputSequence) { + if(InputSequence.Sequence.IsEmpty()) + { + UE_LOG(LogTemp, Error, + TEXT("FFInputBufferComponent :: CheckInputSequence - tried to check input sequence but it was empty")); + return false; + } + int CondIdx = InputSequence.Sequence.Num() - 1; int ElapsedFrames = 0; for(int InpIdx = InputBuffer.Num() - 1; InpIdx > 1; InpIdx--) diff --git a/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp b/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp index 679111c..e98b05a 100644 --- a/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp +++ b/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp @@ -115,12 +115,14 @@ void UFFStateMachineComponent::GoToState(UFFState* NewState) } } + CurrentState->Exit(GetCurrentStateContext()); - CurrentState = NewState; TicksInState = 0; TickStateWasEntered = GetWorld()->GetGameState()->GetCurrentTick(); CurrentSubStateLabel = NAME_None; - CurrentState->Enter(GetCurrentStateContext()); + NewState->Enter(GetCurrentStateContext()); + OnStateTransition.Broadcast(CurrentState, NewState); + CurrentState = NewState; } void UFFStateMachineComponent::GoToEntryState() @@ -251,6 +253,11 @@ void UFFStateMachineComponent::TickComponent(float DeltaTime, ELevelTick TickTyp SMDebugString.Append(FString::Printf(TEXT("Current State: %s\n"), *CurrentState->Name.ToString())); SMDebugString.Append(FString::Printf(TEXT("Current SubState Label: %s\n"), *CurrentSubStateLabel.ToString())); SMDebugString.Append(FString::Printf(TEXT("Ticks In State: %lld\n"), TicksInState)); + SMDebugString.Append(FString::Printf(TEXT("-- States added --\n"))); + for (auto TempState : States) + { + SMDebugString.Append(FString::Printf(TEXT("%s\n"), *TempState->Name.ToString())); + } GEngine->AddOnScreenDebugMessage(-1, 0.0f, FColor::Green, SMDebugString); } diff --git a/Source/UnrealFightingFramework/State/FFStateMachineComponent.h b/Source/UnrealFightingFramework/State/FFStateMachineComponent.h index 52b9c54..d6aae42 100644 --- a/Source/UnrealFightingFramework/State/FFStateMachineComponent.h +++ b/Source/UnrealFightingFramework/State/FFStateMachineComponent.h @@ -12,6 +12,9 @@ #include "FFStateMachineComponent.generated.h" +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnStateTransitionSignature, + const UFFState*, PrevState, const UFFState*, NextState); + /** * A state machine is a component that evaluates and controls the transitions for state objects that * are a part of this state machine. @@ -48,6 +51,9 @@ public: * * @return A pointer to the state that was added or nullptr if there was an issue adding or creating the state */ + // TODO: return should probably be a const pointer if this function is public. I don't want + // anything outside of the state machine modifying the state. Really, I want states in general to + // be read only objects that are only modified at edit time when creating state blueprints. UFFState* AddState(TSubclassOf StateClassToAdd); /** @@ -92,12 +98,18 @@ public: UFUNCTION(BlueprintPure) FORCEINLINE int64 GetTickStateWasEntered() const { return TickStateWasEntered; } + UFUNCTION(BlueprintPure) + const UFFState* GetCurrentState() const { return const_cast(CurrentState); } + /** * Returns the name of the current state */ UFUNCTION(BlueprintPure) FORCEINLINE FName GetCurrentStateName() const; + /** + * Returns the name of the current sub state label + */ UFUNCTION(BlueprintPure) FORCEINLINE FName GetCurrentSubStateLabel() const { return CurrentSubStateLabel; } @@ -115,6 +127,8 @@ public: virtual void MovementModeChanged(EMovementMode PrevMovementMode, uint8 PreviousCustomMode, EMovementMode NewMovementMode, uint8 NewCustomMode); + FOnStateTransitionSignature OnStateTransition; + // IFFSystemInterface interface virtual void FixedTick(float OneFrame) override; // End of IFFSystemInterface interface From d9ec367b82559aa9677f3bbe94c486a1ef09e674 Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Sat, 28 Oct 2023 14:09:02 -0400 Subject: [PATCH 37/38] More control over playing a montage --- .../UnrealFightingFramework/State/FFState.cpp | 29 +++++++++++++------ .../UnrealFightingFramework/State/FFState.h | 10 +++++++ 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/Source/UnrealFightingFramework/State/FFState.cpp b/Source/UnrealFightingFramework/State/FFState.cpp index 962480f..3afcf08 100644 --- a/Source/UnrealFightingFramework/State/FFState.cpp +++ b/Source/UnrealFightingFramework/State/FFState.cpp @@ -63,15 +63,8 @@ bool UFFState::CanTransition(const FFFStateContext& InStateContext) void UFFState::Enter(const FFFStateContext& InStateContext) { - if(InStateContext.Avatar) - { - USkeletalMeshComponent* SMC = InStateContext.Avatar->GetComponentByClass(); - if(SMC && MontageToPlay) - { - SMC->GetAnimInstance()->Montage_Play(MontageToPlay); - } - } - + PlayMontage(InStateContext); + OnEnter(InStateContext); } @@ -180,6 +173,11 @@ void UFFState::RegisterInputHandler(const FFFInputSequence& InRequiredSequence, InputHandlers.Add(TempHandler); } +void UFFState::PlayMontage(const FFFStateContext& InStateContext) +{ + OnPlayMontage(InStateContext); +} + void UFFState::OnInit_Implementation(const FFFStateContext& InStateContext) { @@ -192,6 +190,19 @@ bool UFFState::OnCanTransition_Implementation(const FFFStateContext& InStateCont } +void UFFState::OnPlayMontage_Implementation(const FFFStateContext& InStateContext) +{ + if(InStateContext.Avatar) + { + USkeletalMeshComponent* SMC = InStateContext.Avatar->GetComponentByClass(); + if(SMC && MontageToPlay) + { + SMC->GetAnimInstance()->Montage_Play(MontageToPlay); + } + } +} + + UWorld* UFFState::GetWorld() const { UFFStateMachineComponent* SMC = Cast(GetOuter()); diff --git a/Source/UnrealFightingFramework/State/FFState.h b/Source/UnrealFightingFramework/State/FFState.h index 6fe99ce..693d4ae 100644 --- a/Source/UnrealFightingFramework/State/FFState.h +++ b/Source/UnrealFightingFramework/State/FFState.h @@ -228,6 +228,16 @@ public: UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events") void OnUpdate(float OneFrame, const FFFStateContext& InStateContext); + UFUNCTION(BlueprintCallable, Category="UFF|State|Animations") + void PlayMontage(const FFFStateContext& InStateContext); + + /** + * Blueprint hook for overriding the logic for when a anim montage plays at the start of a state + * @param InStateContext + */ + UFUNCTION(BlueprintNativeEvent, Category="UFF|State|Events") + void OnPlayMontage(const FFFStateContext& InStateContext); + // TODO: document UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events") void OnLanded(const FHitResult& Hit, const FFFStateContext& InStateContext); From d53fba996a99d2b918e06a7fdf342fe206e0f901 Mon Sep 17 00:00:00 2001 From: Kevin Poretti Date: Mon, 30 Oct 2023 18:51:56 -0400 Subject: [PATCH 38/38] Options for when to stop an anim montage when the state ends --- .../UnrealFightingFramework/State/FFState.cpp | 51 ++++++++++++++----- .../UnrealFightingFramework/State/FFState.h | 26 +++++++++- .../State/FFStateMachineComponent.cpp | 15 +++--- .../State/FFStateMachineComponent.h | 12 +++-- 4 files changed, 78 insertions(+), 26 deletions(-) diff --git a/Source/UnrealFightingFramework/State/FFState.cpp b/Source/UnrealFightingFramework/State/FFState.cpp index 3afcf08..2693143 100644 --- a/Source/UnrealFightingFramework/State/FFState.cpp +++ b/Source/UnrealFightingFramework/State/FFState.cpp @@ -69,16 +69,15 @@ void UFFState::Enter(const FFFStateContext& InStateContext) } -void UFFState::Exit(const FFFStateContext& InStateContext) +void UFFState::Exit(const FFFStateContext& InStateContext, EFFStateFinishReason StateFinishReason) { - if(InStateContext.Avatar) + + + if(InStateContext.Avatar && + (bStopMontageOnStateEnd && StateFinishReason == EFFStateFinishReason::SFT_DurationMetOrExceeded || StateFinishReason == EFFStateFinishReason::SFT_Interrupted) || + (bStopMontageOnMovementModeChange && StateFinishReason == EFFStateFinishReason::SFT_NotInReqMovementMode)) { - USkeletalMeshComponent* SMC = InStateContext.Avatar->GetComponentByClass(); - if(SMC && MontageToPlay) - { - FAlphaBlendArgs BlendOutArgs = MontageToPlay->GetBlendOutArgs(); - SMC->GetAnimInstance()->Montage_Stop(BlendOutArgs.BlendTime, MontageToPlay); - } + StopCurrentAnimMontage(InStateContext); } OnExit(InStateContext); @@ -115,7 +114,7 @@ void UFFState::Update(float OneFrame, const FFFStateContext& InStateContext) if(bStateHasDuration && InStateContext.Parent->GetTicksInState() >= StateDuration) { - Finish(InStateContext); + Finish(InStateContext, EFFStateFinishReason::SFT_DurationMetOrExceeded); } } @@ -142,7 +141,7 @@ void UFFState::MovementModeChanged(EMovementMode PrevMovementMode, uint8 Previou if((ReqMovementMode != EMovementMode::MOVE_None && NewMovementMode != ReqMovementMode) || ((ReqMovementMode == MOVE_Custom) && NewCustomMode != RequiredCustomMode)) { - Finish(InStateContext); + Finish(InStateContext, EFFStateFinishReason::SFT_NotInReqMovementMode); } } @@ -159,9 +158,15 @@ void UFFState::Block(const FFFStateContext& InStateContext) } -void UFFState::Finish(const FFFStateContext& InStateContext) +void UFFState::Finish(const FFFStateContext& InStateContext, EFFStateFinishReason StateFinishReason) { - InStateContext.Parent->GoToEntryState(); + // TODO: I really don't like having to pass this state finish reason into this GoToEntryState + // function, which then passes it to GoToState, which then passes it back to the state through + // it's "Exit" function all so we can stop the current anim montage when we exit from a state if + // the appropriate flags are set. I think having this state finish reason is good but I may want + // to rethink the way we handle logic for ending a state and which class is in charge of handling + // what + InStateContext.Parent->GoToEntryState(StateFinishReason); } @@ -175,6 +180,7 @@ void UFFState::RegisterInputHandler(const FFFInputSequence& InRequiredSequence, void UFFState::PlayMontage(const FFFStateContext& InStateContext) { + // TODO: think of a better way to handle optionally playing montages other than the one set as MontageToPlay OnPlayMontage(InStateContext); } @@ -189,7 +195,7 @@ bool UFFState::OnCanTransition_Implementation(const FFFStateContext& InStateCont return true; } - +// TODO: think of a better way to handle optionally playing montages other than the one set as MontageToPlay void UFFState::OnPlayMontage_Implementation(const FFFStateContext& InStateContext) { if(InStateContext.Avatar) @@ -213,3 +219,22 @@ UWorld* UFFState::GetWorld() const return nullptr; } + + +void UFFState::StopCurrentAnimMontage(const FFFStateContext& InStateContext) +{ + USkeletalMeshComponent* SMC = InStateContext.Avatar->GetComponentByClass(); + if(SMC) + { + // TODO: Need a system for keeping track what anim montages were played during a state + // so we can stop only those when the state ends (and if the bStopMontageOnStateEnd flag is set) + // ALSO, this will be important for when we need to fast forward an animation when the game rollsback + // and we have to resimulate to the current local tick + // For now just stop all anim montages + UAnimMontage* CurrAnim = SMC->GetAnimInstance()->GetCurrentActiveMontage(); + if(CurrAnim) + { + SMC->GetAnimInstance()->Montage_Stop(CurrAnim->BlendOut.GetBlendTime()); + } + } +} diff --git a/Source/UnrealFightingFramework/State/FFState.h b/Source/UnrealFightingFramework/State/FFState.h index 693d4ae..8a80d93 100644 --- a/Source/UnrealFightingFramework/State/FFState.h +++ b/Source/UnrealFightingFramework/State/FFState.h @@ -49,6 +49,17 @@ struct FFFInputEventHandler FFFInputEventDelegate Delegate; }; +UENUM(BlueprintType) +enum class EFFStateFinishReason : uint8 +{ + // TODO: document + SFT_Interrupted UMETA(DisplayName="Interrupted"), + SFT_DurationMetOrExceeded UMETA(DisplayName="Duration Reached"), + SFT_NotInReqMovementMode UMETA(DisplayName="Not In Required Movement Mode"), + SFT_MAX UMETA(Hidden) +}; + + /** * 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. @@ -120,6 +131,12 @@ public: UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties") UAnimMontage* MontageToPlay; + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties") + bool bStopMontageOnStateEnd; + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties") + bool bStopMontageOnMovementModeChange; + /** * Event delegates to call when a certain input condition is detected in the Owner's input buffer */ @@ -150,8 +167,10 @@ public: * Called whenever this state is transitioned out of into a new state. * * Calls appropriate Blueprint hooks. + * + * TODO: document StateFinishReason */ - virtual void Exit(const FFFStateContext& InStateContext); + virtual void Exit(const FFFStateContext& InStateContext, EFFStateFinishReason StateFinishReason); /** * Called whenever this state is active and the game logic ticks. @@ -191,7 +210,7 @@ public: * @param InStateContext */ UFUNCTION(BlueprintCallable) - virtual void Finish(const FFFStateContext& InStateContext); + virtual void Finish(const FFFStateContext& InStateContext, EFFStateFinishReason StateFinishReason); // TODO: document UFUNCTION(BlueprintCallable) @@ -260,4 +279,7 @@ public: // UObject interface virtual UWorld* GetWorld() const override; // End of UObject interface + +private: + void StopCurrentAnimMontage(const FFFStateContext& InStateContext); }; diff --git a/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp b/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp index e98b05a..120c8cf 100644 --- a/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp +++ b/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp @@ -89,16 +89,16 @@ void UFFStateMachineComponent::RemoveState(FName StateToRemove) UE_LOG(LogTemp, Error, TEXT("UFFStateMachineComponent::RemoveState is not yet implemented")); } -void UFFStateMachineComponent::GoToState(FName NewStateName) +void UFFStateMachineComponent::GoToState(FName NewStateName, EFFStateFinishReason StateFinishReason) { UFFState* NewState = FindStateWithName(NewStateName); if(NewState) { - GoToState(NewState); + GoToState(NewState, StateFinishReason); } } -void UFFStateMachineComponent::GoToState(UFFState* NewState) +void UFFStateMachineComponent::GoToState(UFFState* NewState, EFFStateFinishReason StateFinishReason) { check(CurrentState); check(NewState); @@ -115,8 +115,7 @@ void UFFStateMachineComponent::GoToState(UFFState* NewState) } } - - CurrentState->Exit(GetCurrentStateContext()); + CurrentState->Exit(GetCurrentStateContext(), StateFinishReason); TicksInState = 0; TickStateWasEntered = GetWorld()->GetGameState()->GetCurrentTick(); CurrentSubStateLabel = NAME_None; @@ -125,12 +124,12 @@ void UFFStateMachineComponent::GoToState(UFFState* NewState) CurrentState = NewState; } -void UFFStateMachineComponent::GoToEntryState() +void UFFStateMachineComponent::GoToEntryState(EFFStateFinishReason StateFinishReason) { // can't have an entry state if there are no states check(States.Num() > 0); - GoToState(States[0]); + GoToState(States[0], StateFinishReason); } @@ -213,7 +212,7 @@ void UFFStateMachineComponent::FixedTick(float OneFrame) // It is OK to transition if state's "CanTransitionToSelf" is true if(StateToTransitionTo) { - GoToState(StateToTransitionTo); + GoToState(StateToTransitionTo, EFFStateFinishReason::SFT_Interrupted); } else { diff --git a/Source/UnrealFightingFramework/State/FFStateMachineComponent.h b/Source/UnrealFightingFramework/State/FFStateMachineComponent.h index d6aae42..2c0c84d 100644 --- a/Source/UnrealFightingFramework/State/FFStateMachineComponent.h +++ b/Source/UnrealFightingFramework/State/FFStateMachineComponent.h @@ -72,20 +72,26 @@ public: * Transitions from CurrentState to the new state with the name passed to this function * * Triggers the Exit callback on the CurrentState and the Enter callback on the new state + * + * TODO: document StateFinishReason */ - void GoToState(FName NewStateName); + void GoToState(FName NewStateName, EFFStateFinishReason StateFinishReason); /** * Transitions from CurrentState to the new state passed to this function * * Triggers the Exit callback on the CurrentState and the Enter callback on the new state + * + * TODO: document StateFinishReason */ - void GoToState(UFFState* NewState); + void GoToState(UFFState* NewState, EFFStateFinishReason StateFinishReason); /** * Transitions from CurrentState to the default entry state + * + * TODO: document StateFinishReason */ - void GoToEntryState(); + void GoToEntryState(EFFStateFinishReason StateFinishReason); UFUNCTION(BlueprintPure) FORCEINLINE int64 GetTicksInState() const { return TicksInState; }