Start creating custom state machine asset type

This commit is contained in:
Kevin Poretti 2022-03-30 21:49:09 -04:00
parent ec0735ceca
commit 1501e35ad7
81 changed files with 498 additions and 493 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -6,6 +6,7 @@
#include "DrawDebugHelpers.h"
#include "Camera/CameraComponent.h"
#include "Components/CapsuleComponent.h"
#include "Components/InputComponent.h"
#include "Components/SNGCharacterMovementComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "Kismet/KismetMathLibrary.h"

View File

@ -4,6 +4,7 @@
#include "Characters/SNGProtagonist.h"
#include "DrawDebugHelpers.h"
#include "Animation/AnimInstance.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Kismet/KismetMathLibrary.h"
#include "Kismet/KismetSystemLibrary.h"

View File

@ -1,15 +1,15 @@
// Project Sword & Gun Copyright © 2021 Kevin Poretti
#include "States/SNGEnemyBaseState.h"
void USNGEnemyBaseState::SetOwner(AActor* StateOwner)
{
Super::SetOwner(StateOwner);
Enemy = Cast<ASNGEnemyBase>(StateOwner);
if(!Enemy)
{
UE_LOG(LogTemp, Error, TEXT("SNGEnemyBaseState :: Could not cast state owner to SNGEnemyBase"));
}
}
// Project Sword & Gun Copyright © 2021 Kevin Poretti
#include "States/OLD/SNGEnemyBaseState.h"
void USNGEnemyBaseState::SetOwner(AActor* StateOwner)
{
Super::SetOwner(StateOwner);
Enemy = Cast<ASNGEnemyBase>(StateOwner);
if(!Enemy)
{
UE_LOG(LogTemp, Error, TEXT("SNGEnemyBaseState :: Could not cast state owner to SNGEnemyBase"));
}
}

View File

@ -1,15 +1,15 @@
// Project Sword & Gun Copyright © 2021 Kevin Poretti
#include "States/SNGProtagonistState.h"
void USNGProtagonistState::SetOwner(AActor* StateOwner)
{
Super::SetOwner(StateOwner);
Protagonist = Cast<ASNGProtagonist>(StateOwner);
if(!Protagonist)
{
UE_LOG(LogTemp, Error, TEXT("SNGProtagonistState :: Could not cast state owner to SNGProtagonist"));
}
}
// Project Sword & Gun Copyright © 2021 Kevin Poretti
#include "States/OLD/SNGProtagonistState.h"
void USNGProtagonistState::SetOwner(AActor* StateOwner)
{
Super::SetOwner(StateOwner);
Protagonist = Cast<ASNGProtagonist>(StateOwner);
if(!Protagonist)
{
UE_LOG(LogTemp, Error, TEXT("SNGProtagonistState :: Could not cast state owner to SNGProtagonist"));
}
}

View File

@ -1,204 +1,204 @@
// Project Sword & Gun Copyright © 2021 Kevin Poretti
#include "States/SNGState.h"
USNGState::USNGState()
{
Name = NAME_None;
TimeInState = 0.0f;
bCanTransitionToItself = false;
DefaultSubStateIndex = 0;
}
void USNGState::RegisterTransition(FName DestState, FName TransitionRuleFunctionName)
{
FStateTransitionRule StateTransitionRule;
StateTransitionRule.BindUFunction(this, TransitionRuleFunctionName);
if(!Transitions.Contains(DestState))
{
Transitions.Add(DestState, StateTransitionRule);
}
else
{
UE_LOG(LogTemp, Warning, TEXT("USNGState :: Transition dictionary already contains transition rule for destination state %s"), *DestState.ToString());
}
}
void USNGState::RegisterEventHandler(ESNGEventType EventType, FName EventHandlerFunctionName)
{
FEventHandlerRule EventHandlerRule;
EventHandlerRule.BindUFunction(this, EventHandlerFunctionName);
if(Handlers.Contains(EventType))
{
Handlers[EventType].Add(EventHandlerRule);
}
else
{
Handlers.Add(EventType, TArray<FEventHandlerRule>());
Handlers[EventType].Add(EventHandlerRule);
}
}
FName USNGState::GetNextStateFromEvent(FSNGEvent& Event)
{
FName DestState = NAME_None;
if(Handlers.Contains(Event.EventType))
{
for (auto EventHandler : Handlers[Event.EventType])
{
// FStateTransitionRule is a delegate that returns true if the transition rules are met
// and false otherwise
if(!EventHandler.IsBound())
{
UE_LOG(LogTemp, Error, TEXT("SNGState :: Tried calling function %s but it is not bound"), *EventHandler.GetFunctionName().ToString());
continue;
}
DestState = EventHandler.Execute(Event);
if(DestState != NAME_None)
{
return DestState;
}
}
}
if(Parent)
{
return Parent->GetNextStateFromEvent(Event);
}
return NAME_None;
}
FName USNGState::GetNextState()
{
// NOTE(kevin): Does this actually have to be a map?
for(const TPair<FName, FStateTransitionRule>& pair : Transitions)
{
// FStateTransitionRule is a delegate that returns true if the transition rules are met
// and false otherwise
if(!pair.Value.IsBound())
{
UE_LOG(LogTemp, Error, TEXT("SNGState :: Tried calling function %s but it is not bound"), *pair.Value.GetFunctionName().ToString());
continue;
}
if(pair.Value.Execute())
{
return pair.Key;
}
}
if(Parent)
{
return Parent->GetNextState();
}
return NAME_None;
}
AActor* USNGState::GetOwner()
{
return Owner;
}
void USNGState::SetOwner(AActor* StateOwner)
{
Owner = StateOwner;
}
USNGState* USNGState::GetParent()
{
return Parent;
}
void USNGState::SetParent(USNGState* ParentState)
{
this->Parent = ParentState;
}
uint32 USNGState::GetDepth()
{
return Depth;
}
void USNGState::CreateState()
{
// pre-compute the depth/level of this state during creation
// used later for calculating least common ancestor of two states
uint32 CurrDepth = 0;
USNGState* CurrState = this;
while(CurrState->Parent != nullptr)
{
CurrDepth++;
CurrState = CurrState->Parent;
}
Depth = CurrDepth;
OnCreateState();
}
USNGState* USNGState::EnterState(FSNGEvent& Event)
{
OnEnterState(Event);
TimeInState = 0.0f;
if(SubStates.Num() > 0 &&
(DefaultSubStateIndex >= 0 && DefaultSubStateIndex < SubStates.Num()))
{
return SubStates[DefaultSubStateIndex]->EnterState(Event);
}
return this;
}
void USNGState::ExitState()
{
OnExitState();
// to do this correctly i think we need to find the shared parent of the current state and the next state
// we only want to exit states until that point
if(Parent)
{
Parent->ExitState();
}
}
void USNGState::UpdateState(float DeltaTime)
{
OnUpdateState(DeltaTime);
TimeInState += DeltaTime;
if(Parent)
{
Parent->UpdateState(DeltaTime);
}
}
FName USNGState::GetName()
{
return Name;
}
float USNGState::GetTimeInState()
{
return TimeInState;
}
bool USNGState::CanTransitionToItself()
{
return bCanTransitionToItself;
}
TArray<TSubclassOf<USNGState>> USNGState::GetSubStateClasses()
{
return SubStateClasses;
}
void USNGState::AddSubState(USNGState* SubState)
{
SubStates.Add(SubState);
}
// Project Sword & Gun Copyright © 2021 Kevin Poretti
#include "States/OLD/SNGState.h"
USNGState::USNGState()
{
Name = NAME_None;
TimeInState = 0.0f;
bCanTransitionToItself = false;
DefaultSubStateIndex = 0;
}
void USNGState::RegisterTransition(FName DestState, FName TransitionRuleFunctionName)
{
FStateTransitionRule StateTransitionRule;
StateTransitionRule.BindUFunction(this, TransitionRuleFunctionName);
if(!Transitions.Contains(DestState))
{
Transitions.Add(DestState, StateTransitionRule);
}
else
{
UE_LOG(LogTemp, Warning, TEXT("USNGState :: Transition dictionary already contains transition rule for destination state %s"), *DestState.ToString());
}
}
void USNGState::RegisterEventHandler(ESNGEventType EventType, FName EventHandlerFunctionName)
{
FEventHandlerRule EventHandlerRule;
EventHandlerRule.BindUFunction(this, EventHandlerFunctionName);
if(Handlers.Contains(EventType))
{
Handlers[EventType].Add(EventHandlerRule);
}
else
{
Handlers.Add(EventType, TArray<FEventHandlerRule>());
Handlers[EventType].Add(EventHandlerRule);
}
}
FName USNGState::GetNextStateFromEvent(FSNGEvent& Event)
{
FName DestState = NAME_None;
if(Handlers.Contains(Event.EventType))
{
for (auto EventHandler : Handlers[Event.EventType])
{
// FStateTransitionRule is a delegate that returns true if the transition rules are met
// and false otherwise
if(!EventHandler.IsBound())
{
UE_LOG(LogTemp, Error, TEXT("SNGState :: Tried calling function %s but it is not bound"), *EventHandler.GetFunctionName().ToString());
continue;
}
DestState = EventHandler.Execute(Event);
if(DestState != NAME_None)
{
return DestState;
}
}
}
if(Parent)
{
return Parent->GetNextStateFromEvent(Event);
}
return NAME_None;
}
FName USNGState::GetNextState()
{
// NOTE(kevin): Does this actually have to be a map?
for(const TPair<FName, FStateTransitionRule>& pair : Transitions)
{
// FStateTransitionRule is a delegate that returns true if the transition rules are met
// and false otherwise
if(!pair.Value.IsBound())
{
UE_LOG(LogTemp, Error, TEXT("SNGState :: Tried calling function %s but it is not bound"), *pair.Value.GetFunctionName().ToString());
continue;
}
if(pair.Value.Execute())
{
return pair.Key;
}
}
if(Parent)
{
return Parent->GetNextState();
}
return NAME_None;
}
AActor* USNGState::GetOwner()
{
return Owner;
}
void USNGState::SetOwner(AActor* StateOwner)
{
Owner = StateOwner;
}
USNGState* USNGState::GetParent()
{
return Parent;
}
void USNGState::SetParent(USNGState* ParentState)
{
this->Parent = ParentState;
}
uint32 USNGState::GetDepth()
{
return Depth;
}
void USNGState::CreateState()
{
// pre-compute the depth/level of this state during creation
// used later for calculating least common ancestor of two states
uint32 CurrDepth = 0;
USNGState* CurrState = this;
while(CurrState->Parent != nullptr)
{
CurrDepth++;
CurrState = CurrState->Parent;
}
Depth = CurrDepth;
OnCreateState();
}
USNGState* USNGState::EnterState(FSNGEvent& Event)
{
OnEnterState(Event);
TimeInState = 0.0f;
if(SubStates.Num() > 0 &&
(DefaultSubStateIndex >= 0 && DefaultSubStateIndex < SubStates.Num()))
{
return SubStates[DefaultSubStateIndex]->EnterState(Event);
}
return this;
}
void USNGState::ExitState()
{
OnExitState();
// to do this correctly i think we need to find the shared parent of the current state and the next state
// we only want to exit states until that point
if(Parent)
{
Parent->ExitState();
}
}
void USNGState::UpdateState(float DeltaTime)
{
OnUpdateState(DeltaTime);
TimeInState += DeltaTime;
if(Parent)
{
Parent->UpdateState(DeltaTime);
}
}
FName USNGState::GetName()
{
return Name;
}
float USNGState::GetTimeInState()
{
return TimeInState;
}
bool USNGState::CanTransitionToItself()
{
return bCanTransitionToItself;
}
TArray<TSubclassOf<USNGState>> USNGState::GetSubStateClasses()
{
return SubStateClasses;
}
void USNGState::AddSubState(USNGState* SubState)
{
SubStates.Add(SubState);
}

View File

@ -5,6 +5,7 @@
#include "DrawDebugHelpers.h"
#include "Characters/SNGCharacterBase_DEPRECATED.h"
#include "GameFramework/DamageType.h"
#include "Kismet/GameplayStatics.h"
#include "Particles/ParticleSystemComponent.h"
#include "PhysicalMaterials/PhysicalMaterial.h"

View File

@ -5,6 +5,7 @@
#include "Characters/SNGCharacterBase_DEPRECATED.h"
#include "GameFramework/DamageType.h"
#include "Kismet/KismetSystemLibrary.h"
#include "SwordNGun/SwordNGun.h"

View File

@ -3,7 +3,7 @@
#pragma once
#include "CoreMinimal.h"
#include "States/SNGState.h"
#include "States/OLD/SNGState.h"
#include "Components/ActorComponent.h"
#include "Interfaces/SNGEventProcessorInterface.h"

View File

@ -1,25 +1,25 @@
// Project Sword & Gun Copyright © 2021 Kevin Poretti
#pragma once
#include "CoreMinimal.h"
#include "Characters/SNGEnemyBase.h"
#include "States/SNGState.h"
#include "SNGEnemyBaseState.generated.h"
/**
*
*/
UCLASS()
class SWORDNGUN_API USNGEnemyBaseState : public USNGState
{
GENERATED_BODY()
public:
virtual void SetOwner(AActor* StateOwner) override;
protected:
UPROPERTY(BlueprintReadOnly)
ASNGEnemyBase* Enemy;
};
// Project Sword & Gun Copyright © 2021 Kevin Poretti
#pragma once
#include "CoreMinimal.h"
#include "Characters/SNGEnemyBase.h"
#include "States/OLD/SNGState.h"
#include "SNGEnemyBaseState.generated.h"
/**
*
*/
UCLASS()
class SWORDNGUN_API USNGEnemyBaseState : public USNGState
{
GENERATED_BODY()
public:
virtual void SetOwner(AActor* StateOwner) override;
protected:
UPROPERTY(BlueprintReadOnly)
ASNGEnemyBase* Enemy;
};

View File

@ -1,25 +1,25 @@
// Project Sword & Gun Copyright © 2021 Kevin Poretti
#pragma once
#include "CoreMinimal.h"
#include "Characters/SNGProtagonist.h"
#include "States/SNGState.h"
#include "SNGProtagonistState.generated.h"
/**
*
*/
UCLASS()
class SWORDNGUN_API USNGProtagonistState : public USNGState
{
GENERATED_BODY()
public:
virtual void SetOwner(AActor* StateOwner) override;
protected:
UPROPERTY(BlueprintReadOnly)
ASNGProtagonist* Protagonist;
};
// Project Sword & Gun Copyright © 2021 Kevin Poretti
#pragma once
#include "CoreMinimal.h"
#include "Characters/SNGProtagonist.h"
#include "States/OLD/SNGState.h"
#include "SNGProtagonistState.generated.h"
/**
*
*/
UCLASS()
class SWORDNGUN_API USNGProtagonistState : public USNGState
{
GENERATED_BODY()
public:
virtual void SetOwner(AActor* StateOwner) override;
protected:
UPROPERTY(BlueprintReadOnly)
ASNGProtagonist* Protagonist;
};

View File

@ -1,110 +1,110 @@
// Project Sword & Gun Copyright © 2021 Kevin Poretti
#pragma once
#include "CoreMinimal.h"
#include "SwordNGun/SNGTypes.h"
#include "SNGState.generated.h"
DECLARE_DYNAMIC_DELEGATE_RetVal(bool, FStateTransitionRule);
DECLARE_DYNAMIC_DELEGATE_RetVal_OneParam(FName, FEventHandlerRule, FSNGEvent&, Event);
/**
*
*/
UCLASS(Blueprintable)
class SWORDNGUN_API USNGState : public UObject
{
GENERATED_BODY()
public:
USNGState();
UFUNCTION(BlueprintCallable)
void RegisterTransition(FName DestState, FName TransitionRuleFunctionName);
UFUNCTION(BlueprintCallable)
void RegisterEventHandler(ESNGEventType EventType, FName EventHandlerFunctionName);
FName GetName();
float GetTimeInState();
bool CanTransitionToItself();
UFUNCTION(BlueprintCallable)
AActor* GetOwner();
virtual void SetOwner(AActor* StateOwner);
USNGState* GetParent();
void SetParent(USNGState* ParentState);
uint32 GetDepth();
TArray<TSubclassOf<USNGState>> GetSubStateClasses();
void AddSubState(USNGState* SubState);
// returns name of the next state if event triggers a state transition
FName GetNextStateFromEvent(FSNGEvent& Event);
// executes transitions rules for this state to determine what substate
FName GetNextState();
void CreateState();
USNGState* EnterState(FSNGEvent& Event);
void ExitState();
void UpdateState(float DeltaTime);
protected:
UPROPERTY(EditDefaultsOnly)
FName Name;
UPROPERTY(BlueprintReadOnly)
float TimeInState;
UPROPERTY(EditDefaultsOnly)
bool bCanTransitionToItself;
UPROPERTY(EditDefaultsOnly, Category="Sub states", meta = (ClampMin = -1))
int32 DefaultSubStateIndex;
UPROPERTY(EditDefaultsOnly, Category="Sub states")
TArray<TSubclassOf<USNGState>> SubStateClasses;
TArray<USNGState*> SubStates;
AActor* Owner;
USNGState* Parent;
// distance from root node
uint32 Depth;
// NOTE(kevin): I don't think this needs to be a map
TMap<FName, FStateTransitionRule> Transitions;
TMap<ESNGEventType, TArray<FEventHandlerRule>> Handlers;
UFUNCTION(BlueprintImplementableEvent)
void OnCreateState();
UFUNCTION(BlueprintImplementableEvent)
void OnEnterState(FSNGEvent Event);
UFUNCTION(BlueprintImplementableEvent)
void OnExitState();
UFUNCTION(BlueprintImplementableEvent)
void OnUpdateState(float DeltaTime);
};
// Project Sword & Gun Copyright © 2021 Kevin Poretti
#pragma once
#include "CoreMinimal.h"
#include "SwordNGun/SNGTypes.h"
#include "SNGState.generated.h"
DECLARE_DYNAMIC_DELEGATE_RetVal(bool, FStateTransitionRule);
DECLARE_DYNAMIC_DELEGATE_RetVal_OneParam(FName, FEventHandlerRule, FSNGEvent&, Event);
/**
*
*/
UCLASS(Blueprintable)
class SWORDNGUN_API USNGState : public UObject
{
GENERATED_BODY()
public:
USNGState();
UFUNCTION(BlueprintCallable)
void RegisterTransition(FName DestState, FName TransitionRuleFunctionName);
UFUNCTION(BlueprintCallable)
void RegisterEventHandler(ESNGEventType EventType, FName EventHandlerFunctionName);
FName GetName();
float GetTimeInState();
bool CanTransitionToItself();
UFUNCTION(BlueprintCallable)
AActor* GetOwner();
virtual void SetOwner(AActor* StateOwner);
USNGState* GetParent();
void SetParent(USNGState* ParentState);
uint32 GetDepth();
TArray<TSubclassOf<USNGState>> GetSubStateClasses();
void AddSubState(USNGState* SubState);
// returns name of the next state if event triggers a state transition
FName GetNextStateFromEvent(FSNGEvent& Event);
// executes transitions rules for this state to determine what substate
FName GetNextState();
void CreateState();
USNGState* EnterState(FSNGEvent& Event);
void ExitState();
void UpdateState(float DeltaTime);
protected:
UPROPERTY(EditDefaultsOnly)
FName Name;
UPROPERTY(BlueprintReadOnly)
float TimeInState;
UPROPERTY(EditDefaultsOnly)
bool bCanTransitionToItself;
UPROPERTY(EditDefaultsOnly, Category="Sub states", meta = (ClampMin = -1))
int32 DefaultSubStateIndex;
UPROPERTY(EditDefaultsOnly, Category="Sub states")
TArray<TSubclassOf<USNGState>> SubStateClasses;
TArray<USNGState*> SubStates;
AActor* Owner;
USNGState* Parent;
// distance from root node
uint32 Depth;
// NOTE(kevin): I don't think this needs to be a map
TMap<FName, FStateTransitionRule> Transitions;
TMap<ESNGEventType, TArray<FEventHandlerRule>> Handlers;
UFUNCTION(BlueprintImplementableEvent)
void OnCreateState();
UFUNCTION(BlueprintImplementableEvent)
void OnEnterState(FSNGEvent Event);
UFUNCTION(BlueprintImplementableEvent)
void OnExitState();
UFUNCTION(BlueprintImplementableEvent)
void OnUpdateState(float DeltaTime);
};

View File

@ -8,7 +8,7 @@ public class SwordNGun : ModuleRules
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "PhysicsCore" });
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "PhysicsCore", "UnrealEd", "AssetTools" });
PrivateDependencyModuleNames.AddRange(new string[] { });

View File

@ -10,7 +10,8 @@
"LoadingPhase": "Default",
"AdditionalDependencies": [
"Engine",
"CoreUObject"
"CoreUObject",
"UnrealEd"
]
}
],