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. 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; using UnrealBuildTool;
public class UnrealFightingEngine : ModuleRules public class UnrealFightingFramework : ModuleRules
{ {
public UnrealFightingEngine(ReadOnlyTargetRules Target) : base(Target) public UnrealFightingFramework(ReadOnlyTargetRules Target) : base(Target)
{ {
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PublicIncludePaths.AddRange( PublicIncludePaths.AddRange(
new string[] { new string[] {
// ... add public include paths required here ... Path.Combine(PluginDirectory,"Source/UnrealFightingFramework"),
} }
); );
PrivateIncludePaths.AddRange( PrivateIncludePaths.AddRange(
new string[] { new string[] {
// ... add other private include paths required here ...
} }
); );
@ -26,6 +25,7 @@ public class UnrealFightingEngine : ModuleRules
new string[] new string[]
{ {
"Core", "Core",
"EnhancedInput",
// ... add other public dependencies that you statically link with here ... // ... add other public dependencies that you statically link with here ...
} }
); );
@ -36,8 +36,6 @@ public class UnrealFightingEngine : ModuleRules
{ {
"CoreUObject", "CoreUObject",
"Engine", "Engine",
"Slate",
"SlateCore",
// ... add private dependencies that you statically link with here ... // ... add private dependencies that you statically link with here ...
} }
); );

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 #pragma once
#include "Kismet/BlueprintFunctionLibrary.h" #include "Kismet/BlueprintFunctionLibrary.h"
#include "UnrealFightingEngineBPLibrary.generated.h" #include "UnrealFightingFrameworkBPLibrary.generated.h"
/* /*
* Function library class. * Function library class.
@ -23,10 +23,10 @@
* https://wiki.unrealengine.com/Custom_Blueprint_Node_Creation * https://wiki.unrealengine.com/Custom_Blueprint_Node_Creation
*/ */
UCLASS() UCLASS()
class UUnrealFightingEngineBPLibrary : public UBlueprintFunctionLibrary class UUnrealFightingFrameworkBPLibrary : public UBlueprintFunctionLibrary
{ {
GENERATED_UCLASS_BODY() GENERATED_UCLASS_BODY()
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Execute Sample function", Keywords = "UnrealFightingEngine sample test testing"), Category = "UnrealFightingEngineTesting") UFUNCTION(BlueprintCallable, meta = (DisplayName = "Execute Sample function", Keywords = "UnrealFightingFramework sample test testing"), Category = "UnrealFightingFrameworkTesting")
static float UnrealFightingEngineSampleFunction(float Param); static float UnrealFightingFrameworkSampleFunction(float Param);
}; };

View File

@ -1,16 +1,16 @@
// Copyright Epic Games, Inc. All Rights Reserved. // Copyright Epic Games, Inc. All Rights Reserved.
#include "UnrealFightingEngine.h" #include "UnrealFightingFrameworkModule.h"
#define LOCTEXT_NAMESPACE "FUnrealFightingEngineModule" #define LOCTEXT_NAMESPACE "FUnrealFightingFrameworkModule"
void FUnrealFightingEngineModule::StartupModule() void FUnrealFightingFrameworkModule::StartupModule()
{ {
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
} }
void FUnrealFightingEngineModule::ShutdownModule() void FUnrealFightingFrameworkModule::ShutdownModule()
{ {
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
// we call this function before unloading the module. // we call this function before unloading the module.
@ -19,4 +19,4 @@ void FUnrealFightingEngineModule::ShutdownModule()
#undef LOCTEXT_NAMESPACE #undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FUnrealFightingEngineModule, UnrealFightingEngine) IMPLEMENT_MODULE(FUnrealFightingFrameworkModule, UnrealFightingFramework)

View File

@ -4,7 +4,7 @@
#include "Modules/ModuleManager.h" #include "Modules/ModuleManager.h"
class FUnrealFightingEngineModule : public IModuleInterface class FUnrealFightingFrameworkModule : public IModuleInterface
{ {
public: 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, "FileVersion": 3,
"Version": 1, "Version": 1,
"VersionName": "1.0", "VersionName": "1.0",
"FriendlyName": "UnrealFightingEngine", "FriendlyName": "UnrealFightingFramework",
"Description": "This library provides actors, components, and other general data structures that are useful for developing character action or fighting games.", "Description": "This library provides actors, components, and other general data structures that are useful for developing character action or fighting games.",
"Category": "Other", "Category": "Other",
"CreatedBy": "Kevin Poretti", "CreatedBy": "Kevin Poretti",
@ -16,9 +16,15 @@
"Installed": false, "Installed": false,
"Modules": [ "Modules": [
{ {
"Name": "UnrealFightingEngine", "Name": "UnrealFightingFramework",
"Type": "Runtime", "Type": "Runtime",
"LoadingPhase": "PreLoadingScreen" "LoadingPhase": "PreLoadingScreen"
} }
],
"Plugins": [
{
"Name": "EnhancedInput",
"Enabled": true
}
] ]
} }