UnrealFightingFramework/Source/UnrealFightingFramework/State/FFStateMachineComponent.cpp

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