Merge branch 'release/0.1.0'
This commit is contained in:
commit
df3c340779
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.
|
||||
|
||||
|
@ -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;
|
||||
|
||||
public class UnrealFightingEngine : ModuleRules
|
||||
public class UnrealFightingFramework : ModuleRules
|
||||
{
|
||||
public UnrealFightingEngine(ReadOnlyTargetRules Target) : base(Target)
|
||||
public UnrealFightingFramework(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
PublicIncludePaths.AddRange(
|
||||
new string[] {
|
||||
// ... add public include paths required here ...
|
||||
Path.Combine(PluginDirectory,"Source/UnrealFightingFramework"),
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
PrivateIncludePaths.AddRange(
|
||||
new string[] {
|
||||
// ... add other private include paths required here ...
|
||||
}
|
||||
);
|
||||
|
||||
@ -26,6 +25,7 @@ public class UnrealFightingEngine : ModuleRules
|
||||
new string[]
|
||||
{
|
||||
"Core",
|
||||
"EnhancedInput",
|
||||
// ... add other public dependencies that you statically link with here ...
|
||||
}
|
||||
);
|
||||
@ -36,8 +36,6 @@ public class UnrealFightingEngine : ModuleRules
|
||||
{
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"Slate",
|
||||
"SlateCore",
|
||||
// ... 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
|
||||
|
||||
#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);
|
||||
};
|
@ -1,16 +1,16 @@
|
||||
// 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
|
||||
|
||||
}
|
||||
|
||||
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)
|
||||
IMPLEMENT_MODULE(FUnrealFightingFrameworkModule, UnrealFightingFramework)
|
@ -4,7 +4,7 @@
|
||||
|
||||
#include "Modules/ModuleManager.h"
|
||||
|
||||
class FUnrealFightingEngineModule : public IModuleInterface
|
||||
class FUnrealFightingFrameworkModule : public IModuleInterface
|
||||
{
|
||||
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,
|
||||
"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,9 +16,15 @@
|
||||
"Installed": false,
|
||||
"Modules": [
|
||||
{
|
||||
"Name": "UnrealFightingEngine",
|
||||
"Name": "UnrealFightingFramework",
|
||||
"Type": "Runtime",
|
||||
"LoadingPhase": "PreLoadingScreen"
|
||||
}
|
||||
],
|
||||
"Plugins": [
|
||||
{
|
||||
"Name": "EnhancedInput",
|
||||
"Enabled": true
|
||||
}
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue
Block a user