在 Unity 中搭建通用 3C 框架 (Draft)

最后更新于 2023-11-05 1719 次阅读


通用 3C 框架

国庆前开的一个坑,在 Unity 中实现一个基于状态的通用 3C 框架

Character - Controller - Camera

开发施工中,文章可读性为 0 (没法解析UML可读性更低了) ,待整理

@startuml

class InputManager {
-currentInput: InputData
+Update(): void
}

class StateManager {
-currentState: State
+ChangeState(newState: State): void
+OnStateChanged(newState: State)
}

abstract class BaseController {
-inputData: InputData
-currentState: State
+HandleController(): void
}

abstract class PlayerControllerBase {
+HandleController(): void
}

abstract class BaseTranslator {
-rawInput: InputData
+translatedInput: InputData
+TranslateInput(): void
}

class CharacterBase {
-characterState: State
-currentAnimation: Animation
+Update(): void
}

InputManager --> BaseController : provides input data
BaseController --> StateManager : interacts with
BaseController --> BaseTranslator : uses
BaseController --> CharacterBase : controls

@enduml

@startuml

interface IInputModule {
+playerMovement: Vector2
}

class InputManager {
+InitializeInputSystem(): void
+RegisterInputModule(module: IInputModule): void
+UnregisterInputModule(module: IInputModule): void
-inputModules: List<IInputModule>
}

class ThirdPersonCameraInputModule implements IInputModule {
+playerMovement: Vector2 { get }
}

class BasicMovementInputModule implements IInputModule {
+playerMovement: Vector2 { get }
}

class ClimbingInputModule implements IInputModule {
+playerMovement: Vector2 { get }
}

@enduml

@startuml

interface IInputModule {
+playerMovement: Vector2
}

interface IController {
+HandleControl(): void
}

class State {
+Enter(): void
+Exit(): void
-inputModule: IInputModule
-controller: IController
}

class StateManager {
+ChangeState(newState: State): void
-currentState: State
}

class InputManager {
+RegisterInputModule(module: IInputModule): void
+UnregisterInputModule(module: IInputModule): void
}

class ThirdPersonMovementState extends State {
-inputModule: ThirdPersonCameraInputModule
-controller: ThirdPersonMovementController
}

@enduml

接下来我们考虑一个角色State下会需要什么:

  • 状态迁移字典

    • 前往下一个状态的<条件,状态>对
    • 其中状态是一个新的IState资源,而条件是一个被封装好的东西,可能使用ScriptableObject
  • State需要注册的Controller和InputModule

  • 将Controller和InputModule注册至ControllerManager和InputManager的方法,在进入状态时调用

之后我希望有一个视窗类,用于收集并可视化角色所有State的信息和它们之间的迁移关系。

@startuml

interface IInputModule {
+playerMovement: Vector2
}

interface IController {
+HandleControl(): void
}

class Condition {
+IsMet(): bool
}

class State <<ScriptableObject>> {
+Enter(): void
+Exit(): void
+RegisterModules(): void
-inputModule: IInputModule
-controller: IController
-transitions: Dictionary<Condition, State>
}

class StateManager {
+ChangeState(newState: State): void
-currentState: State
}

class InputManager {
+RegisterInputModule(module: IInputModule): void
+UnregisterInputModule(module: IInputModule): void
}

class ControllerManager {
+RegisterController(controller: IController): void
+UnregisterController(controller: IController): void
}

class StateWindow {
+DisplayStates(states: List<State>): void
+DisplayTransitions(transitions: Dictionary<Condition, State>): void
}

State --> Condition : contains
State --> IInputModule : uses
State --> IController : uses
StateManager --> State : uses
InputManager --> IInputModule : uses
ControllerManager --> IController : uses
StateWindow --> State : uses

@enduml

State 伪代码

另外,在StateManager中需要补充一个可用State列表,可能会用于管理特定游戏模式下可用是State

这样避免State超出集合

[CreateAssetMenu]
public class State : ScriptableObject
{
    public IInputModule inputModule;
    public IController controller;
    public List<Transition> transitions;

    public void Enter()
    {
        RegisterModules();
    }

    public void Exit()
    {
        InputManager.UnregisterInputModule(inputModule);
        ControllerManager.UnregisterController(controller);
    }

    public void RegisterModules()
    {
        InputManager.RegisterInputModule(inputModule);
        ControllerManager.RegisterController(controller);
    }

    public State CheckTransitions()
    {
        foreach (var transition in transitions)
        {
            if (transition.condition.IsMet())
            {
                return transition.nextState;
            }
        }
        return this;
    }
}

[System.Serializable]
public class Transition
{
    public Condition condition;
    public State nextState;
}

举个例子,在StateManager中需要补充一个可用State列表,可能会用于管理特定游戏模式下可用的State,以避免状态转移超出集合。
对于这种情况,就可能需要为不同游戏模式设置不同的State转移关系,并对转移关系进行统一管理。这时,State本身管理的更多只是对IInputModule、IController的引用关系。你觉得对于这种情况,将State视为SO是否太累赘了呢?

另外,对于State可能有着更多的子类,比方说移动类型的State其状态转移更多为进入不同状态后移动方式的切换,而对于连招之类的状态,就可能需要处理动作完成的回调,从而切换自身状态迁移条件。对于这种复杂的情景,SO是否有无法较好的处理了?

@startuml

interface IInputModule {
+playerMovement: Vector2
+Initialize(): void
}

interface IController {
+HandleControl(): void
+Initialize(): void
}

abstract class Condition {
+IsMet(): bool
}

class State {
+Enter(): void
+Exit(): void
+RegisterModules(): void
+CheckTransitions(): void
-inputModule: IInputModule
-controller: IController
-transitions: Dictionary<Condition, State>
}

class StateManager {
+ChangeState(newState: State): void
-currentState: State
}

class InputManager {
+RegisterInputModule(module: IInputModule): void
+UnregisterInputModule(module: IInputModule): void
+ActiveInputModule: IInputModule { get; set; }
}

class ControllerManager {
+RegisterController(controller: IController): void
+UnregisterController(controller: IController): void
+ActiveController: IController { get; set; }
}

class StateWindow {
+DisplayStates(states: List<State>): void
+DisplayTransitions(transitions: Dictionary<Condition, State>): void
}

State --> Condition : contains
State --> IInputModule : uses
State --> IController : uses
StateManager --> State : uses
InputManager --> IInputModule : uses
ControllerManager --> IController : uses
StateWindow --> State : uses

@enduml

@startuml

interface IInputModule {
+ProcessInput(input: Vector2): void
}

interface IController {
+HandleControl(): void
}

class Condition {
+IsMet(): bool
}

class State {
+Enter(): void
+Exit(): void
+RegisterModules(characterManager: CharacterManager): void
-inputModule: IInputModule
-controller: IController
-transitions: Dictionary<Condition, State>
}

class StateManager {
+ChangeState(newState: State): void
-currentState: State
}

class InputManager {
+RegisterInputModule(module: IInputModule): void
+UnregisterInputModule(module: IInputModule): void
+CurrentInputModule: IInputModule
}

class ControllerManager {
+RegisterController(controller: IController): void
+UnregisterController(controller: IController): void
-currentController: IController
}

class GameManager {
+ActiveCharacter: CharacterManager
+InputManager : InputManager
}

class CharacterManager {
+HandleInput(input: Vector2): void
-stateManager: StateManager
-inputManager: InputManager
-controllerManager: ControllerManager
}

State --> Condition : contains
State --> IInputModule : uses
State --> IController : uses
StateManager --> State : uses
CharacterManager --> StateManager : uses
CharacterManager --> InputManager : uses
CharacterManager --> ControllerManager : uses
InputManager --> IInputModule : manages
ControllerManager --> IController : manages
GameManager --> CharacterManager : uses
GameManager --> InputManager : uses

@enduml

@startuml

interface IInputModule {
+ProcessInput(): void
}

interface ICharacterController {
+HandleControl(): void
+RegisterInputModule(): void
-inputModule: IInputModule
}

interface ICameraController {
+HandleControl(): void
+RegisterInputModule(): void
-inputModule: IInputModule
}

class Condition {
+IsMet(): bool
}

class State <<ScriptableObject>> {
+Enter(): void
+Exit(): void
+RegisterControllers(characterManager: CharacterManager): void
-characterController: ICharacterController
-cameraController: ICameraController
}

class StateManager {
+ChangeState(newState: State): void
-currentState: State
}

class InputManager {
+RegisterInputModule(module: IInputModule): void
+UnregisterInputModule(module: IInputModule): void
}

class CharacterManager {
-stateManager: StateManager
-inputManager: InputManager
}

State --> Condition : contains
State --> ICharacterController : uses
State --> ICameraController : uses
StateManager --> State : uses
CharacterManager --> StateManager : uses
CharacterManager --> InputManager : uses
ICharacterController --> IInputModule : uses
ICameraController --> IInputModule : uses
InputManager --> IInputModule : manages

@enduml

/Assets
    /Scripts
        /Managers
            GameManager.cs
            CharacterManager.cs
            StateManager.cs
            InputManager.cs
        /States
            State.cs
            ConcreteState1.cs
            ConcreteState2.cs
            ...
        /Conditions
            Condition.cs
            ConcreteCondition1.cs
            ConcreteCondition2.cs
            ...
        /Controllers
            IController.cs
            PlayerController.cs
            EnemyController.cs
            ...
        /InputModules
            IInputModule.cs
            BasicMovementInputModule.cs
            ThirdPersonCameraInputModule.cs
            ...
    /Prefabs
        PlayerPrefab.prefab
        EnemyPrefab.prefab
    /Resources
        /States
            ConcreteState1.asset
            ConcreteState2.asset
            ...

类名 类型 完成 描述
InputActions InputSystem的输入Map,用于接收信息
InputModule 用于翻译输入至Controller可读
InputManager 管理器 管理InputModule的装载与卸载
GameManager 管理器 管理游戏逻辑
CharacterManager 管理器 待定管理器,创建角色的中介层
Character 定义游戏中的角色,通常与Gameplay关联
CharacterBase 角色基类,主要管理状态、Pawn、Camera
StateManager 所属Character的状态管理器,储存状态迁移相关信息,决定了角色可用的状态
CharacterState FSM状态类型,描述角色状态特供。
会储存一系列状态相关Controller
Controller Mono 被State所指定,用于读取玩家输入信号,创建Pawn运动和状态转移
Pawn Mono 用于标识可控制角色的基类。
装载 Pawn 时会自动装载 Animator / CC 等模组,实现角色相关操控和表现接口

基于连招状态表,储存抽象State对象

包含如硬直恢复时间,恢复操作等特殊

开辟镜头和角色状态两组状态机,分别用于管理镜头控制状态和角色操控状态

百无一用是书生
最后更新于 2023-11-05