WAYNETS.ORG

Game and Program

第三人称射击游戏

作者:

发表于

Context Polling System Post-Processing Renderer

EventBus设计模式

EventBus是一种解耦的通信机制,它的核心思想是:

发布者(Publisher)只把事件发送到总线,不需要知道谁会接收;订阅者(Subscriber)向总线注册感兴趣的事件回调,总线负责把事件分发给所有订阅者。

优点:

  • 强烈解耦:发布者不依赖订阅者,便于模块化开发。
  • 易扩展:增加新订阅者无需改发布者代码。
  • 适合事件驱动/松耦合架构:UI、系统事件(音效、数据统计、AI信号)等很方便。

缺点:

  • 可追踪性差:调用链被隐藏,调试困难(谁在什么时候发布/接收到?)。
  • 内存泄漏:静态事件/未取消订阅会导致对象无法回收(特别容易在 Unity 中出现)。
  • 隐式控制流:业务逻辑可能散落各地,读代码很难理解程序流程(容易变成“事件地狱”)。

有限状态机(FSM)架构详解

有限状态机是一个抽象模型,它有:

  • 状态集合(比如 Idle、Patrol、Attack)
  • 状态切换条件(什么时候从一个状态切换到另一个状态)
  • 行为逻辑(每个状态具体的行为)

在游戏AI里,FSM就是根据当前状态和条件执行对应行为,并根据条件切换状态。

有限状态机的工作流程:

  • 初始化状态机,指定初始状态(比如IdleState)。
  • 在游戏循环里(比如Update函数中)调用当前状态的Update方法,执行状态逻辑。
  • 状态逻辑内部根据条件判断,调用stateMachine.ChangeState()切换状态。
  • 切换时自动调用ExitEnter,完成切换准备。

实现有限状态机的步骤:

  • 定义状态基类(EnemyState),抽象公共行为和接口
  • 实现状态机类(EnemyStateMachine),管理当前状态,处理切换
  • 实现具体状态子类,覆盖Enter、Update、Exit方法写行为逻辑
  • 敌人主体(Enemy)初始化状态机,并每帧调用当前状态更新
  • 状态逻辑里根据条件调用ChangeState切换状态

EnemyStateMachine类

职责:

  • 管理当前状态currentState,负责切换状态。
  • Initilaize():设置初始状态,并调用Enter()执行进入逻辑。
  • ChangeState():切换状态时,先调用旧状态的Exit(),再赋新状态并调用新状态的Enter()
public class EnemyStateMachine
{
    public EnemyState currentState { get; private set; }

    public void Initialize(EnemyState startState)
    {
        currentState = startState;
        currentState.Enter(); // 进入初始状态
    }

    public void ChangeState(EnemyState newState)
    {
        currentState.Exit();  // 退出当前状态
        currentState = newState;
        currentState.Enter(); // 进入新状态
    }
}

EnemyState类

职责:

  • 抽象了一个状态的行为,包括三个状态:Enter / Update / Exit。
  • 保存了引用的敌人对象和状态机,方便访问数据和切换状态。
  • 绑定动画参数名,方便状态对应动画切换。
  • 设计了一个动画触发器标志(triggerCalled),方便动画事件通信。
public class EnemyState
{
    protected Enemy enemyBase;           // 持有敌人对象,方便访问敌人数据和行为
    protected EnemyStateMachine stateMachine;  // 状态机对象,方便切换状态

    protected string animBoolName;      // 动画参数名称,用于控制动画切换
    protected float stateTimer;         // 状态计时器,用于状态时间控制

    protected bool triggerCalled;       // 用于动画触发事件标记

    public EnemyState(Enemy enemyBase, EnemyStateMachine stateMachine, string animBoolName)
    {
        this.enemyBase = enemyBase;
        this.stateMachine = stateMachine;
        this.animBoolName = animBoolName;
    }

    public virtual void Enter()
    {
        enemyBase.animator.SetBool(animBoolName, true);  // 进入状态时,开启对应动画参数
        triggerCalled = false;
    }

    public virtual void Update()
    {
        stateTimer -= Time.deltaTime;  // 状态持续时间递减
    }

    public virtual void Exit()
    {
        enemyBase.animator.SetBool(animBoolName, false); // 退出状态时关闭动画参数
    }

    public void AnimationTrigger() => triggerCalled = true; // 动画事件触发
}

具体状态类的实现:IdleState

public class IdleState_Range : EnemyState
{
    private Enemy_Range enemy;  // 持有具体敌人类型引用,方便调用远程敌人特有方法和属性

    // 构造函数:传入敌人基类、状态机和动画参数名称
    public IdleState_Range(Enemy enemyBase, EnemyStateMachine stateMachine, string animBoolName) 
        : base(enemyBase, stateMachine, animBoolName)
    {
        enemy = enemyBase as Enemy_Range; // 转型为远程敌人类型
    }

    public override void Enter()
    {
        base.Enter();

        // 启用IK,具体控制是否启用手部或头部IK,这里启用左手IK,禁用右手IK
        enemy.visuals.EnableIK(true, false);

        // 随机播放一个Idle动画变体(0或1)
        enemy.animator.SetFloat("IdleAnimIndex", Random.Range(0, 2));

        // 如果武器是手枪,则禁用IK(可能因为手枪动作不需要IK)
        if (enemy.weaponType == Enemy_RangeWeaponType.Pistol)
        {
            enemy.visuals.EnableIK(false, false);
        }

        // 设置计时器,从Enemy_Range的idleTime获取,用来控制多久后切换状态
        stateTimer = enemy.idleTime; 
    }

    public override void Update()
    {
        base.Update();

        // 计时器倒计时结束,切换到移动状态
        if (stateTimer < 0)
        {
            stateMachine.ChangeState(enemy.moveState);
        }
    }
}

行为树

行为树是一种组织AI行为的树形结构,每个节点代表一个行为单元或逻辑控制点,树从根节点开始,遍历执行子节点,从而实现复杂行为决策。

相比有限状态机(FSM),行为树能更灵活地组织复杂行为,易扩展,调试清晰。

Leave a comment