feat(角色状态机): 重构角色状态系统为基于类的设计

新增通用状态类和职业专属状态类,将状态逻辑从状态机中解耦
添加状态注册机制,支持按职业配置状态
实现基础状态如待机、移动、跳跃、受击等
为剑士职业实现专属攻击状态
补充状态机开发说明文档
This commit is contained in:
2026-04-03 17:11:22 +08:00
parent de522a1e64
commit f64180ebed
25 changed files with 1658 additions and 235 deletions

View File

@@ -44,11 +44,15 @@ public:
const CharacterWorldPosition& GetWorldPosition() const { return motor_.position; }
const CharacterMotor& GetMotor() const { return motor_; }
CharacterMotor& GetMotorMutable() { return motor_; }
CharacterCommandBuffer& GetCommandBufferMutable() { return commandBuffer_; }
float GetLastDeltaTime() const { return lastDeltaTime_; }
const CharacterActionDefinition* FindAction(const std::string& actionId) const;
std::string ResolveAnimationTag(const std::string& preferred,
const std::string& fallback) const;
void PlayAnimationTag(const std::string& actionTag);
void RunStateHook(const std::string& phase,
CharacterStateId stateId,
const CharacterActionDefinition* action);
void SetFacing(int direction);
const character::CharacterConfig* GetConfig() const {

View File

@@ -1,20 +1,28 @@
#pragma once
#include "character/CharacterActionLibrary.h"
#include "character/CharacterDataLoader.h"
#include "character/states/CharacterStateBase.h"
#include <memory>
#include <unordered_map>
namespace frostbite2D {
class CharacterObject;
/// 角色主状态机。输入先变成意图,再由状态机决定当前大状态和动作推进。
class CharacterStateMachine {
public:
using StateRegistry = std::unordered_map<CharacterStateId, std::unique_ptr<ICharacterStateNode>>;
CharacterStateMachine() = default;
void Configure(const character::CharacterConfig& config);
void Reset();
void Update(CharacterObject& owner,
CharacterCommandBuffer& commandBuffer,
const CharacterIntent& intent,
float deltaTime);
void ForceHurt(const CharacterActionDefinition* hurtAction);
void ForceHurt(CharacterObject& owner, const CharacterActionDefinition* hurtAction);
CharacterStateId GetState() const { return currentState_; }
const std::string& GetCurrentActionId() const { return currentActionId_; }
@@ -27,24 +35,21 @@ public:
bool IsMovementLocked() const;
bool CanTurn() const;
void RegisterState(std::unique_ptr<ICharacterStateNode> stateNode);
void ClearRegisteredStates();
ICharacterStateNode* FindStateNode(CharacterStateId stateId) const;
private:
void ChangeState(CharacterObject& owner, CharacterStateId nextState);
void EnterAction(CharacterObject& owner, const CharacterActionDefinition* action);
void UpdateGroundState(CharacterObject& owner,
CharacterCommandBuffer& commandBuffer,
const CharacterIntent& intent);
void UpdateAirState(CharacterObject& owner,
CharacterCommandBuffer& commandBuffer,
const CharacterIntent& intent);
void UpdateActionState(CharacterObject& owner,
CharacterCommandBuffer& commandBuffer,
const CharacterIntent& intent,
float deltaTime);
void UpdateHurtState(CharacterObject& owner, float deltaTime);
void InvokeStateHook(CharacterObject& owner,
const CharacterActionDefinition* action,
const char* phase) const;
bool TryStartAction(CharacterObject& owner,
CharacterCommandBuffer& commandBuffer,
CharacterCommandType commandType,
const std::string& actionId);
bool TryStartRegisteredAction(CharacterStateContext& context);
void ApplyFrameEvents(CharacterObject& owner, int previousFrame, int currentFrame);
CharacterStateId currentState_ = CharacterStateId::Idle;
@@ -54,6 +59,9 @@ private:
float actionFrameProgress_ = 0.0f;
int actionFrame_ = 0;
float landingTimer_ = 0.0f;
StateRegistry stateRegistry_;
friend class CharacterStateBase;
};
} // namespace frostbite2D

View File

@@ -0,0 +1,94 @@
#pragma once
#include "character/CharacterActionTypes.h"
#include <string>
namespace frostbite2D {
class CharacterObject;
class CharacterStateMachine;
struct CharacterActionDefinition;
struct CharacterStateContext {
CharacterObject& owner;
CharacterCommandBuffer& commandBuffer;
const CharacterIntent& intent;
float deltaTime = 0.0f;
};
class ICharacterStateNode {
public:
virtual ~ICharacterStateNode() = default;
virtual CharacterStateId GetId() const = 0;
virtual void OnEnter(CharacterStateMachine& machine,
CharacterStateContext& context,
CharacterStateId previousState) {
(void)machine;
(void)context;
(void)previousState;
}
virtual void OnUpdate(CharacterStateMachine& machine,
CharacterStateContext& context) = 0;
virtual void OnExit(CharacterStateMachine& machine,
CharacterStateContext& context,
CharacterStateId nextState) {
(void)machine;
(void)context;
(void)nextState;
}
};
class ICharacterActionStateNode {
public:
virtual ~ICharacterActionStateNode() = default;
virtual bool TryEnter(CharacterStateMachine& machine,
CharacterStateContext& context) = 0;
};
class CharacterStateBase : public ICharacterStateNode {
public:
explicit CharacterStateBase(CharacterStateId stateId);
~CharacterStateBase() override = default;
CharacterStateId GetId() const override;
protected:
void ChangeState(CharacterStateMachine& machine,
CharacterStateContext& context,
CharacterStateId nextState) const;
void PlayActionById(CharacterObject& owner,
const std::string& actionId,
const std::string& fallbackTag = "rest") const;
void InvokeHook(CharacterStateMachine& machine,
CharacterObject& owner,
const CharacterActionDefinition* action,
const char* phase) const;
bool TryStartAction(CharacterStateMachine& machine,
CharacterStateContext& context,
CharacterCommandType commandType,
const std::string& actionId) const;
bool TryStartRegisteredAction(CharacterStateMachine& machine,
CharacterStateContext& context) const;
void ApplyFrameEvents(CharacterStateMachine& machine,
CharacterObject& owner,
int previousFrame,
int currentFrame) const;
const CharacterActionDefinition* GetCurrentAction(const CharacterStateMachine& machine) const;
const std::string& GetCurrentActionId(const CharacterStateMachine& machine) const;
void SetCurrentAction(CharacterStateMachine& machine,
const CharacterActionDefinition* action,
const std::string& actionId) const;
void ClearCurrentAction(CharacterStateMachine& machine) const;
float& StateTime(CharacterStateMachine& machine) const;
float& ActionFrameProgress(CharacterStateMachine& machine) const;
int& ActionFrame(CharacterStateMachine& machine) const;
float& LandingTimer(CharacterStateMachine& machine) const;
private:
CharacterStateId stateId_ = CharacterStateId::Idle;
};
} // namespace frostbite2D

View File

@@ -0,0 +1,13 @@
#pragma once
#include "character/CharacterDataLoader.h"
namespace frostbite2D {
class CharacterStateMachine;
void RegisterCommonCharacterStates(CharacterStateMachine& machine);
void RegisterJobCharacterStates(CharacterStateMachine& machine,
const character::CharacterConfig& config);
} // namespace frostbite2D

View File

@@ -0,0 +1,15 @@
#pragma once
#include "character/states/CharacterStateBase.h"
namespace frostbite2D {
class DeadState : public CharacterStateBase {
public:
DeadState();
void OnUpdate(CharacterStateMachine& machine,
CharacterStateContext& context) override;
};
} // namespace frostbite2D

View File

@@ -0,0 +1,18 @@
#pragma once
#include "character/states/CharacterStateBase.h"
namespace frostbite2D {
class FallState : public CharacterStateBase {
public:
FallState();
void OnEnter(CharacterStateMachine& machine,
CharacterStateContext& context,
CharacterStateId previousState) override;
void OnUpdate(CharacterStateMachine& machine,
CharacterStateContext& context) override;
};
} // namespace frostbite2D

View File

@@ -0,0 +1,21 @@
#pragma once
#include "character/states/CharacterStateBase.h"
namespace frostbite2D {
class HurtState : public CharacterStateBase {
public:
HurtState();
void OnEnter(CharacterStateMachine& machine,
CharacterStateContext& context,
CharacterStateId previousState) override;
void OnUpdate(CharacterStateMachine& machine,
CharacterStateContext& context) override;
void OnExit(CharacterStateMachine& machine,
CharacterStateContext& context,
CharacterStateId nextState) override;
};
} // namespace frostbite2D

View File

@@ -0,0 +1,18 @@
#pragma once
#include "character/states/CharacterStateBase.h"
namespace frostbite2D {
class IdleState : public CharacterStateBase {
public:
IdleState();
void OnEnter(CharacterStateMachine& machine,
CharacterStateContext& context,
CharacterStateId previousState) override;
void OnUpdate(CharacterStateMachine& machine,
CharacterStateContext& context) override;
};
} // namespace frostbite2D

View File

@@ -0,0 +1,18 @@
#pragma once
#include "character/states/CharacterStateBase.h"
namespace frostbite2D {
class JumpState : public CharacterStateBase {
public:
JumpState();
void OnEnter(CharacterStateMachine& machine,
CharacterStateContext& context,
CharacterStateId previousState) override;
void OnUpdate(CharacterStateMachine& machine,
CharacterStateContext& context) override;
};
} // namespace frostbite2D

View File

@@ -0,0 +1,18 @@
#pragma once
#include "character/states/CharacterStateBase.h"
namespace frostbite2D {
class LandingState : public CharacterStateBase {
public:
LandingState();
void OnEnter(CharacterStateMachine& machine,
CharacterStateContext& context,
CharacterStateId previousState) override;
void OnUpdate(CharacterStateMachine& machine,
CharacterStateContext& context) override;
};
} // namespace frostbite2D

View File

@@ -0,0 +1,18 @@
#pragma once
#include "character/states/CharacterStateBase.h"
namespace frostbite2D {
class MoveState : public CharacterStateBase {
public:
MoveState();
void OnEnter(CharacterStateMachine& machine,
CharacterStateContext& context,
CharacterStateId previousState) override;
void OnUpdate(CharacterStateMachine& machine,
CharacterStateContext& context) override;
};
} // namespace frostbite2D

View File

@@ -0,0 +1,23 @@
#pragma once
#include "character/states/CharacterStateBase.h"
namespace frostbite2D {
class SwordmanAttackState : public CharacterStateBase, public ICharacterActionStateNode {
public:
SwordmanAttackState();
bool TryEnter(CharacterStateMachine& machine,
CharacterStateContext& context) override;
void OnEnter(CharacterStateMachine& machine,
CharacterStateContext& context,
CharacterStateId previousState) override;
void OnUpdate(CharacterStateMachine& machine,
CharacterStateContext& context) override;
void OnExit(CharacterStateMachine& machine,
CharacterStateContext& context,
CharacterStateId nextState) override;
};
} // namespace frostbite2D