Compare commits
40 Commits
bf2c911b4d
...
df3c340779
Author | SHA1 | Date | |
---|---|---|---|
df3c340779 | |||
d53fba996a | |||
d9ec367b82 | |||
28963c78e9 | |||
755ee34343 | |||
c43aca536a | |||
b3cee07ada | |||
09c817ef49 | |||
c654bfd8fb | |||
6c06611901 | |||
bd195c7ee8 | |||
93ca460c8a | |||
08d52a913f | |||
d89face881 | |||
87c2aafd49 | |||
e032ddaaa5 | |||
8420f654e8 | |||
3938ac54b5 | |||
c652716895 | |||
afc34be554 | |||
d7245957df | |||
4cf3834b2d | |||
85f0cbf804 | |||
984fc0ad6a | |||
b8fa341503 | |||
9f16901de6 | |||
a85a9a7005 | |||
eb735db17a | |||
2c5569b6d5 | |||
ec6daa06fb | |||
f31f0437e9 | |||
e38379586c | |||
891cb89228 | |||
0265ce246c | |||
29e84cec80 | |||
ecd8b1ca3c | |||
3090798d20 | |||
a42e4c6667 | |||
d869d0105f | |||
fef1217877 |
BIN
Content/BPML_StateMacros.uasset
Normal file
BIN
Content/BPML_StateMacros.uasset
Normal file
Binary file not shown.
@ -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.
|
This library provides actors, components, and other general data structures that are useful for developing character action or fighting games.
|
||||||
|
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
||||||
|
|
||||||
#include "UnrealFightingEngineBPLibrary.h"
|
|
||||||
#include "UnrealFightingEngine.h"
|
|
||||||
|
|
||||||
UUnrealFightingEngineBPLibrary::UUnrealFightingEngineBPLibrary(const FObjectInitializer& ObjectInitializer)
|
|
||||||
: Super(ObjectInitializer)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
float UUnrealFightingEngineBPLibrary::UnrealFightingEngineSampleFunction(float Param)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
@ -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++;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
};
|
35
Source/UnrealFightingFramework/IFFSystemInterface.h
Normal file
35
Source/UnrealFightingFramework/IFFSystemInterface.h
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// Unreal Fighting Framework by Kevin Poretti
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// UE includes
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "UObject/Interface.h"
|
||||||
|
|
||||||
|
#include "IFFSystemInterface.generated.h"
|
||||||
|
|
||||||
|
// This class does not need to be modified.
|
||||||
|
UINTERFACE(MinimalAPI)
|
||||||
|
class UFFSystemInterface : 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 UNREALFIGHTINGFRAMEWORK_API IFFSystemInterface
|
||||||
|
{
|
||||||
|
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) = 0;
|
||||||
|
};
|
@ -0,0 +1,86 @@
|
|||||||
|
// Unreal Fighting Framework by Kevin Poretti
|
||||||
|
|
||||||
|
#include "FFInputBufferComponent.h"
|
||||||
|
|
||||||
|
UFFInputBufferComponent::UFFInputBufferComponent()
|
||||||
|
{
|
||||||
|
PrimaryComponentTick.bCanEverTick = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void UFFInputBufferComponent::AddInput(const FFFInputState& InputState)
|
||||||
|
{
|
||||||
|
InputBuffer.ForcePush(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--)
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
// TODO: should it be (PrevInput & RequiredButtons) == RequiredButtons or what we have now?
|
||||||
|
case EFFButtonState::BTNS_Pressed:
|
||||||
|
if(!(PrevInput & RequiredButtons | PrevDisable) &&
|
||||||
|
CurrInput & RequiredButtons & ~CurrDisable)
|
||||||
|
{
|
||||||
|
CondIdx--;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EFFButtonState::BTNS_Released:
|
||||||
|
if(PrevInput & RequiredButtons & ~PrevDisable &&
|
||||||
|
!(CurrInput & RequiredButtons | CurrDisable))
|
||||||
|
{
|
||||||
|
CondIdx--;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EFFButtonState::BTNS_Down:
|
||||||
|
if(CurrInput & RequiredButtons & ~CurrDisable)
|
||||||
|
{
|
||||||
|
CondIdx--;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
// TODO: implement button held condition
|
||||||
|
/*
|
||||||
|
case EFFButtonState::BTNS_Held:
|
||||||
|
break;
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
// All conditions were met
|
||||||
|
if(CondIdx == -1)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ElapsedFrames++;
|
||||||
|
if(ElapsedFrames > InputSequence.MaxDuration)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
102
Source/UnrealFightingFramework/Input/FFInputBufferComponent.h
Normal file
102
Source/UnrealFightingFramework/Input/FFInputBufferComponent.h
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
// Unreal Fighting Framework by Kevin Poretti
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// FF includes
|
||||||
|
#include "Utils/TCircleBuffer.h"
|
||||||
|
|
||||||
|
// UE includes
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Components/ActorComponent.h"
|
||||||
|
|
||||||
|
#include "FFInputBufferComponent.generated.h"
|
||||||
|
|
||||||
|
UENUM(BlueprintType)
|
||||||
|
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)
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Struct representing the state of a player's inputs for one frame
|
||||||
|
*/
|
||||||
|
USTRUCT()
|
||||||
|
struct FFFInputState
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
FVector2D MoveAxes;
|
||||||
|
FVector2D LookAxes;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, Meta = (Bitmask))
|
||||||
|
int32 Buttons;
|
||||||
|
int32 DisabledButtons;
|
||||||
|
|
||||||
|
FFFInputState()
|
||||||
|
: MoveAxes(FVector2D::ZeroVector)
|
||||||
|
, LookAxes(FVector2D::ZeroVector)
|
||||||
|
, Buttons(0)
|
||||||
|
, DisabledButtons(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct FFFInputCondition
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
// Buttons required for this specific condition to be valid
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||||
|
int32 RequiredButtons;
|
||||||
|
|
||||||
|
// The button state required for condition to be valid i.e. pressed or released
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||||
|
EFFButtonState RequiredButtonState;
|
||||||
|
|
||||||
|
FFFInputCondition()
|
||||||
|
: RequiredButtons(0)
|
||||||
|
, RequiredButtonState(EFFButtonState::BTNS_Pressed)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct FFFInputSequence
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||||
|
TArray<FFFInputCondition> Sequence;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||||
|
int32 MaxDuration;
|
||||||
|
|
||||||
|
FFFInputSequence()
|
||||||
|
: MaxDuration(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
UCLASS( ClassGroup=(UnrealFightingFramework), meta=(BlueprintSpawnableComponent) )
|
||||||
|
class UNREALFIGHTINGFRAMEWORK_API UFFInputBufferComponent : public UActorComponent
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UFFInputBufferComponent();
|
||||||
|
|
||||||
|
void AddInput(const FFFInputState& InputState);
|
||||||
|
|
||||||
|
bool CheckInputSequence(const FFFInputSequence& InputSequence);
|
||||||
|
|
||||||
|
void DisableMostRecentInput();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/** The underlying buffer data structure for holding past input states */
|
||||||
|
TCircleBuffer<FFFInputState, 120> InputBuffer;
|
||||||
|
};
|
79
Source/UnrealFightingFramework/Input/FFPlayerController.cpp
Normal file
79
Source/UnrealFightingFramework/Input/FFPlayerController.cpp
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
// Unreal Fighting Framework by Kevin Poretti
|
||||||
|
|
||||||
|
#include "FFPlayerController.h"
|
||||||
|
|
||||||
|
// FF includes
|
||||||
|
#include "FFInputBufferComponent.h"
|
||||||
|
|
||||||
|
// UE includes
|
||||||
|
#include "EnhancedInputSubsystems.h"
|
||||||
|
|
||||||
|
AFFPlayerController::AFFPlayerController()
|
||||||
|
{
|
||||||
|
InputBuffer = CreateDefaultSubobject<UFFInputBufferComponent>(TEXT("InputBuffer"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void AFFPlayerController::SendInputsToRemote() const
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void AFFPlayerController::ModifyRawInput()
|
||||||
|
{
|
||||||
|
ModifiedInput = RawInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void AFFPlayerController::FixedTick(float OneFrame)
|
||||||
|
{
|
||||||
|
//UnacknowledgedInputs.Push(RawInput);
|
||||||
|
InputBuffer->AddInput(ModifiedInput);
|
||||||
|
//SendInputsToRemote();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AFFPlayerController::CheckInputSequence(const FFFInputSequence& InInputSequence)
|
||||||
|
{
|
||||||
|
return InputBuffer->CheckInputSequence(InInputSequence);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool AFFPlayerController::CheckInputSequences(const TArray<FFFInputSequence>& InputSequences)
|
||||||
|
{
|
||||||
|
// no input conditions to check
|
||||||
|
if(InputSequences.IsEmpty())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const FFFInputSequence& ThisInputSequence : InputSequences)
|
||||||
|
{
|
||||||
|
if(InputBuffer->CheckInputSequence(ThisInputSequence))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AFFPlayerController::DisableMostRecentInput()
|
||||||
|
{
|
||||||
|
InputBuffer->DisableMostRecentInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void AFFPlayerController::SetupInputComponent()
|
||||||
|
{
|
||||||
|
Super::SetupInputComponent();
|
||||||
|
|
||||||
|
if (ULocalPlayer* LocalPlayer = Cast<ULocalPlayer>(Player))
|
||||||
|
{
|
||||||
|
if (UEnhancedInputLocalPlayerSubsystem* InputSystem = LocalPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>())
|
||||||
|
{
|
||||||
|
if (!DefaultInputMapping.IsNull())
|
||||||
|
{
|
||||||
|
InputSystem->AddMappingContext(DefaultInputMapping.LoadSynchronous(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
92
Source/UnrealFightingFramework/Input/FFPlayerController.h
Normal file
92
Source/UnrealFightingFramework/Input/FFPlayerController.h
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
// Unreal Fighting Framework by Kevin Poretti
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// FF includes
|
||||||
|
#include "FFInputBufferComponent.h"
|
||||||
|
#include "IFFSystemInterface.h"
|
||||||
|
#include "Utils/TCircleBuffer.h"
|
||||||
|
#include "State/IFFStateOwnerInterface.h"
|
||||||
|
|
||||||
|
// UE includes
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "GameFramework/PlayerController.h"
|
||||||
|
#include "InputMappingContext.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 UNREALFIGHTINGFRAMEWORK_API AFFPlayerController : public APlayerController, public IFFSystemInterface, public IFFStateOwnerInterface
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
AFFPlayerController();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends all unacknowledged inputs to the remote
|
||||||
|
*/
|
||||||
|
virtual void SendInputsToRemote() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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;
|
||||||
|
// End of IFFSystemInterface
|
||||||
|
|
||||||
|
// IFFStateOwnerInterface
|
||||||
|
UFUNCTION()
|
||||||
|
virtual bool CheckInputSequence(const FFFInputSequence& InInputSequence) override;
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
virtual bool CheckInputSequences(const TArray<FFFInputSequence>& InputSequences) override;
|
||||||
|
|
||||||
|
virtual void DisableMostRecentInput() override;
|
||||||
|
// End of IFFStateOwnerInterface
|
||||||
|
|
||||||
|
// APlayerController interface
|
||||||
|
virtual void SetupInputComponent() override;
|
||||||
|
// End of APlayerController interface
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/** MappingContext */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UFF|Input", meta = (AllowPrivateAccess = "true"))
|
||||||
|
TSoftObjectPtr<UInputMappingContext> DefaultInputMapping;
|
||||||
|
|
||||||
|
/** Input Buffer component */
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "UFF|Input", meta = (AllowPrivateAccess = "true"))
|
||||||
|
UFFInputBufferComponent* InputBuffer;
|
||||||
|
|
||||||
|
/** Current state of the player's inputs */
|
||||||
|
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
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
TCircleBuffer<FFFInputState, 20> UnacknowledgedInputs;
|
||||||
|
};
|
240
Source/UnrealFightingFramework/State/FFState.cpp
Normal file
240
Source/UnrealFightingFramework/State/FFState.cpp
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
// Unreal Fighting Framework by Kevin Poretti
|
||||||
|
|
||||||
|
#include "FFState.h"
|
||||||
|
|
||||||
|
// FF includes
|
||||||
|
#include "FFStateMachineComponent.h"
|
||||||
|
#include "IFFStateAvatarInterface.h"
|
||||||
|
#include "IFFStateOwnerInterface.h"
|
||||||
|
|
||||||
|
// UE includes
|
||||||
|
#include "Components/SkeletalMeshComponent.h"
|
||||||
|
|
||||||
|
void UFFState::Init(const FFFStateContext& InStateContext)
|
||||||
|
{
|
||||||
|
OnInit(InStateContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UFFState::CanTransition(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 to see if the owner implements the StateOwnerInterface
|
||||||
|
* Check input conditions if there are any
|
||||||
|
*
|
||||||
|
* If at least one input condition is good then we can transition
|
||||||
|
* so return true otherwise return false
|
||||||
|
*/
|
||||||
|
IFFStateAvatarInterface* SAI = Cast<IFFStateAvatarInterface>(InStateContext.Avatar);
|
||||||
|
if(SAI)
|
||||||
|
{
|
||||||
|
if(!(SAI->CheckStance(StanceRequired) && SAI->CheckStateEnabled(StateType)))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Error, TEXT("CanTransition :: Avatar of FFFStateContext does not implement IFFStateAvatarInterface"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
IFFStateOwnerInterface* SOI = Cast<IFFStateOwnerInterface>(InStateContext.Owner);
|
||||||
|
if(SOI)
|
||||||
|
{
|
||||||
|
if(!SOI->CheckInputSequences(InputSequences))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Error, TEXT("CanTransition :: Owner of FFFStateContext does not implement IFFStateOwnerInterface"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return OnCanTransition(InStateContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void UFFState::Enter(const FFFStateContext& InStateContext)
|
||||||
|
{
|
||||||
|
PlayMontage(InStateContext);
|
||||||
|
|
||||||
|
OnEnter(InStateContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void UFFState::Exit(const FFFStateContext& InStateContext, EFFStateFinishReason StateFinishReason)
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
if(InStateContext.Avatar &&
|
||||||
|
(bStopMontageOnStateEnd && StateFinishReason == EFFStateFinishReason::SFT_DurationMetOrExceeded || StateFinishReason == EFFStateFinishReason::SFT_Interrupted) ||
|
||||||
|
(bStopMontageOnMovementModeChange && StateFinishReason == EFFStateFinishReason::SFT_NotInReqMovementMode))
|
||||||
|
{
|
||||||
|
StopCurrentAnimMontage(InStateContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
OnExit(InStateContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void UFFState::Update(float OneFrame, const FFFStateContext& InStateContext)
|
||||||
|
{
|
||||||
|
IFFStateOwnerInterface* SOI = Cast<IFFStateOwnerInterface>(InStateContext.Owner);
|
||||||
|
// TODO: maybe a check/asset is better because I can't think of a reason that not having the Owner implement
|
||||||
|
// IFFStateOwnerInterface is valid. Although there could be objects that have state that don't necessarily directly
|
||||||
|
// respond to input and don't have an Owner/controller responsible for them. Or they do have an owner but are associated
|
||||||
|
// with another Avatar like a character who is owned by a player/player controller.
|
||||||
|
if(!SOI)
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Error, TEXT("Owner of FFFStateContext does not implement IFFStateOwnerInterface"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(FFFInputEventHandler InputHandler : InputHandlers)
|
||||||
|
{
|
||||||
|
if(SOI->CheckInputSequence(InputHandler.RequiredSequence))
|
||||||
|
{
|
||||||
|
if(InputHandler.Delegate.ExecuteIfBound(InStateContext))
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Error,
|
||||||
|
TEXT("Trying to execute an input handler delegate on %s but it is not bound"),
|
||||||
|
*Name.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OnUpdate(OneFrame, InStateContext);
|
||||||
|
|
||||||
|
if(bStateHasDuration && InStateContext.Parent->GetTicksInState() >= StateDuration)
|
||||||
|
{
|
||||||
|
Finish(InStateContext, EFFStateFinishReason::SFT_DurationMetOrExceeded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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::MovementModeChanged(EMovementMode PrevMovementMode, uint8 PreviousCustomMode,
|
||||||
|
EMovementMode NewMovementMode, uint8 NewCustomMode, const FFFStateContext& InStateContext)
|
||||||
|
{
|
||||||
|
OnMovementModeChanged(PrevMovementMode, PreviousCustomMode,
|
||||||
|
NewMovementMode, NewCustomMode, InStateContext);
|
||||||
|
|
||||||
|
// 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, EFFStateFinishReason::SFT_NotInReqMovementMode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void UFFState::Hit(const FFFStateContext& InStateContext)
|
||||||
|
{
|
||||||
|
OnHit(InStateContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void UFFState::Block(const FFFStateContext& InStateContext)
|
||||||
|
{
|
||||||
|
OnBlock(InStateContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void UFFState::Finish(const FFFStateContext& InStateContext, EFFStateFinishReason StateFinishReason)
|
||||||
|
{
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void UFFState::RegisterInputHandler(const FFFInputSequence& InRequiredSequence, FFFInputEventDelegate InDelegate)
|
||||||
|
{
|
||||||
|
FFFInputEventHandler TempHandler;
|
||||||
|
TempHandler.RequiredSequence = InRequiredSequence;
|
||||||
|
TempHandler.Delegate = InDelegate;
|
||||||
|
InputHandlers.Add(TempHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void UFFState::OnInit_Implementation(const FFFStateContext& InStateContext)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool UFFState::OnCanTransition_Implementation(const FFFStateContext& InStateContext)
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
USkeletalMeshComponent* SMC = InStateContext.Avatar->GetComponentByClass<USkeletalMeshComponent>();
|
||||||
|
if(SMC && MontageToPlay)
|
||||||
|
{
|
||||||
|
SMC->GetAnimInstance()->Montage_Play(MontageToPlay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
UWorld* UFFState::GetWorld() const
|
||||||
|
{
|
||||||
|
UFFStateMachineComponent* SMC = Cast<UFFStateMachineComponent>(GetOuter());
|
||||||
|
if(SMC)
|
||||||
|
{
|
||||||
|
return SMC->GetWorld();
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void UFFState::StopCurrentAnimMontage(const FFFStateContext& InStateContext)
|
||||||
|
{
|
||||||
|
USkeletalMeshComponent* SMC = InStateContext.Avatar->GetComponentByClass<USkeletalMeshComponent>();
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
285
Source/UnrealFightingFramework/State/FFState.h
Normal file
285
Source/UnrealFightingFramework/State/FFState.h
Normal file
@ -0,0 +1,285 @@
|
|||||||
|
// Unreal Fighting Framework by Kevin Poretti
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// FF includes
|
||||||
|
#include "Input/FFInputBufferComponent.h"
|
||||||
|
|
||||||
|
// UE includes
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
|
||||||
|
#include "FFState.generated.h"
|
||||||
|
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
UPROPERTY(BlueprintReadOnly)
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
UCLASS(Blueprintable)
|
||||||
|
class UNREALFIGHTINGFRAMEWORK_API UFFState : public UObject
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
// State parameters (should be read-only)
|
||||||
|
/** Name of this state behavior */
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, 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")
|
||||||
|
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")
|
||||||
|
int32 StateType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<FFFInputSequence> 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;
|
||||||
|
|
||||||
|
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties")
|
||||||
|
TEnumAsByte<EMovementMode> ReqMovementMode;
|
||||||
|
|
||||||
|
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties")
|
||||||
|
uint8 RequiredCustomMode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Animation to begin playing when this state is entered
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category="UFF State Events")
|
||||||
|
TArray<FFFInputEventHandler> InputHandlers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when state is first created.
|
||||||
|
*/
|
||||||
|
virtual void Init(const FFFStateContext& InStateContext);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
virtual bool CanTransition(const FFFStateContext& InStateContext);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called whenever this state is transitioned into.
|
||||||
|
*
|
||||||
|
* Calls appropriate Blueprint hooks.
|
||||||
|
*/
|
||||||
|
virtual void Enter(const FFFStateContext& InStateContext);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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, EFFStateFinishReason StateFinishReason);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called whenever this state is active and the game logic ticks.
|
||||||
|
*
|
||||||
|
* Calls appropriate Blueprint hooks.
|
||||||
|
*
|
||||||
|
* @param OneFrame the time that elapses during one fixed tick
|
||||||
|
*/
|
||||||
|
virtual void Update(float OneFrame, const FFFStateContext& InStateContext);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
*
|
||||||
|
* 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, EFFStateFinishReason StateFinishReason);
|
||||||
|
|
||||||
|
// TODO: document
|
||||||
|
UFUNCTION(BlueprintCallable)
|
||||||
|
virtual void RegisterInputHandler(
|
||||||
|
const FFFInputSequence& InRequiredSequence, FFFInputEventDelegate InDelegate);
|
||||||
|
|
||||||
|
// TODO: document
|
||||||
|
UFUNCTION(BlueprintNativeEvent, Category="UFF|State|Events")
|
||||||
|
void OnInit(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
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events")
|
||||||
|
void OnEnter(const FFFStateContext& InStateContext);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blueprint hook for whenever this state is transitioned out of into a new state
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events")
|
||||||
|
void OnExit(const FFFStateContext& InStateContext);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blueprint hook for whenever this state is active and the game logic ticks.
|
||||||
|
*
|
||||||
|
* @param OneFrame the time that elapses during one fixed tick
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 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")
|
||||||
|
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);
|
||||||
|
|
||||||
|
// UObject interface
|
||||||
|
virtual UWorld* GetWorld() const override;
|
||||||
|
// End of UObject interface
|
||||||
|
|
||||||
|
private:
|
||||||
|
void StopCurrentAnimMontage(const FFFStateContext& InStateContext);
|
||||||
|
};
|
28
Source/UnrealFightingFramework/State/FFStateData.h
Normal file
28
Source/UnrealFightingFramework/State/FFStateData.h
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// 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(BlueprintType)
|
||||||
|
class UNREALFIGHTINGFRAMEWORK_API UFFStateData : public UDataAsset
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
|
||||||
|
TSubclassOf<UFFState> EntryState;
|
||||||
|
|
||||||
|
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
|
||||||
|
TArray<TSubclassOf<UFFState>> OtherStates;
|
||||||
|
};
|
264
Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp
Normal file
264
Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
// Unreal Fighting Framework by Kevin Poretti
|
||||||
|
|
||||||
|
#include "FFStateMachineComponent.h"
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
TEXT("Print state machine information for character"),
|
||||||
|
ECVF_Cheat);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
UFFStateMachineComponent::UFFStateMachineComponent()
|
||||||
|
{
|
||||||
|
// Don't use Unreal's tick instead use a fixed tick
|
||||||
|
#if !UE_BUILD_SHIPPING
|
||||||
|
PrimaryComponentTick.bCanEverTick = true;
|
||||||
|
#else
|
||||||
|
PrimaryComponentTick.bCanEverTick = false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void UFFStateMachineComponent::Initialize(TSubclassOf<UFFState> EntryState, const TArray<TSubclassOf<UFFState>>& InitialStates)
|
||||||
|
{
|
||||||
|
UFFState* EntryStateInstance = AddState(EntryState);
|
||||||
|
|
||||||
|
for(const TSubclassOf<UFFState>& CurrState : InitialStates)
|
||||||
|
{
|
||||||
|
AddState(CurrState);
|
||||||
|
}
|
||||||
|
|
||||||
|
// need an entry state or something went seriously wrong
|
||||||
|
check(EntryStateInstance);
|
||||||
|
|
||||||
|
CurrentState = EntryStateInstance;
|
||||||
|
CurrentState->Enter(GetCurrentStateContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void UFFStateMachineComponent::InitActorInfo(AActor* InOwner, AActor* InAvatar)
|
||||||
|
{
|
||||||
|
Owner = InOwner;
|
||||||
|
Avatar = InAvatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
UFFState* UFFStateMachineComponent::AddState(TSubclassOf<UFFState> StateClassToAdd)
|
||||||
|
{
|
||||||
|
UFFState* TempState = NewObject<UFFState>(this, StateClassToAdd);
|
||||||
|
if(TempState)
|
||||||
|
{
|
||||||
|
if(!FindStateWithName(TempState->Name))
|
||||||
|
{
|
||||||
|
States.Add(TempState);
|
||||||
|
TempState->Init(GetCurrentStateContext());
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void UFFStateMachineComponent::AddStates(const TArray<TSubclassOf<UFFState>>& StateClassesToAdd)
|
||||||
|
{
|
||||||
|
for(const TSubclassOf<UFFState>& CurrState : StateClassesToAdd)
|
||||||
|
{
|
||||||
|
AddState(CurrState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void UFFStateMachineComponent::RemoveState(FName StateToRemove)
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Error, TEXT("UFFStateMachineComponent::RemoveState is not yet implemented"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void UFFStateMachineComponent::GoToState(FName NewStateName, EFFStateFinishReason StateFinishReason)
|
||||||
|
{
|
||||||
|
UFFState* NewState = FindStateWithName(NewStateName);
|
||||||
|
if(NewState)
|
||||||
|
{
|
||||||
|
GoToState(NewState, StateFinishReason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UFFStateMachineComponent::GoToState(UFFState* NewState, EFFStateFinishReason StateFinishReason)
|
||||||
|
{
|
||||||
|
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<IFFStateOwnerInterface>(Owner);
|
||||||
|
if(PC)
|
||||||
|
{
|
||||||
|
PC->DisableMostRecentInput();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CurrentState->Exit(GetCurrentStateContext(), StateFinishReason);
|
||||||
|
TicksInState = 0;
|
||||||
|
TickStateWasEntered = GetWorld()->GetGameState<AFFGameState>()->GetCurrentTick();
|
||||||
|
CurrentSubStateLabel = NAME_None;
|
||||||
|
NewState->Enter(GetCurrentStateContext());
|
||||||
|
OnStateTransition.Broadcast(CurrentState, NewState);
|
||||||
|
CurrentState = NewState;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UFFStateMachineComponent::GoToEntryState(EFFStateFinishReason StateFinishReason)
|
||||||
|
{
|
||||||
|
// can't have an entry state if there are no states
|
||||||
|
check(States.Num() > 0);
|
||||||
|
|
||||||
|
GoToState(States[0], StateFinishReason);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
FName UFFStateMachineComponent::GetCurrentStateName() const
|
||||||
|
{
|
||||||
|
check(CurrentState)
|
||||||
|
|
||||||
|
return CurrentState->Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void UFFStateMachineComponent::SetSubStateLabel(FName InSubStateLabel)
|
||||||
|
{
|
||||||
|
CurrentSubStateLabel = InSubStateLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
FFFStateContext UFFStateMachineComponent::GetCurrentStateContext()
|
||||||
|
{
|
||||||
|
FFFStateContext CurrStateContext;
|
||||||
|
CurrStateContext.Owner = Owner;
|
||||||
|
CurrStateContext.Avatar = Avatar;
|
||||||
|
CurrStateContext.Parent = this;
|
||||||
|
return CurrStateContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void UFFStateMachineComponent::Landed(const FHitResult& Hit)
|
||||||
|
{
|
||||||
|
check(CurrentState)
|
||||||
|
|
||||||
|
CurrentState->Landed(Hit, GetCurrentStateContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
// TODO: Should probably assert or whatever UE's equivalent is
|
||||||
|
//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;
|
||||||
|
for(UFFState* ThisState : States)
|
||||||
|
{
|
||||||
|
// found a state
|
||||||
|
if(ThisState->CanTransition(GetCurrentStateContext()) &&
|
||||||
|
(CurrentState->Name != ThisState->Name || ThisState->bCanTransitionToSelf))
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
GoToState(StateToTransitionTo, EFFStateFinishReason::SFT_Interrupted);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Tick current state
|
||||||
|
TicksInState++;
|
||||||
|
CurrentState->Update(OneFrame, GetCurrentStateContext());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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"), *StateName.ToString());
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !UE_BUILD_SHIPPING
|
||||||
|
void UFFStateMachineComponent::TickComponent(float DeltaTime, ELevelTick TickType,
|
||||||
|
FActorComponentTickFunction* ThisTickFunction)
|
||||||
|
{
|
||||||
|
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
|
||||||
|
|
||||||
|
// Debug
|
||||||
|
if(StateMachineDebug && CurrentState)
|
||||||
|
{
|
||||||
|
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));
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
188
Source/UnrealFightingFramework/State/FFStateMachineComponent.h
Normal file
188
Source/UnrealFightingFramework/State/FFStateMachineComponent.h
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
// Unreal Fighting Framework by Kevin Poretti
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// FF includes
|
||||||
|
#include "IFFSystemInterface.h"
|
||||||
|
#include "FFState.h"
|
||||||
|
|
||||||
|
// UE includes
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Components/ActorComponent.h"
|
||||||
|
|
||||||
|
#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.
|
||||||
|
*
|
||||||
|
* This component also calls the appropriate state logic when a state is changed or the component ticks.
|
||||||
|
*/
|
||||||
|
UCLASS( ClassGroup=(UnrealFightingFramework), meta=(BlueprintSpawnableComponent) )
|
||||||
|
class UNREALFIGHTINGFRAMEWORK_API UFFStateMachineComponent : public UActorComponent, public IFFSystemInterface
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UFFStateMachineComponent();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and adds default states and enters the first state
|
||||||
|
*/
|
||||||
|
void Initialize(TSubclassOf<UFFState> EntryState, const TArray<TSubclassOf<UFFState>>& InitialStates);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
// 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<UFFState> 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<TSubclassOf<UFFState>>& 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 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, 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, EFFStateFinishReason StateFinishReason);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transitions from CurrentState to the default entry state
|
||||||
|
*
|
||||||
|
* TODO: document StateFinishReason
|
||||||
|
*/
|
||||||
|
void GoToEntryState(EFFStateFinishReason StateFinishReason);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintPure)
|
||||||
|
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; }
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintPure)
|
||||||
|
const UFFState* GetCurrentState() const { return const_cast<UFFState*>(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; }
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable)
|
||||||
|
void SetSubStateLabel(FName InSubStateLabel);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
FFFStateContext GetCurrentStateContext();
|
||||||
|
|
||||||
|
// Events
|
||||||
|
virtual void Landed(const FHitResult& Hit);
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* Actor that owns this state machine.
|
||||||
|
* This will typically be a player controller that possesses the avatar.
|
||||||
|
*/
|
||||||
|
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()
|
||||||
|
AActor* Avatar;
|
||||||
|
|
||||||
|
/** 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;
|
||||||
|
|
||||||
|
/** Current SubState label or NAME_None if there is no SubState label set for the current state*/
|
||||||
|
FName CurrentSubStateLabel;
|
||||||
|
|
||||||
|
// States that have been added
|
||||||
|
UPROPERTY()
|
||||||
|
TArray<UFFState*> States;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the state with corresponding name
|
||||||
|
*/
|
||||||
|
UFFState* FindStateWithName(FName StateName);
|
||||||
|
|
||||||
|
#if !UE_BUILD_SHIPPING
|
||||||
|
// UActorComponent interface
|
||||||
|
virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
|
||||||
|
// End of UActorComponent interface
|
||||||
|
#endif
|
||||||
|
};
|
@ -0,0 +1,27 @@
|
|||||||
|
// Unreal Fighting Framework by Kevin Poretti
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "UObject/Interface.h"
|
||||||
|
|
||||||
|
#include "IFFStateAvatarInterface.generated.h"
|
||||||
|
|
||||||
|
UINTERFACE(MinimalAPI)
|
||||||
|
class UFFStateAvatarInterface : public UInterface
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class UNREALFIGHTINGFRAMEWORK_API IFFStateAvatarInterface
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual bool CheckStance(int32 Stance) = 0;
|
||||||
|
|
||||||
|
virtual bool CheckStateEnabled(int32 StateType) = 0;
|
||||||
|
};
|
@ -0,0 +1,31 @@
|
|||||||
|
// Unreal Fighting Framework by Kevin Poretti
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "UObject/Interface.h"
|
||||||
|
|
||||||
|
#include "IFFStateOwnerInterface.generated.h"
|
||||||
|
|
||||||
|
UINTERFACE(MinimalAPI, NotBlueprintable)
|
||||||
|
class UFFStateOwnerInterface : public UInterface
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
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<FFFInputSequence>& InInputSequences) = 0;
|
||||||
|
|
||||||
|
virtual void DisableMostRecentInput() = 0;
|
||||||
|
};
|
@ -1,23 +1,22 @@
|
|||||||
// Some copyright should be here...
|
// Unreal Fighting Framework by Kevin Poretti
|
||||||
|
using System.IO;
|
||||||
using UnrealBuildTool;
|
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;
|
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||||
|
|
||||||
PublicIncludePaths.AddRange(
|
PublicIncludePaths.AddRange(
|
||||||
new string[] {
|
new string[] {
|
||||||
// ... add public include paths required here ...
|
Path.Combine(PluginDirectory,"Source/UnrealFightingFramework"),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
PrivateIncludePaths.AddRange(
|
PrivateIncludePaths.AddRange(
|
||||||
new string[] {
|
new string[] {
|
||||||
// ... add other private include paths required here ...
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -26,6 +25,7 @@ public class UnrealFightingEngine : ModuleRules
|
|||||||
new string[]
|
new string[]
|
||||||
{
|
{
|
||||||
"Core",
|
"Core",
|
||||||
|
"EnhancedInput",
|
||||||
// ... add other public dependencies that you statically link with here ...
|
// ... add other public dependencies that you statically link with here ...
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -36,8 +36,6 @@ public class UnrealFightingEngine : ModuleRules
|
|||||||
{
|
{
|
||||||
"CoreUObject",
|
"CoreUObject",
|
||||||
"Engine",
|
"Engine",
|
||||||
"Slate",
|
|
||||||
"SlateCore",
|
|
||||||
// ... add private dependencies that you statically link with here ...
|
// ... add private dependencies that you statically link with here ...
|
||||||
}
|
}
|
||||||
);
|
);
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||||
#include "UnrealFightingEngineBPLibrary.generated.h"
|
#include "UnrealFightingFrameworkBPLibrary.generated.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Function library class.
|
* Function library class.
|
||||||
@ -23,10 +23,10 @@
|
|||||||
* https://wiki.unrealengine.com/Custom_Blueprint_Node_Creation
|
* https://wiki.unrealengine.com/Custom_Blueprint_Node_Creation
|
||||||
*/
|
*/
|
||||||
UCLASS()
|
UCLASS()
|
||||||
class UUnrealFightingEngineBPLibrary : public UBlueprintFunctionLibrary
|
class UUnrealFightingFrameworkBPLibrary : public UBlueprintFunctionLibrary
|
||||||
{
|
{
|
||||||
GENERATED_UCLASS_BODY()
|
GENERATED_UCLASS_BODY()
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Execute Sample function", Keywords = "UnrealFightingEngine sample test testing"), Category = "UnrealFightingEngineTesting")
|
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Execute Sample function", Keywords = "UnrealFightingFramework sample test testing"), Category = "UnrealFightingFrameworkTesting")
|
||||||
static float UnrealFightingEngineSampleFunction(float Param);
|
static float UnrealFightingFrameworkSampleFunction(float Param);
|
||||||
};
|
};
|
@ -1,16 +1,16 @@
|
|||||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
#include "UnrealFightingEngine.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
|
// 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,
|
// 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.
|
// we call this function before unloading the module.
|
||||||
@ -19,4 +19,4 @@ void FUnrealFightingEngineModule::ShutdownModule()
|
|||||||
|
|
||||||
#undef LOCTEXT_NAMESPACE
|
#undef LOCTEXT_NAMESPACE
|
||||||
|
|
||||||
IMPLEMENT_MODULE(FUnrealFightingEngineModule, UnrealFightingEngine)
|
IMPLEMENT_MODULE(FUnrealFightingFrameworkModule, UnrealFightingFramework)
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
#include "Modules/ModuleManager.h"
|
#include "Modules/ModuleManager.h"
|
||||||
|
|
||||||
class FUnrealFightingEngineModule : public IModuleInterface
|
class FUnrealFightingFrameworkModule : public IModuleInterface
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
151
Source/UnrealFightingFramework/Utils/TCircleBuffer.h
Normal file
151
Source/UnrealFightingFramework/Utils/TCircleBuffer.h
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
// Unreal Fighting Framework by Kevin Poretti
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#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<typename T, SIZE_T L = 64>
|
||||||
|
class UNREALFIGHTINGFRAMEWORK_API TCircleBuffer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TCircleBuffer()
|
||||||
|
: WriteIdx(0)
|
||||||
|
, ReadIdx(0)
|
||||||
|
, _Num(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[(ReadIdx + Index) % L];
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
@ -2,7 +2,7 @@
|
|||||||
"FileVersion": 3,
|
"FileVersion": 3,
|
||||||
"Version": 1,
|
"Version": 1,
|
||||||
"VersionName": "1.0",
|
"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.",
|
"Description": "This library provides actors, components, and other general data structures that are useful for developing character action or fighting games.",
|
||||||
"Category": "Other",
|
"Category": "Other",
|
||||||
"CreatedBy": "Kevin Poretti",
|
"CreatedBy": "Kevin Poretti",
|
||||||
@ -16,9 +16,15 @@
|
|||||||
"Installed": false,
|
"Installed": false,
|
||||||
"Modules": [
|
"Modules": [
|
||||||
{
|
{
|
||||||
"Name": "UnrealFightingEngine",
|
"Name": "UnrealFightingFramework",
|
||||||
"Type": "Runtime",
|
"Type": "Runtime",
|
||||||
"LoadingPhase": "PreLoadingScreen"
|
"LoadingPhase": "PreLoadingScreen"
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"Plugins": [
|
||||||
|
{
|
||||||
|
"Name": "EnhancedInput",
|
||||||
|
"Enabled": true
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user