Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
df3c340779 |
Binary file not shown.
@ -4,12 +4,8 @@ This library provides actors, components, and other general data structures that
|
|||||||
|
|
||||||
### To-Do
|
### To-Do
|
||||||
|
|
||||||
- [X] State Machine
|
- [ ] State Machine
|
||||||
- [X] Input Buffer
|
- [ ] Input Buffer
|
||||||
- [ ] Unit tests
|
|
||||||
- [ ] Generic "Actor" class for any game affecting actors that need to run at a fixed tick & rollback
|
|
||||||
- [ ] Interfaces/virtual functions for rolling back and resimulating objects
|
|
||||||
- [ ] Methods for queueing animations/VFX/SFX when re-simulating
|
|
||||||
|
|
||||||
### Setup
|
### Setup
|
||||||
|
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
// Project Sword & Gun Copyright Kevin Poretti
|
|
||||||
|
|
||||||
|
|
||||||
#include "GameplayFramework/FFAIController.h"
|
|
||||||
|
|
||||||
AFFAIController::AFFAIController()
|
|
||||||
{
|
|
||||||
InputBuffer = CreateDefaultSubobject<UFFInputBufferComponent>(TEXT("InputBuffer"));
|
|
||||||
bWantsPlayerState = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AFFAIController::CheckInputSequence(const FFFInputSequence& InInputSequence)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AFFAIController::CheckInputSequences(const TArray<FFFInputSequence>& InInputSequences)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
FVector AFFAIController::GetMoveInputAsWorldDirection() const
|
|
||||||
{
|
|
||||||
return FVector::ZeroVector;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AFFAIController::DisableMostRecentInput()
|
|
||||||
{
|
|
||||||
check(InputBuffer);
|
|
||||||
InputBuffer->DisableMostRecentInput();
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
// Project Sword & Gun Copyright Kevin Poretti
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
// FF includes
|
|
||||||
#include "State/IFFStateOwnerInterface.h"
|
|
||||||
#include "Input/FFInputBufferComponent.h"
|
|
||||||
|
|
||||||
// UE includes
|
|
||||||
#include "CoreMinimal.h"
|
|
||||||
#include "AIController.h"
|
|
||||||
|
|
||||||
#include "FFAIController.generated.h"
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
UCLASS()
|
|
||||||
class UNREALFIGHTINGFRAMEWORK_API AFFAIController : public AAIController, public IFFStateOwnerInterface
|
|
||||||
{
|
|
||||||
GENERATED_BODY()
|
|
||||||
|
|
||||||
public:
|
|
||||||
AFFAIController();
|
|
||||||
|
|
||||||
// IFFStateOwnerInterface
|
|
||||||
UFUNCTION(BlueprintCallable, Category="UFF State Owner Interface")
|
|
||||||
virtual bool CheckInputSequence(const FFFInputSequence& InInputSequence) override;
|
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category="UFF State Owner Interface")
|
|
||||||
virtual bool CheckInputSequences(const TArray<FFFInputSequence>& InInputSequences) override;
|
|
||||||
|
|
||||||
virtual FVector GetMoveInputAsWorldDirection() const override;
|
|
||||||
|
|
||||||
virtual void DisableMostRecentInput() override;
|
|
||||||
// End of IFFStateOwnerInterface
|
|
||||||
|
|
||||||
protected:
|
|
||||||
/** Input Buffer component */
|
|
||||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "UFF|Input", meta = (AllowPrivateAccess = "true"))
|
|
||||||
UFFInputBufferComponent* InputBuffer;
|
|
||||||
};
|
|
@ -1,13 +1,7 @@
|
|||||||
// Unreal Fighting Framework by Kevin Poretti
|
// Project Sword & Gun Copyright Kevin Poretti
|
||||||
|
|
||||||
#include "GameplayFramework/FFGameState.h"
|
#include "GameplayFramework/FFGameState.h"
|
||||||
|
|
||||||
// FF includes
|
|
||||||
#include "Input/FFPlayerController.h"
|
|
||||||
|
|
||||||
// UE includes
|
|
||||||
#include "Kismet/GameplayStatics.h"
|
|
||||||
|
|
||||||
void AFFGameState::OnFixedTick(float OneTick)
|
void AFFGameState::OnFixedTick(float OneTick)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -17,50 +11,15 @@ void AFFGameState::Tick(float DeltaSeconds)
|
|||||||
{
|
{
|
||||||
Super::Tick(DeltaSeconds);
|
Super::Tick(DeltaSeconds);
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO: I want to reinvestigate how we're collecting and applying inputs. I seem to keep getting
|
|
||||||
* weird timing issues due to how Unreal collects and informs the game of inputs through function
|
|
||||||
* callbacks rather than some mechanism where we can poll the state of various actions/axes when
|
|
||||||
* it comes time to update. The investigation should be two-fold:
|
|
||||||
*
|
|
||||||
* 1 - is there any alternative ways of using the EnhancedInputComponent but polling for inputs
|
|
||||||
* rather than this whole callback system, so when it is time to tick the game state we can just
|
|
||||||
* do something like a bool bIsJumpPressed = GetActionValue("Jump") for all inputs and axes
|
|
||||||
* we need to collect?
|
|
||||||
*
|
|
||||||
* 2 - if there isn't any polling mechanism in the EnhancedInputComponent, how can we work around
|
|
||||||
* that and what are some of the things we have to keep in mind? What happens when the Unreal tick
|
|
||||||
* rate is slower than the game tick rate? Do we just apply the current input to multiple game ticks?
|
|
||||||
* What about when Unreal's tick rate is faster than the game? Would that make the game feel
|
|
||||||
* unresponsive i.e. feel like inputs are getting dropped?
|
|
||||||
*
|
|
||||||
* 3 - would like to know more about EnhancedInputComponent and inputs in Unreal in general as well.
|
|
||||||
* Are inputs collected in a separate thread? How does unreal determine when to call any input callbacks
|
|
||||||
* you've registered with the input component?
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Get inputs
|
|
||||||
AFFPlayerController* PC = Cast<AFFPlayerController>(UGameplayStatics::GetPlayerController(GetWorld(), 0));
|
|
||||||
if(!PC)
|
|
||||||
{
|
|
||||||
UE_LOG(LogTemp, Warning, TEXT("Could not get a local player controller so no inputs could be collected"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
PC->ModifyRawInput();
|
|
||||||
|
|
||||||
// Run anything game logic related at a fixed tick rate
|
// 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
|
// TODO: Interpolate between prev and current game state if there is time left in accumulator
|
||||||
AccumulatedTime += DeltaSeconds;
|
AccumulatedTime += DeltaSeconds;
|
||||||
while(AccumulatedTime > ONE_TICK)
|
while(AccumulatedTime > ONE_TICK)
|
||||||
{
|
{
|
||||||
PC->AddCurrentInputToBuffer();
|
|
||||||
OnFixedTick(ONE_TICK);
|
OnFixedTick(ONE_TICK);
|
||||||
|
|
||||||
AccumulatedTime -= ONE_TICK;
|
AccumulatedTime -= ONE_TICK;
|
||||||
|
|
||||||
CurrentTick++;
|
CurrentTick++;
|
||||||
}
|
}
|
||||||
|
|
||||||
PC->ConsumeMoveInput();
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Unreal Fighting Framework by Kevin Poretti
|
// Project Sword & Gun Copyright Kevin Poretti
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
@ -18,37 +18,29 @@ bool UFFInputBufferComponent::CheckInputSequence(const FFFInputSequence& InputSe
|
|||||||
{
|
{
|
||||||
if(InputSequence.Sequence.IsEmpty())
|
if(InputSequence.Sequence.IsEmpty())
|
||||||
{
|
{
|
||||||
UE_LOG(LogTemp, Warning,
|
UE_LOG(LogTemp, Error,
|
||||||
TEXT("FFInputBufferComponent :: CheckInputSequence - tried to check input sequence but it was empty"));
|
TEXT("FFInputBufferComponent :: CheckInputSequence - tried to check input sequence but it was empty"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int CondIdx = InputSequence.Sequence.Num() - 1;
|
int CondIdx = InputSequence.Sequence.Num() - 1;
|
||||||
int ElapsedFrames = 0;
|
int ElapsedFrames = 0;
|
||||||
for(int InpIdx = InputBuffer.Num() - 1; InpIdx > 0; InpIdx--)
|
for(int InpIdx = InputBuffer.Num() - 1; InpIdx > 1; InpIdx--)
|
||||||
{
|
{
|
||||||
int32 RequiredButtons = InputSequence.Sequence[CondIdx].RequiredButtons;
|
int32 RequiredButtons = InputSequence.Sequence[CondIdx].RequiredButtons;
|
||||||
EFFButtonState RequiredButtonState = InputSequence.Sequence[CondIdx].RequiredButtonState;
|
EFFButtonState RequiredButtonState = InputSequence.Sequence[CondIdx].RequiredButtonState;
|
||||||
int32 TimeoutDuration = InputSequence.Sequence[CondIdx].TimeoutDuration;
|
|
||||||
int32 PrevInput = InputBuffer[InpIdx - 1].Buttons;
|
int32 PrevInput = InputBuffer[InpIdx - 1].Buttons;
|
||||||
int32 CurrInput = InputBuffer[InpIdx].Buttons;
|
int32 CurrInput = InputBuffer[InpIdx].Buttons;
|
||||||
int32 PrevDisable = InputBuffer[InpIdx - 1].DisabledButtons;
|
int32 PrevDisable = InputBuffer[InpIdx - 1].DisabledButtons;
|
||||||
int32 CurrDisable = InputBuffer[InpIdx].DisabledButtons;
|
int32 CurrDisable = InputBuffer[InpIdx].DisabledButtons;
|
||||||
bool bCondFoundThisIteration = false;
|
|
||||||
switch (RequiredButtonState)
|
switch (RequiredButtonState)
|
||||||
{
|
{
|
||||||
// TODO: should it be (PrevInput & RequiredButtons) == RequiredButtons or what we have now?
|
// TODO: should it be (PrevInput & RequiredButtons) == RequiredButtons or what we have now?
|
||||||
// TODO: If you have a sequence that is defined as something like button 1 pressed + button 2 pressed
|
|
||||||
// and the buttons are pressed on the same exact frame, then this implementation will not
|
|
||||||
// account for that. We should keep checking the sequences against the current frame's
|
|
||||||
// inputs until no sequences are detected, then continue on checking through the input buffer.
|
|
||||||
case EFFButtonState::BTNS_Pressed:
|
case EFFButtonState::BTNS_Pressed:
|
||||||
if(!(PrevInput & RequiredButtons | PrevDisable) &&
|
if(!(PrevInput & RequiredButtons | PrevDisable) &&
|
||||||
CurrInput & RequiredButtons & ~CurrDisable)
|
CurrInput & RequiredButtons & ~CurrDisable)
|
||||||
{
|
{
|
||||||
CondIdx--;
|
CondIdx--;
|
||||||
ElapsedFrames = 0;
|
|
||||||
bCondFoundThisIteration = true;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case EFFButtonState::BTNS_Released:
|
case EFFButtonState::BTNS_Released:
|
||||||
@ -56,16 +48,12 @@ bool UFFInputBufferComponent::CheckInputSequence(const FFFInputSequence& InputSe
|
|||||||
!(CurrInput & RequiredButtons | CurrDisable))
|
!(CurrInput & RequiredButtons | CurrDisable))
|
||||||
{
|
{
|
||||||
CondIdx--;
|
CondIdx--;
|
||||||
ElapsedFrames = 0;
|
|
||||||
bCondFoundThisIteration = true;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case EFFButtonState::BTNS_Down:
|
case EFFButtonState::BTNS_Down:
|
||||||
if(CurrInput & RequiredButtons & ~CurrDisable)
|
if(CurrInput & RequiredButtons & ~CurrDisable)
|
||||||
{
|
{
|
||||||
CondIdx--;
|
CondIdx--;
|
||||||
ElapsedFrames = 0;
|
|
||||||
bCondFoundThisIteration = true;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
// TODO: implement button held condition
|
// TODO: implement button held condition
|
||||||
@ -81,32 +69,18 @@ bool UFFInputBufferComponent::CheckInputSequence(const FFFInputSequence& InputSe
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// No condition found this iteration, so increment elapse frames and see if current condition timed out
|
|
||||||
if(!bCondFoundThisIteration)
|
|
||||||
{
|
|
||||||
ElapsedFrames++;
|
ElapsedFrames++;
|
||||||
if(ElapsedFrames > TimeoutDuration)
|
if(ElapsedFrames > InputSequence.MaxDuration)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UFFInputBufferComponent::DisableMostRecentInput()
|
void UFFInputBufferComponent::DisableMostRecentInput()
|
||||||
{
|
{
|
||||||
if(InputBuffer.Num() < 2)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
InputBuffer[InputBuffer.Num() - 1].DisabledButtons |= InputBuffer[InputBuffer.Num() - 1].Buttons;
|
InputBuffer[InputBuffer.Num() - 1].DisabledButtons |= InputBuffer[InputBuffer.Num() - 1].Buttons;
|
||||||
InputBuffer[InputBuffer.Num() - 2].DisabledButtons |= InputBuffer[InputBuffer.Num() - 2].Buttons;
|
InputBuffer[InputBuffer.Num() - 2].DisabledButtons |= InputBuffer[InputBuffer.Num() - 2].Buttons;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UFFInputBufferComponent::FlushBuffer()
|
|
||||||
{
|
|
||||||
InputBuffer.Flush();
|
|
||||||
}
|
|
||||||
|
@ -17,7 +17,6 @@ enum class EFFButtonState : uint8
|
|||||||
BTNS_Pressed UMETA(DisplayName="Pressed"),
|
BTNS_Pressed UMETA(DisplayName="Pressed"),
|
||||||
BTNS_Released UMETA(DisplayName="Released"),
|
BTNS_Released UMETA(DisplayName="Released"),
|
||||||
BTNS_Down UMETA(DisplayName="Down"),
|
BTNS_Down UMETA(DisplayName="Down"),
|
||||||
//BTNS_Up UMETA(DisplayName="Up),
|
|
||||||
//BTNS_Held UMETA(DisplayName="Held"),
|
//BTNS_Held UMETA(DisplayName="Held"),
|
||||||
BTNS_MAX UMETA(Hidden)
|
BTNS_MAX UMETA(Hidden)
|
||||||
};
|
};
|
||||||
@ -31,7 +30,7 @@ struct FFFInputState
|
|||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
FVector2D MoveAxes;
|
FVector2D MoveAxes;
|
||||||
FRotator LookRot;
|
FVector2D LookAxes;
|
||||||
|
|
||||||
UPROPERTY(EditAnywhere, Meta = (Bitmask))
|
UPROPERTY(EditAnywhere, Meta = (Bitmask))
|
||||||
int32 Buttons;
|
int32 Buttons;
|
||||||
@ -39,7 +38,7 @@ struct FFFInputState
|
|||||||
|
|
||||||
FFFInputState()
|
FFFInputState()
|
||||||
: MoveAxes(FVector2D::ZeroVector)
|
: MoveAxes(FVector2D::ZeroVector)
|
||||||
, LookRot(FRotator::ZeroRotator)
|
, LookAxes(FVector2D::ZeroVector)
|
||||||
, Buttons(0)
|
, Buttons(0)
|
||||||
, DisabledButtons(0)
|
, DisabledButtons(0)
|
||||||
{
|
{
|
||||||
@ -51,25 +50,17 @@ struct FFFInputCondition
|
|||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
/** Buttons required for this specific condition to be valid */
|
// Buttons required for this specific condition to be valid
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||||
int32 RequiredButtons;
|
int32 RequiredButtons;
|
||||||
|
|
||||||
/** The button state required for condition to be valid i.e. pressed or released */
|
// The button state required for condition to be valid i.e. pressed or released
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||||
EFFButtonState RequiredButtonState;
|
EFFButtonState RequiredButtonState;
|
||||||
|
|
||||||
/*
|
|
||||||
* How many ticks that can be checked since the last valid input condition was found before
|
|
||||||
* this condition is considered not valid/found in the input buffer
|
|
||||||
*/
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
|
||||||
int32 TimeoutDuration;
|
|
||||||
|
|
||||||
FFFInputCondition()
|
FFFInputCondition()
|
||||||
: RequiredButtons(0)
|
: RequiredButtons(0)
|
||||||
, RequiredButtonState(EFFButtonState::BTNS_Pressed)
|
, RequiredButtonState(EFFButtonState::BTNS_Pressed)
|
||||||
, TimeoutDuration(0)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -82,7 +73,11 @@ struct FFFInputSequence
|
|||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||||
TArray<FFFInputCondition> Sequence;
|
TArray<FFFInputCondition> Sequence;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||||
|
int32 MaxDuration;
|
||||||
|
|
||||||
FFFInputSequence()
|
FFFInputSequence()
|
||||||
|
: MaxDuration(0)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -101,8 +96,6 @@ public:
|
|||||||
|
|
||||||
void DisableMostRecentInput();
|
void DisableMostRecentInput();
|
||||||
|
|
||||||
void FlushBuffer();
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/** The underlying buffer data structure for holding past input states */
|
/** The underlying buffer data structure for holding past input states */
|
||||||
TCircleBuffer<FFFInputState, 120> InputBuffer;
|
TCircleBuffer<FFFInputState, 120> InputBuffer;
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
// UE includes
|
// UE includes
|
||||||
#include "EnhancedInputSubsystems.h"
|
#include "EnhancedInputSubsystems.h"
|
||||||
#include "Kismet/KismetMathLibrary.h"
|
|
||||||
|
|
||||||
AFFPlayerController::AFFPlayerController()
|
AFFPlayerController::AFFPlayerController()
|
||||||
{
|
{
|
||||||
@ -26,37 +25,13 @@ void AFFPlayerController::ModifyRawInput()
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool AFFPlayerController::HasMoveInput()
|
void AFFPlayerController::FixedTick(float OneFrame)
|
||||||
{
|
|
||||||
// TODO: configurable deadzone
|
|
||||||
return !ModifiedInput.MoveAxes.IsNearlyZero();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void AFFPlayerController::ConsumeMoveInput()
|
|
||||||
{
|
|
||||||
RawInput.MoveAxes = FVector2D::ZeroVector;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
FVector AFFPlayerController::GetMoveInputAsWorldDirection() const
|
|
||||||
{
|
|
||||||
FRotator ViewRotationYaw = FRotator(0.0f, ModifiedInput.LookRot.Yaw, 0.0f);
|
|
||||||
FVector ForwardInput = UKismetMathLibrary::GetForwardVector(ViewRotationYaw) * ModifiedInput.MoveAxes.Y;
|
|
||||||
FVector RightInput = UKismetMathLibrary::GetRightVector(ViewRotationYaw) * ModifiedInput.MoveAxes.X;
|
|
||||||
|
|
||||||
return ForwardInput + RightInput;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void AFFPlayerController::AddCurrentInputToBuffer()
|
|
||||||
{
|
{
|
||||||
//UnacknowledgedInputs.Push(RawInput);
|
//UnacknowledgedInputs.Push(RawInput);
|
||||||
InputBuffer->AddInput(ModifiedInput);
|
InputBuffer->AddInput(ModifiedInput);
|
||||||
//SendInputsToRemote();
|
//SendInputsToRemote();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool AFFPlayerController::CheckInputSequence(const FFFInputSequence& InInputSequence)
|
bool AFFPlayerController::CheckInputSequence(const FFFInputSequence& InInputSequence)
|
||||||
{
|
{
|
||||||
return InputBuffer->CheckInputSequence(InInputSequence);
|
return InputBuffer->CheckInputSequence(InInputSequence);
|
||||||
@ -81,7 +56,6 @@ bool AFFPlayerController::CheckInputSequences(const TArray<FFFInputSequence>& In
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void AFFPlayerController::DisableMostRecentInput()
|
void AFFPlayerController::DisableMostRecentInput()
|
||||||
{
|
{
|
||||||
InputBuffer->DisableMostRecentInput();
|
InputBuffer->DisableMostRecentInput();
|
||||||
@ -103,12 +77,3 @@ void AFFPlayerController::SetupInputComponent()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void AFFPlayerController::FlushPressedKeys()
|
|
||||||
{
|
|
||||||
Super::FlushPressedKeys();
|
|
||||||
|
|
||||||
RawInput.Buttons = 0;
|
|
||||||
RawInput.MoveAxes = FVector2D::ZeroVector;
|
|
||||||
}
|
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
* unacknowledged inputs to a remote client or server for processing.
|
* unacknowledged inputs to a remote client or server for processing.
|
||||||
*/
|
*/
|
||||||
UCLASS()
|
UCLASS()
|
||||||
class UNREALFIGHTINGFRAMEWORK_API AFFPlayerController : public APlayerController, public IFFStateOwnerInterface
|
class UNREALFIGHTINGFRAMEWORK_API AFFPlayerController : public APlayerController, public IFFSystemInterface, public IFFStateOwnerInterface
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ public:
|
|||||||
* @brief Gets the current input after cleaning/modifying the raw input state
|
* @brief Gets the current input after cleaning/modifying the raw input state
|
||||||
* @return the current input
|
* @return the current input
|
||||||
*/
|
*/
|
||||||
virtual FFFInputState GetModifiedInput() const { return ModifiedInput; }
|
virtual FFFInputState GetModifiedInput() const { return ModifiedInput; };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Function called before inputs are passed to the game logic to update the game state.
|
* @brief Function called before inputs are passed to the game logic to update the game state.
|
||||||
@ -47,23 +47,9 @@ public:
|
|||||||
*/
|
*/
|
||||||
virtual void ModifyRawInput();
|
virtual void ModifyRawInput();
|
||||||
|
|
||||||
/**
|
// IFFSystemInterface interface
|
||||||
* @brief Checks if the current input state has movement input being applied
|
virtual void FixedTick(float OneFrame) override;
|
||||||
* @return True if move axes is non-zero, false otherwise
|
// End of IFFSystemInterface
|
||||||
*/
|
|
||||||
UFUNCTION(BlueprintPure)
|
|
||||||
bool HasMoveInput();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Zeroes out the raw movement axes after input has been processed
|
|
||||||
*/
|
|
||||||
void ConsumeMoveInput();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Adds the current state of the player input to the input buffer for processing and
|
|
||||||
* interpretation after input has been cleaned
|
|
||||||
*/
|
|
||||||
void AddCurrentInputToBuffer();
|
|
||||||
|
|
||||||
// IFFStateOwnerInterface
|
// IFFStateOwnerInterface
|
||||||
UFUNCTION()
|
UFUNCTION()
|
||||||
@ -72,15 +58,11 @@ public:
|
|||||||
UFUNCTION()
|
UFUNCTION()
|
||||||
virtual bool CheckInputSequences(const TArray<FFFInputSequence>& InputSequences) override;
|
virtual bool CheckInputSequences(const TArray<FFFInputSequence>& InputSequences) override;
|
||||||
|
|
||||||
virtual FVector GetMoveInputAsWorldDirection() const override;
|
|
||||||
|
|
||||||
virtual void DisableMostRecentInput() override;
|
virtual void DisableMostRecentInput() override;
|
||||||
// End of IFFStateOwnerInterface
|
// End of IFFStateOwnerInterface
|
||||||
|
|
||||||
// APlayerController interface
|
// APlayerController interface
|
||||||
virtual void SetupInputComponent() override;
|
virtual void SetupInputComponent() override;
|
||||||
|
|
||||||
virtual void FlushPressedKeys() override;
|
|
||||||
// End of APlayerController interface
|
// End of APlayerController interface
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -57,7 +57,7 @@ bool UFFState::CanTransition(const FFFStateContext& InStateContext)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return OnCanTransition(InStateContext) && !bIsFollowupState;
|
return OnCanTransition(InStateContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -65,21 +65,14 @@ void UFFState::Enter(const FFFStateContext& InStateContext)
|
|||||||
{
|
{
|
||||||
PlayMontage(InStateContext);
|
PlayMontage(InStateContext);
|
||||||
|
|
||||||
if(bDisableMostRecentInputOnEntry)
|
|
||||||
{
|
|
||||||
IFFStateOwnerInterface* PC = Cast<IFFStateOwnerInterface>(InStateContext.Owner);
|
|
||||||
if(PC)
|
|
||||||
{
|
|
||||||
PC->DisableMostRecentInput();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OnEnter(InStateContext);
|
OnEnter(InStateContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void UFFState::Exit(const FFFStateContext& InStateContext, EFFStateFinishReason StateFinishReason)
|
void UFFState::Exit(const FFFStateContext& InStateContext, EFFStateFinishReason StateFinishReason)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
if(InStateContext.Avatar &&
|
if(InStateContext.Avatar &&
|
||||||
(bStopMontageOnStateEnd && StateFinishReason == EFFStateFinishReason::SFT_DurationMetOrExceeded || StateFinishReason == EFFStateFinishReason::SFT_Interrupted) ||
|
(bStopMontageOnStateEnd && StateFinishReason == EFFStateFinishReason::SFT_DurationMetOrExceeded || StateFinishReason == EFFStateFinishReason::SFT_Interrupted) ||
|
||||||
(bStopMontageOnMovementModeChange && StateFinishReason == EFFStateFinishReason::SFT_NotInReqMovementMode))
|
(bStopMontageOnMovementModeChange && StateFinishReason == EFFStateFinishReason::SFT_NotInReqMovementMode))
|
||||||
@ -140,24 +133,28 @@ void UFFState::MovementModeChanged(EMovementMode PrevMovementMode, uint8 Previou
|
|||||||
{
|
{
|
||||||
OnMovementModeChanged(PrevMovementMode, PreviousCustomMode,
|
OnMovementModeChanged(PrevMovementMode, PreviousCustomMode,
|
||||||
NewMovementMode, NewCustomMode, InStateContext);
|
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::AttackHit(const FHitResult& HitResult, const FFFStateContext& InStateContext)
|
void UFFState::Hit(const FFFStateContext& InStateContext)
|
||||||
{
|
{
|
||||||
OnAttackHit(HitResult, InStateContext);
|
OnHit(InStateContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void UFFState::HitTaken(const FFFStateContext& InStateContext)
|
void UFFState::Block(const FFFStateContext& InStateContext)
|
||||||
{
|
{
|
||||||
OnHitTaken(InStateContext);
|
OnBlock(InStateContext);
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void UFFState::BlockTaken(const FFFStateContext& InStateContext)
|
|
||||||
{
|
|
||||||
OnBlockTaken(InStateContext);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -169,24 +166,18 @@ void UFFState::Finish(const FFFStateContext& InStateContext, EFFStateFinishReaso
|
|||||||
// the appropriate flags are set. I think having this state finish reason is good but I may want
|
// 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
|
// to rethink the way we handle logic for ending a state and which class is in charge of handling
|
||||||
// what
|
// what
|
||||||
|
|
||||||
if(GetFollowupState(InStateContext) != NAME_None)
|
|
||||||
{
|
|
||||||
InStateContext.Parent->GoToState(GetFollowupState(InStateContext), StateFinishReason);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
InStateContext.Parent->GoToEntryState(StateFinishReason);
|
InStateContext.Parent->GoToEntryState(StateFinishReason);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
FName UFFState::GetFollowupState_Implementation(const FFFStateContext& InStateContext)
|
void UFFState::RegisterInputHandler(const FFFInputSequence& InRequiredSequence, FFFInputEventDelegate InDelegate)
|
||||||
{
|
{
|
||||||
return FollowupState;
|
FFFInputEventHandler TempHandler;
|
||||||
|
TempHandler.RequiredSequence = InRequiredSequence;
|
||||||
|
TempHandler.Delegate = InDelegate;
|
||||||
|
InputHandlers.Add(TempHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void UFFState::PlayMontage(const FFFStateContext& InStateContext)
|
void UFFState::PlayMontage(const FFFStateContext& InStateContext)
|
||||||
{
|
{
|
||||||
// TODO: think of a better way to handle optionally playing montages other than the one set as MontageToPlay
|
// TODO: think of a better way to handle optionally playing montages other than the one set as MontageToPlay
|
||||||
|
@ -35,11 +35,6 @@ struct FFFStateContext
|
|||||||
|
|
||||||
DECLARE_DYNAMIC_DELEGATE_OneParam(FFFInputEventDelegate, const FFFStateContext&, InStateContext);
|
DECLARE_DYNAMIC_DELEGATE_OneParam(FFFInputEventDelegate, const FFFStateContext&, InStateContext);
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO: check if this is even used anymore and if not delete? I recall using this in a few
|
|
||||||
* states/actions but it was a pain to use and I switched to checking an input sequence directly in
|
|
||||||
* a state's update function
|
|
||||||
*/
|
|
||||||
USTRUCT(BlueprintType)
|
USTRUCT(BlueprintType)
|
||||||
struct FFFInputEventHandler
|
struct FFFInputEventHandler
|
||||||
{
|
{
|
||||||
@ -57,15 +52,10 @@ struct FFFInputEventHandler
|
|||||||
UENUM(BlueprintType)
|
UENUM(BlueprintType)
|
||||||
enum class EFFStateFinishReason : uint8
|
enum class EFFStateFinishReason : uint8
|
||||||
{
|
{
|
||||||
/** State ended due to another state's entry condition being satisfied */
|
// TODO: document
|
||||||
SFT_Interrupted UMETA(DisplayName="Interrupted"),
|
SFT_Interrupted UMETA(DisplayName="Interrupted"),
|
||||||
|
|
||||||
/** State ended because the number of ticks in this state exceeded state's duration property */
|
|
||||||
SFT_DurationMetOrExceeded UMETA(DisplayName="Duration Reached"),
|
SFT_DurationMetOrExceeded UMETA(DisplayName="Duration Reached"),
|
||||||
|
|
||||||
/** State ended because the owner is no longer in the movement mode required by the state */
|
|
||||||
SFT_NotInReqMovementMode UMETA(DisplayName="Not In Required Movement Mode"),
|
SFT_NotInReqMovementMode UMETA(DisplayName="Not In Required Movement Mode"),
|
||||||
|
|
||||||
SFT_MAX UMETA(Hidden)
|
SFT_MAX UMETA(Hidden)
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -122,13 +112,6 @@ public:
|
|||||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties")
|
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties")
|
||||||
TArray<FFFInputSequence> InputSequences;
|
TArray<FFFInputSequence> InputSequences;
|
||||||
|
|
||||||
/**
|
|
||||||
* Should the most recent input in the controlling player's input buffer be disabled when
|
|
||||||
* entering this state?
|
|
||||||
*/
|
|
||||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties")
|
|
||||||
bool bDisableMostRecentInputOnEntry = true;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If true the state can transition from itself into itself without having to go through
|
* If true the state can transition from itself into itself without having to go through
|
||||||
* another state like Idle
|
* another state like Idle
|
||||||
@ -136,23 +119,11 @@ public:
|
|||||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties")
|
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties")
|
||||||
bool bCanTransitionToSelf = false;
|
bool bCanTransitionToSelf = false;
|
||||||
|
|
||||||
/**
|
|
||||||
* Specifies whether a state is a followup state or not.
|
|
||||||
*
|
|
||||||
* Followup states are states that should only be transitioned into from another specific state
|
|
||||||
* but otherwise have no other specific conditions that need to be met to transition.
|
|
||||||
* Setting this flag to true when there are no other conditions prevents this state from
|
|
||||||
* constantly being transitioned into by the state machine.
|
|
||||||
*/
|
|
||||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties")
|
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties")
|
||||||
bool bIsFollowupState = false;
|
TEnumAsByte<EMovementMode> ReqMovementMode;
|
||||||
|
|
||||||
/**
|
|
||||||
* State to transition to when this state is finished. If left blank then the entry state
|
|
||||||
* of the state machine will be transitioned into.
|
|
||||||
*/
|
|
||||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties")
|
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="UFF State Properties")
|
||||||
FName FollowupState = NAME_None;
|
uint8 RequiredCustomMode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Animation to begin playing when this state is entered
|
* Animation to begin playing when this state is entered
|
||||||
@ -197,7 +168,7 @@ public:
|
|||||||
*
|
*
|
||||||
* Calls appropriate Blueprint hooks.
|
* Calls appropriate Blueprint hooks.
|
||||||
*
|
*
|
||||||
* @param StateFinishReason the reason the state is ending i.e. the state's duration elapse
|
* TODO: document StateFinishReason
|
||||||
*/
|
*/
|
||||||
virtual void Exit(const FFFStateContext& InStateContext, EFFStateFinishReason StateFinishReason);
|
virtual void Exit(const FFFStateContext& InStateContext, EFFStateFinishReason StateFinishReason);
|
||||||
|
|
||||||
@ -210,40 +181,20 @@ public:
|
|||||||
*/
|
*/
|
||||||
virtual void Update(float OneFrame, const FFFStateContext& InStateContext);
|
virtual void Update(float OneFrame, const FFFStateContext& InStateContext);
|
||||||
|
|
||||||
/**
|
// TODO: document
|
||||||
* Called whenever state owners lands on a walkable surface after they were previously falling.
|
|
||||||
*
|
|
||||||
* Calls appropriate Blueprint hooks.
|
|
||||||
*
|
|
||||||
* @param Hit Hit result for the surface that was landed on
|
|
||||||
*/
|
|
||||||
virtual void Landed(const FHitResult& Hit, const FFFStateContext& InStateContext);
|
virtual void Landed(const FHitResult& Hit, const FFFStateContext& InStateContext);
|
||||||
|
|
||||||
/**
|
// TODO: document
|
||||||
* @brief Called whenever the state's owner current movement mode changes
|
|
||||||
*
|
|
||||||
* Calls appropriate Blueprint hooks.
|
|
||||||
*
|
|
||||||
* @param PrevMovementMode previous movement mode.
|
|
||||||
* @param PreviousCustomMode previous movement mode if PrevMovementMode is MOVE_Custom
|
|
||||||
* @param NewMovementMode new movement mode
|
|
||||||
* @param NewCustomMode new movement mode if NewMovementMode is MOVE_Custom
|
|
||||||
*/
|
|
||||||
virtual void MovementModeChanged(EMovementMode PrevMovementMode, uint8 PreviousCustomMode,
|
virtual void MovementModeChanged(EMovementMode PrevMovementMode, uint8 PreviousCustomMode,
|
||||||
EMovementMode NewMovementMode, uint8 NewCustomMode, const FFFStateContext& InStateContext);
|
EMovementMode NewMovementMode, uint8 NewCustomMode, const FFFStateContext& InStateContext);
|
||||||
|
|
||||||
// TODO: document
|
// TODO: document
|
||||||
virtual void AttackHit(const FHitResult& HitResult, const FFFStateContext& InStateContext);
|
// TODO: pass in hitdata struct as well
|
||||||
|
virtual void Hit(const FFFStateContext& InStateContext);
|
||||||
|
|
||||||
// TODO: document
|
// TODO: document
|
||||||
// TODO: call this callback when the avatar is hit
|
|
||||||
// TODO: pass in hitdata struct as well
|
// TODO: pass in hitdata struct as well
|
||||||
virtual void HitTaken(/*const FHitParams& HitParams,*/ const FFFStateContext& InStateContext);
|
virtual void Block(const FFFStateContext& InStateContext);
|
||||||
|
|
||||||
// TODO: document
|
|
||||||
// TODO: call this callback when the avatar blocks a hit
|
|
||||||
// TODO: pass in hitdata struct as well
|
|
||||||
virtual void BlockTaken(const FFFStateContext& InStateContext);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when you want to exit from this state but no eligible transitions exist to other states,
|
* Called when you want to exit from this state but no eligible transitions exist to other states,
|
||||||
@ -261,7 +212,12 @@ public:
|
|||||||
UFUNCTION(BlueprintCallable)
|
UFUNCTION(BlueprintCallable)
|
||||||
virtual void Finish(const FFFStateContext& InStateContext, EFFStateFinishReason StateFinishReason);
|
virtual void Finish(const FFFStateContext& InStateContext, EFFStateFinishReason StateFinishReason);
|
||||||
|
|
||||||
/** @brief Blueprint hook for when the state is first created. */
|
// TODO: document
|
||||||
|
UFUNCTION(BlueprintCallable)
|
||||||
|
virtual void RegisterInputHandler(
|
||||||
|
const FFFInputSequence& InRequiredSequence, FFFInputEventDelegate InDelegate);
|
||||||
|
|
||||||
|
// TODO: document
|
||||||
UFUNCTION(BlueprintNativeEvent, Category="UFF|State|Events")
|
UFUNCTION(BlueprintNativeEvent, Category="UFF|State|Events")
|
||||||
void OnInit(const FFFStateContext& InStateContext);
|
void OnInit(const FFFStateContext& InStateContext);
|
||||||
|
|
||||||
@ -291,23 +247,9 @@ public:
|
|||||||
UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events")
|
UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events")
|
||||||
void OnUpdate(float OneFrame, const FFFStateContext& InStateContext);
|
void OnUpdate(float OneFrame, const FFFStateContext& InStateContext);
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the next state to transition into when this state finishes.
|
|
||||||
*
|
|
||||||
* This is called during the Finish function, either when the state duration is reached or the
|
|
||||||
* state is manually finished due to some other logic.
|
|
||||||
*
|
|
||||||
* By default this returns the name of the state specified by the FollowupState property or
|
|
||||||
* NAME_None if the FollowupState is not specified.
|
|
||||||
*/
|
|
||||||
UFUNCTION(BlueprintNativeEvent, Category="UFF|State|Events")
|
|
||||||
FName GetFollowupState(const FFFStateContext& InStateContext);
|
|
||||||
|
|
||||||
// TODO: Don't like this at all and needs to be refactored or redesigned
|
|
||||||
UFUNCTION(BlueprintCallable, Category="UFF|State|Animations")
|
UFUNCTION(BlueprintCallable, Category="UFF|State|Animations")
|
||||||
void PlayMontage(const FFFStateContext& InStateContext);
|
void PlayMontage(const FFFStateContext& InStateContext);
|
||||||
|
|
||||||
// TODO: Don't like this at all and needs to be refactored or redesigned
|
|
||||||
/**
|
/**
|
||||||
* Blueprint hook for overriding the logic for when a anim montage plays at the start of a state
|
* Blueprint hook for overriding the logic for when a anim montage plays at the start of a state
|
||||||
* @param InStateContext
|
* @param InStateContext
|
||||||
@ -315,23 +257,11 @@ public:
|
|||||||
UFUNCTION(BlueprintNativeEvent, Category="UFF|State|Events")
|
UFUNCTION(BlueprintNativeEvent, Category="UFF|State|Events")
|
||||||
void OnPlayMontage(const FFFStateContext& InStateContext);
|
void OnPlayMontage(const FFFStateContext& InStateContext);
|
||||||
|
|
||||||
/**
|
// TODO: document
|
||||||
* @brief Blueprint hook for whenever state owners lands on a walkable surface after they were
|
|
||||||
* previously falling.
|
|
||||||
*
|
|
||||||
* @param Hit Hit result for the surface that was landed on
|
|
||||||
*/
|
|
||||||
UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events")
|
UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events")
|
||||||
void OnLanded(const FHitResult& Hit, const FFFStateContext& InStateContext);
|
void OnLanded(const FHitResult& Hit, const FFFStateContext& InStateContext);
|
||||||
|
|
||||||
/**
|
// TODO: document
|
||||||
* @brief Blueprint hook whenever the state's owner current movement mode changes
|
|
||||||
*
|
|
||||||
* @param PrevMovementMode previous movement mode.
|
|
||||||
* @param PreviousCustomMode previous movement mode if PrevMovementMode is MOVE_Custom
|
|
||||||
* @param NewMovementMode new movement mode
|
|
||||||
* @param NewCustomMode new movement mode if NewMovementMode is MOVE_Custom
|
|
||||||
*/
|
|
||||||
UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events")
|
UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events")
|
||||||
void OnMovementModeChanged(EMovementMode PrevMovementMode, uint8 PreviousCustomMode,
|
void OnMovementModeChanged(EMovementMode PrevMovementMode, uint8 PreviousCustomMode,
|
||||||
EMovementMode NewMovementMode, uint8 NewCustomMode, const FFFStateContext& InStateContext);
|
EMovementMode NewMovementMode, uint8 NewCustomMode, const FFFStateContext& InStateContext);
|
||||||
@ -339,16 +269,12 @@ public:
|
|||||||
// TODO: document
|
// TODO: document
|
||||||
// TODO: pass in hitdata struct as well
|
// TODO: pass in hitdata struct as well
|
||||||
UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events")
|
UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events")
|
||||||
void OnAttackHit(const FHitResult& Hit, const FFFStateContext& InStateContext);
|
void OnHit(const FFFStateContext& InStateContext);
|
||||||
|
|
||||||
// TODO: document
|
|
||||||
UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events")
|
|
||||||
void OnHitTaken(/*const FHitParams,*/ const FFFStateContext& InStateContext);
|
|
||||||
|
|
||||||
// TODO: document
|
// TODO: document
|
||||||
// TODO: pass in hitdata struct as well
|
// TODO: pass in hitdata struct as well
|
||||||
UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events")
|
UFUNCTION(BlueprintImplementableEvent, Category="UFF|State|Events")
|
||||||
void OnBlockTaken(/*const FHitParams,*/ const FFFStateContext& InStateContext);
|
void OnBlock(const FFFStateContext& InStateContext);
|
||||||
|
|
||||||
// UObject interface
|
// UObject interface
|
||||||
virtual UWorld* GetWorld() const override;
|
virtual UWorld* GetWorld() const override;
|
||||||
|
@ -103,9 +103,20 @@ void UFFStateMachineComponent::GoToState(UFFState* NewState, EFFStateFinishReaso
|
|||||||
check(CurrentState);
|
check(CurrentState);
|
||||||
check(NewState);
|
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);
|
CurrentState->Exit(GetCurrentStateContext(), StateFinishReason);
|
||||||
TicksInState = 0;
|
TicksInState = 0;
|
||||||
OnLandedTick = 0;
|
|
||||||
TickStateWasEntered = GetWorld()->GetGameState<AFFGameState>()->GetCurrentTick();
|
TickStateWasEntered = GetWorld()->GetGameState<AFFGameState>()->GetCurrentTick();
|
||||||
CurrentSubStateLabel = NAME_None;
|
CurrentSubStateLabel = NAME_None;
|
||||||
NewState->Enter(GetCurrentStateContext());
|
NewState->Enter(GetCurrentStateContext());
|
||||||
@ -150,8 +161,6 @@ void UFFStateMachineComponent::Landed(const FHitResult& Hit)
|
|||||||
{
|
{
|
||||||
check(CurrentState)
|
check(CurrentState)
|
||||||
|
|
||||||
OnLandedTick = GetWorld()->GetGameState<AFFGameState>()->GetCurrentTick();
|
|
||||||
|
|
||||||
CurrentState->Landed(Hit, GetCurrentStateContext());
|
CurrentState->Landed(Hit, GetCurrentStateContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,16 +183,6 @@ void UFFStateMachineComponent::MovementModeChanged(EMovementMode PrevMovementMod
|
|||||||
NewMovementMode, NewCustomMode, GetCurrentStateContext());
|
NewMovementMode, NewCustomMode, GetCurrentStateContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
void UFFStateMachineComponent::AttackHit(const FHitResult& HitResult)
|
|
||||||
{
|
|
||||||
check(CurrentState)
|
|
||||||
CurrentState->AttackHit(HitResult, GetCurrentStateContext());
|
|
||||||
}
|
|
||||||
|
|
||||||
void UFFStateMachineComponent::TakeHit()
|
|
||||||
{
|
|
||||||
CurrentState->HitTaken(GetCurrentStateContext());
|
|
||||||
}
|
|
||||||
|
|
||||||
void UFFStateMachineComponent::FixedTick(float OneFrame)
|
void UFFStateMachineComponent::FixedTick(float OneFrame)
|
||||||
{
|
{
|
||||||
@ -234,7 +233,7 @@ UFFState* UFFStateMachineComponent::FindStateWithName(FName StateName)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UE_LOG(LogTemp, Error,
|
UE_LOG(LogTemp, Warning,
|
||||||
TEXT("Could not find state in state machine with name %s"), *StateName.ToString());
|
TEXT("Could not find state in state machine with name %s"), *StateName.ToString());
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@ -73,10 +73,8 @@ public:
|
|||||||
*
|
*
|
||||||
* Triggers the Exit callback on the CurrentState and the Enter callback on the new state
|
* Triggers the Exit callback on the CurrentState and the Enter callback on the new state
|
||||||
*
|
*
|
||||||
* @param StateFinishReason the reason the current state is ending i.e. the current state's
|
* TODO: document StateFinishReason
|
||||||
* duration elapsed
|
|
||||||
*/
|
*/
|
||||||
UFUNCTION(BlueprintCallable)
|
|
||||||
void GoToState(FName NewStateName, EFFStateFinishReason StateFinishReason);
|
void GoToState(FName NewStateName, EFFStateFinishReason StateFinishReason);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -84,16 +82,14 @@ public:
|
|||||||
*
|
*
|
||||||
* Triggers the Exit callback on the CurrentState and the Enter callback on the new state
|
* Triggers the Exit callback on the CurrentState and the Enter callback on the new state
|
||||||
*
|
*
|
||||||
* @param StateFinishReason the reason the current state is ending i.e. the current state's
|
* TODO: document StateFinishReason
|
||||||
* duration elapsed
|
|
||||||
*/
|
*/
|
||||||
void GoToState(UFFState* NewState, EFFStateFinishReason StateFinishReason);
|
void GoToState(UFFState* NewState, EFFStateFinishReason StateFinishReason);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transitions from CurrentState to the default entry state
|
* Transitions from CurrentState to the default entry state
|
||||||
*
|
*
|
||||||
* @param StateFinishReason the reason the current state is ending i.e. the current state's
|
* TODO: document StateFinishReason
|
||||||
* duration elapsed
|
|
||||||
*/
|
*/
|
||||||
void GoToEntryState(EFFStateFinishReason StateFinishReason);
|
void GoToEntryState(EFFStateFinishReason StateFinishReason);
|
||||||
|
|
||||||
@ -108,14 +104,6 @@ public:
|
|||||||
UFUNCTION(BlueprintPure)
|
UFUNCTION(BlueprintPure)
|
||||||
FORCEINLINE int64 GetTickStateWasEntered() const { return TickStateWasEntered; }
|
FORCEINLINE int64 GetTickStateWasEntered() const { return TickStateWasEntered; }
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the tick number which corresponds to when the On Landed event fired
|
|
||||||
*
|
|
||||||
* The tick number represents the ticks that have elapsed since the game began.
|
|
||||||
*/
|
|
||||||
UFUNCTION(BlueprintPure)
|
|
||||||
FORCEINLINE int64 GetOnLandedTick() const { return OnLandedTick; }
|
|
||||||
|
|
||||||
UFUNCTION(BlueprintPure)
|
UFUNCTION(BlueprintPure)
|
||||||
const UFFState* GetCurrentState() const { return const_cast<UFFState*>(CurrentState); }
|
const UFFState* GetCurrentState() const { return const_cast<UFFState*>(CurrentState); }
|
||||||
|
|
||||||
@ -135,38 +123,16 @@ public:
|
|||||||
void SetSubStateLabel(FName InSubStateLabel);
|
void SetSubStateLabel(FName InSubStateLabel);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: document
|
*
|
||||||
*/
|
*/
|
||||||
FFFStateContext GetCurrentStateContext();
|
FFFStateContext GetCurrentStateContext();
|
||||||
|
|
||||||
// Events
|
// Events
|
||||||
/**
|
|
||||||
* Called whenever state machine's owner lands on a walkable surface after they were previously falling.
|
|
||||||
*
|
|
||||||
* @param Hit Hit result for the surface that was landed on
|
|
||||||
*/
|
|
||||||
virtual void Landed(const FHitResult& Hit);
|
virtual void Landed(const FHitResult& Hit);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Called whenever the state machine's owner current movement mode changes
|
|
||||||
*
|
|
||||||
* Calls appropriate Blueprint hooks.
|
|
||||||
*
|
|
||||||
* @param PrevMovementMode previous movement mode.
|
|
||||||
* @param PreviousCustomMode previous movement mode if PrevMovementMode is MOVE_Custom
|
|
||||||
* @param NewMovementMode new movement mode
|
|
||||||
* @param NewCustomMode new movement mode if NewMovementMode is MOVE_Custom
|
|
||||||
*/
|
|
||||||
virtual void MovementModeChanged(EMovementMode PrevMovementMode, uint8 PreviousCustomMode,
|
virtual void MovementModeChanged(EMovementMode PrevMovementMode, uint8 PreviousCustomMode,
|
||||||
EMovementMode NewMovementMode, uint8 NewCustomMode);
|
EMovementMode NewMovementMode, uint8 NewCustomMode);
|
||||||
|
|
||||||
// TODO: document
|
|
||||||
virtual void AttackHit(const FHitResult& HitResult);
|
|
||||||
|
|
||||||
// TODO: document
|
|
||||||
// TODO: bring HitParams struct into FightingFramework
|
|
||||||
virtual void TakeHit(/* const FHitParams& HitParams*/);
|
|
||||||
|
|
||||||
FOnStateTransitionSignature OnStateTransition;
|
FOnStateTransitionSignature OnStateTransition;
|
||||||
|
|
||||||
// IFFSystemInterface interface
|
// IFFSystemInterface interface
|
||||||
@ -192,19 +158,12 @@ protected:
|
|||||||
int64 TicksInState;
|
int64 TicksInState;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The tick number which corresponds to when the current state was entered.
|
* 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.
|
* The tick number represents the ticks that have elapsed since the game began.
|
||||||
*/
|
*/
|
||||||
int64 TickStateWasEntered;
|
int64 TickStateWasEntered;
|
||||||
|
|
||||||
/**
|
|
||||||
* The tick number which corresponds to when the On Landed event was called for the current tick.
|
|
||||||
*
|
|
||||||
* The tick number represents the ticks that have elapsed since the game began.
|
|
||||||
*/
|
|
||||||
int64 OnLandedTick;
|
|
||||||
|
|
||||||
/** Current active state for this state machine */
|
/** Current active state for this state machine */
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
UFFState* CurrentState;
|
UFFState* CurrentState;
|
||||||
@ -212,7 +171,6 @@ protected:
|
|||||||
/** Current SubState label or NAME_None if there is no SubState label set for the current state*/
|
/** Current SubState label or NAME_None if there is no SubState label set for the current state*/
|
||||||
FName CurrentSubStateLabel;
|
FName CurrentSubStateLabel;
|
||||||
|
|
||||||
// TODO: should be a TMap
|
|
||||||
// States that have been added
|
// States that have been added
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
TArray<UFFState*> States;
|
TArray<UFFState*> States;
|
||||||
|
@ -2,10 +2,6 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
// FF includes
|
|
||||||
#include "Input/FFInputBufferComponent.h"
|
|
||||||
|
|
||||||
// UE includes
|
|
||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
#include "UObject/Interface.h"
|
#include "UObject/Interface.h"
|
||||||
|
|
||||||
@ -18,46 +14,18 @@ class UFFStateOwnerInterface : public UInterface
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: document
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// TODO: why is this an interface again?
|
|
||||||
class UNREALFIGHTINGFRAMEWORK_API IFFStateOwnerInterface
|
class UNREALFIGHTINGFRAMEWORK_API IFFStateOwnerInterface
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
|
||||||
* @brief Checks to see if the provided input sequence has a valid matching series of inputs in
|
|
||||||
* the input buffer
|
|
||||||
*
|
|
||||||
* @param InInputSequence sequence of inputs to check in the input buffer
|
|
||||||
* @return true if the input sequence has a valid matching series of inputs in the input buffer
|
|
||||||
*/
|
|
||||||
UFUNCTION(BlueprintCallable, Category="UFF State Owner Interface")
|
UFUNCTION(BlueprintCallable, Category="UFF State Owner Interface")
|
||||||
virtual bool CheckInputSequence(const FFFInputSequence& InInputSequence) = 0;
|
virtual bool CheckInputSequence(const FFFInputSequence& InInputSequence) = 0;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Checks to see if at least one of the provided input sequences has a valid matching
|
|
||||||
* series of inputs in the input buffer
|
|
||||||
*
|
|
||||||
* @param InInputSequences array of sequences of inputs to check in the input buffer
|
|
||||||
* @return true if at least one input sequence has a valid matching series of inputs in the
|
|
||||||
* input buffer
|
|
||||||
*/
|
|
||||||
UFUNCTION(BlueprintCallable, Category="UFF State Owner Interface")
|
UFUNCTION(BlueprintCallable, Category="UFF State Owner Interface")
|
||||||
virtual bool CheckInputSequences(const TArray<FFFInputSequence>& InInputSequences) = 0;
|
virtual bool CheckInputSequences(const TArray<FFFInputSequence>& InInputSequences) = 0;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Convert 2D movement input a 3D world direction relative to the view orientation
|
|
||||||
*
|
|
||||||
* @return direction of player's 2D movement input in world space
|
|
||||||
*/
|
|
||||||
virtual FVector GetMoveInputAsWorldDirection() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Sets the current tick's input in the input buffer as disabled, preventing it from
|
|
||||||
* being considered as valid input for input sequence interpretation
|
|
||||||
*/
|
|
||||||
virtual void DisableMostRecentInput() = 0;
|
virtual void DisableMostRecentInput() = 0;
|
||||||
};
|
};
|
||||||
|
@ -1,139 +0,0 @@
|
|||||||
// Unreal Fighting Framework by Kevin Poretti
|
|
||||||
|
|
||||||
// FF includes
|
|
||||||
#include "Input/FFInputBufferComponent.h"
|
|
||||||
|
|
||||||
// UE includes
|
|
||||||
#include "CoreMinimal.h"
|
|
||||||
#include "Misc/AutomationTest.h"
|
|
||||||
|
|
||||||
#if WITH_DEV_AUTOMATION_TESTS
|
|
||||||
|
|
||||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FInputBufferTest, "FightingFramework.Input.InputBuffer",
|
|
||||||
EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::ProductFilter)
|
|
||||||
bool FInputBufferTest::RunTest(const FString& Parameters)
|
|
||||||
{
|
|
||||||
TStrongObjectPtr<UWorld> World = TStrongObjectPtr(UWorld::CreateWorld(EWorldType::Game, false));
|
|
||||||
// Create a dummy actor
|
|
||||||
TStrongObjectPtr<AActor> InputBufferOwner = TStrongObjectPtr<AActor>(World->SpawnActor<AActor>());
|
|
||||||
TestTrue(TEXT("Create InputBufferOwner"), InputBufferOwner.IsValid());
|
|
||||||
UFFInputBufferComponent* InputBuffer =
|
|
||||||
NewObject<UFFInputBufferComponent>(InputBufferOwner.Get(),
|
|
||||||
UFFInputBufferComponent::StaticClass(), TEXT("InputBuffer"));
|
|
||||||
InputBuffer->RegisterComponent();
|
|
||||||
|
|
||||||
// InputBuffer must be able to be created as a subobject of an actor
|
|
||||||
{
|
|
||||||
TestNotNull(TEXT("Create new InputBuffer component"), InputBuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// InputBuffer should not attempt to evaluate an input sequence if there is only one input in
|
|
||||||
// the buffer and should return false
|
|
||||||
{
|
|
||||||
FFFInputState InputDown;
|
|
||||||
InputDown.Buttons = 0x00000001;
|
|
||||||
|
|
||||||
InputBuffer->AddInput(InputDown);
|
|
||||||
|
|
||||||
FFFInputCondition PressedCondition;
|
|
||||||
PressedCondition.RequiredButtons = 0x00000001;
|
|
||||||
PressedCondition.RequiredButtonState = EFFButtonState::BTNS_Pressed;
|
|
||||||
PressedCondition.TimeoutDuration = 0;
|
|
||||||
|
|
||||||
FFFInputSequence PressedSequence;
|
|
||||||
PressedSequence.Sequence.Add(PressedCondition);
|
|
||||||
TestFalse(TEXT("Don't try to detect a sequence if only one input is present in the buffer"),
|
|
||||||
InputBuffer->CheckInputSequence(PressedSequence));
|
|
||||||
}
|
|
||||||
|
|
||||||
InputBuffer->FlushBuffer();
|
|
||||||
|
|
||||||
// InputBuffer should not attempt to evaluate an input sequence if the sequence has no input conditions
|
|
||||||
{
|
|
||||||
FFFInputState InputUp;
|
|
||||||
InputUp.Buttons = 0x00000000;
|
|
||||||
FFFInputState InputDown;
|
|
||||||
InputDown.Buttons = 0x00000001;
|
|
||||||
|
|
||||||
InputBuffer->AddInput(InputUp);
|
|
||||||
InputBuffer->AddInput(InputDown);
|
|
||||||
|
|
||||||
FFFInputSequence EmptySequence;
|
|
||||||
TestFalse(TEXT("Don't try to detect a sequence if the sequence has no input conditions"),
|
|
||||||
InputBuffer->CheckInputSequence(EmptySequence));
|
|
||||||
}
|
|
||||||
|
|
||||||
InputBuffer->FlushBuffer();
|
|
||||||
|
|
||||||
// InputBuffer must detect simple button pressed condition
|
|
||||||
{
|
|
||||||
FFFInputState InputUp;
|
|
||||||
InputUp.Buttons = 0x00000000;
|
|
||||||
FFFInputState InputDown;
|
|
||||||
InputDown.Buttons = 0x00000001;
|
|
||||||
|
|
||||||
InputBuffer->AddInput(InputUp);
|
|
||||||
InputBuffer->AddInput(InputDown);
|
|
||||||
|
|
||||||
FFFInputCondition PressedCondition;
|
|
||||||
PressedCondition.RequiredButtons = 0x00000001;
|
|
||||||
PressedCondition.RequiredButtonState = EFFButtonState::BTNS_Pressed;
|
|
||||||
PressedCondition.TimeoutDuration = 0;
|
|
||||||
|
|
||||||
FFFInputSequence PressedSequence;
|
|
||||||
PressedSequence.Sequence.Add(PressedCondition);
|
|
||||||
TestTrue(TEXT("Detect a single button press in the buffer"),
|
|
||||||
InputBuffer->CheckInputSequence(PressedSequence));
|
|
||||||
}
|
|
||||||
|
|
||||||
InputBuffer->FlushBuffer();
|
|
||||||
|
|
||||||
// InputBuffer must detect simple button released condition
|
|
||||||
{
|
|
||||||
FFFInputState InputUp;
|
|
||||||
InputUp.Buttons = 0x0000000;
|
|
||||||
FFFInputState InputDown;
|
|
||||||
InputDown.Buttons = 0x00000001;
|
|
||||||
|
|
||||||
InputBuffer->AddInput(InputDown);
|
|
||||||
InputBuffer->AddInput(InputUp);
|
|
||||||
|
|
||||||
FFFInputCondition ReleasedCondition;
|
|
||||||
ReleasedCondition.RequiredButtons = 0x00000001;
|
|
||||||
ReleasedCondition.RequiredButtonState = EFFButtonState::BTNS_Released;
|
|
||||||
ReleasedCondition.TimeoutDuration = 0;
|
|
||||||
|
|
||||||
FFFInputSequence ReleasedSequence;
|
|
||||||
ReleasedSequence.Sequence.Add(ReleasedCondition);
|
|
||||||
TestTrue(TEXT("Detect a single button release in the buffer"),
|
|
||||||
InputBuffer->CheckInputSequence(ReleasedSequence));
|
|
||||||
}
|
|
||||||
|
|
||||||
InputBuffer->FlushBuffer();
|
|
||||||
|
|
||||||
// InputBuffer must detect simple button down condition
|
|
||||||
{
|
|
||||||
FFFInputState InputUp;
|
|
||||||
InputUp.Buttons = 0x0000000;
|
|
||||||
FFFInputState InputDown;
|
|
||||||
InputDown.Buttons = 0x00000001;
|
|
||||||
|
|
||||||
InputBuffer->AddInput(InputUp);
|
|
||||||
InputBuffer->AddInput(InputDown);
|
|
||||||
|
|
||||||
FFFInputCondition DownCondition;
|
|
||||||
DownCondition.RequiredButtons = 0x00000001;
|
|
||||||
DownCondition.RequiredButtonState = EFFButtonState::BTNS_Down;
|
|
||||||
DownCondition.TimeoutDuration = 0;
|
|
||||||
|
|
||||||
FFFInputSequence DownSequence;
|
|
||||||
DownSequence.Sequence.Add(DownCondition);
|
|
||||||
TestTrue(TEXT("Detect a single button press in the buffer"),
|
|
||||||
InputBuffer->CheckInputSequence(DownSequence));
|
|
||||||
}
|
|
||||||
|
|
||||||
World->DestroyWorld(false);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //WITH_DEV_AUTOMATION_TESTS
|
|
@ -26,7 +26,6 @@ public class UnrealFightingFramework : ModuleRules
|
|||||||
{
|
{
|
||||||
"Core",
|
"Core",
|
||||||
"EnhancedInput",
|
"EnhancedInput",
|
||||||
"AIModule",
|
|
||||||
// ... add other public dependencies that you statically link with here ...
|
// ... add other public dependencies that you statically link with here ...
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -108,16 +108,6 @@ public:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Flushes the buffer of all contents
|
|
||||||
*/
|
|
||||||
void Flush()
|
|
||||||
{
|
|
||||||
WriteIdx = 0;
|
|
||||||
ReadIdx = 0;
|
|
||||||
_Num = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns the element at an index supplied to the function.
|
* @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.
|
* This function will account for write index offset and wraparound of the supplied index.
|
||||||
|
Loading…
Reference in New Issue
Block a user