Keep your place in this quest

Log in or sign up for free to subscribe, follow lesson progress, and access more learning content.

随着你的游戏玩法变得更复杂,你最终需要让对象根据它们当前的状态表现出不同的行为。一个敌人可能处于闲置、巡逻、追击、攻击、眩晕或死亡状态。一个门可能是关闭、开启中、开启或锁定状态。一个Boss战可能会经历多个阶段。

这时候,状态机就非常有用了。

image.png

在上图中,你可以看到Crave为新项目创建的默认敌人,它的状态机打开在右侧。我们处在“wander”状态内,该状态拥有内部状态,如“random selector”(初始状态,绿色表示)、“go to random”和“idle”。你可以看到状态之间用箭头表示的转换,每个状态都有自己的逻辑,可以是图中展示的Python代码,也可以是逻辑积木。

状态机帮助你将行为组织成清晰的状态,而不是写一个充满不相关条件的大脚本。

本课你将学到:

  • 什么是状态。
  • 什么是转换。
  • 分层状态机为何有用。
  • Cave的状态机组件如何与实体配合。
  • On EnterOn UpdateOn Exit 如何工作。
  • Python和逻辑积木如何在状态和转换中使用。

目标是先理解思维模型。一旦明白了,编辑器的使用就会更轻松。

什么是状态?

状态描述对象当前正在做什么。

image.png

例如,敌人可能有这些状态:

  • Idle
  • Patrol
  • Chase
  • Attack
  • Dead

通常一时间只会有一个状态控制敌人。如果敌人在Patrol状态,它不会同时运行攻击行为。如果处于Dead状态,则不应该继续追击玩家。

这让游戏设计更易理解,因为每个状态有明确职责。

状态 功能示例
Idle 站立等待
Patrol 在点间移动
Chase 向玩家移动
Attack 播放攻击动画并造成伤害
Dead 禁止移动并播放死亡动画

相比于在一个巨大脚本里问“本帧敌人该做什么?”,你可以问“敌人当前处于哪个状态?”

什么是转换?

转换是让状态机从一个状态切换到另一个状态的规则。

image.png

例如:

  • 敌人经过计时器后由Idle切换到Patrol
  • 玩家被发现后从Patrol切到Chase
  • 敌人接近后从Chase切到Attack
  • 攻击结束后从Attack切回Chase
  • 血量归零时任何状态切到Dead

转换不是行为本身。它是“现在应该切换状态”的决定。

这种分离很重要。Attack状态专注于攻击行为,转换决定何时进入或离开该状态。

分层状态机的优势

Cave采用分层状态机,即状态可以包含子状态。这对共享父级概念的多个行为非常有用。

例如,敌人可以这样组织:

Alive
    Idle
    Patrol
    Chase
    Attack
Dead

IdlePatrolChaseAttack 都属于更大的 Alive 状态。这样结构更清晰,避免重复逻辑。

初学者项目可以从简单的平铺状态机开始,游戏复杂时分层有助于逻辑整洁。

状态机组件

场景中使用状态机,实体需要一个状态机组件

组件连接实体与状态机资产。资产定义状态、转换和逻辑,组件在游戏运行中控制实体执行。

这与Cave中的其他系统类似:

资产 组件
Python Script Python Component 执行它
Animation Animation Component 播放它
State Machine State Machine Component 执行它

实际中,敌人实体拥有状态机组件,该组件运行敌人AI状态机。

状态机资产可能有属性,可通过属性面板查看,也可以在组件中局部修改,支持创建针对不同实体设置不同参数的模块化状态机。

状态事件

每个状态可响应三个重要时刻:

事件 触发时机 常见用途
On Enter 状态开始时 重置计时器、初始化变量、播放动画、选择目标
On Update 状态活动时 移动、检测距离、更新计时器
On Exit 离开状态时 停止特效、清理临时值

举个例子,Attack状态可能如下:

  • On Enter:播放攻击动画。
  • On Update:等待击中时刻或动画回调。
  • On Exit:清除攻击标记。

这样每个状态就像一个小型的自包含行为。

状态中的Python

状态机可以使用Python编写逻辑。

每个状态的Python代码可使用Cave提供的变量,如entityscene,控制拥有该状态机组件的实体。

例如,进入状态时播放声音:

cave.playSound("Enemy Alert", volume=0.8)

或者读取实体属性:

health = entity.properties.get("health", 100)

这保证了状态逻辑紧贴行为。

转换中的Python

转换也可使用Python。转换Python代码中,Cave使用变量result。当resultTrue时,转换发生。例如,切换到Dead状态的转换检查血量:

health = entity.properties.get("health", 100)
result = health <= 0

ChaseAttack的转换可以检查与玩家距离(如果脚本或属性提供此值):

player = scene.get("Player")
playerPos = player.getTransform().getWorldPosition()
entityPos = entity.getTransform().getWorldPosition()

distanceToPlayer = (playerPos - entityPos).length()
result = distanceToPlayer < 2.0

具体数据取决于你的游戏,但思想相同:转换决定该不该切换。

提示: 转换时,来自当前状态定义的所有局部变量都可用。上述例子中,可在来源状态On Enter时缓存玩家和实体变换,减少每帧的数据获取。

状态机中的逻辑积木

状态机也能使用逻辑积木。

当你想通过视觉方式构建行为而非写Python时,这非常实用。很多玩法任务用逻辑积木连接事件、条件和动作都很易懂。

逻辑积木在状态和转换中工作方式与Python代码一样。状态的逻辑积木和转换的逻辑积木都预先初始化,帮助你快速开始。

你可以根据行为需求混合使用Python和逻辑积木。如果一个状态或转换同时拥有两者,则两者都会执行,满足任一条件转换即可触发。

何时使用状态机

对象具有明显行为模式时使用状态机。

好的例子有:

  • 敌人AI。
  • Boss阶段。
  • 门及可交互对象。
  • 任务流程。
  • 剧情进度。
  • 角色能力状态。

对于简单单一动作对象可能不需状态机,比如拾取物仅播放声音后消失,简单Python脚本或逻辑积木即可。

你应该记住的

状态机将游戏玩法组织为状态和转换。

状态描述当前发生的事。转换决定何时切换。On EnterOn UpdateOn Exit 帮助保持状态专注且易读。

简单行为可能脚本足够,多状态行为用状态机更易理解和扩展。