Merge branch 'release/0.1.0'

This commit is contained in:
Kevin Poretti 2023-11-01 18:38:12 -04:00
commit df3c340779
24 changed files with 1715 additions and 37 deletions

Binary file not shown.

View File

@ -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.

View File

@ -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;
}

View File

@ -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++;
}
}

View File

@ -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;
};

View 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;
};

View File

@ -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;
}

View 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;
};

View 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);
}
}
}
}

View 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;
};

View 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());
}
}
}

View 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);
};

View 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;
};

View 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

View 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
};

View File

@ -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;
};

View File

@ -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;
};

View File

@ -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 ...
}
);

View File

@ -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;
}

View File

@ -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);
};

View File

@ -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)

View File

@ -4,7 +4,7 @@
#include "Modules/ModuleManager.h"
class FUnrealFightingEngineModule : public IModuleInterface
class FUnrealFightingFrameworkModule : public IModuleInterface
{
public:

View 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;
};

View File

@ -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
}
]
}