256 lines
7.2 KiB
C++
256 lines
7.2 KiB
C++
// 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);
|
|
|
|
CurrentState->Exit(GetCurrentStateContext(), StateFinishReason);
|
|
TicksInState = 0;
|
|
OnLandedTick = 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)
|
|
|
|
OnLandedTick = GetWorld()->GetGameState<AFFGameState>()->GetCurrentTick();
|
|
|
|
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
|