今日速览

  • 为什么要 FSM:角色/敌人行为多了之后,一堆 isJumping_、isDucking_ 布尔标志互相打架,改一个坏三个——这正是《Game Programming Patterns》里经典的「布尔值地狱」。
  • FSM 三要素:一组状态(State)、一组转移(Transition)、触发转移的事件(Event);任意时刻只能处于一个状态,每个状态只关心自己的行为。
  • Godot 落地姿势:enum + match 是最轻量的写法;敌人 AI 经典三态 = IDLE(待机)→ PATROL(巡逻)→ CHASE(追击)。
  • 动画联动:状态切换时同步切 AnimatedSprite2D 的动画(idle/run/attack),用 SpriteFrames 资源管理逐帧素材。
  • 进阶方向:状态多了就升级为分层状态机(HSM)或状态模式(每个状态一个类/节点),但小游戏从 enum + match 起步足够。

核心学习内容

1. 为什么你的敌人会「抽风」——布尔值地狱

假设写一个敌人:没发现玩家时巡逻,发现了就追。直觉写法是两个布尔标志 is_patrolling、is_chasing 加一堆 if。很快你会遇到:追到一半玩家跑远,敌人站在原地发呆;切状态时动画没换过来;攻击冷却期间行为错乱。每次加新行为都要回去改旧代码——这就是《Game Programming Patterns》里女主角例子(跳跃/下蹲/俯冲连环 bug)的翻版。FSM 的核心价值不是「省代码」,而是把「角色在干什么」变成唯一确定的值,让 bug 无处藏身。

2. FSM 三要素怎么理解

  • 状态(State):角色当前唯一在做的事,如 IDLE / PATROL / CHASE / ATTACK。
  • 事件(Event):输入或世界变化,如「玩家进入视野范围」「到达巡逻点」。
  • 转移(Transition):某状态下收到某事件,切换到哪个新状态。没有定义转移的事件,直接忽略。

画一张流程图(方框=状态,箭头=转移),就是完整的 FSM。这也是让 DeepSeek 帮你写 AI 时最好的沟通方式——先画图再写码。

3. Godot 实操:巡逻→追击→返回

一个 2D 敌人的最小 FSM 骨架(CharacterBody2D + AnimatedSprite2D + 视野检测 Area2D):

enum State { IDLE, PATROL, CHASE }
var state: State = State.IDLE

func _physics_process(delta: float) -> void:
    match state:
        State.IDLE:
            # 站着不动,几秒后转巡逻;看到玩家转追击
            pass
        State.PATROL:
            # 沿巡逻点来回走,碰到边缘/墙掉头;看到玩家转追击
            pass
        State.CHASE:
            # 朝玩家移动,超过追击距离就转回 PATROL
            pass
    _update_animation()

func _update_animation() -> void:
    match state:
        State.IDLE: $AnimatedSprite2D.play("idle")
        State.PATROL: $AnimatedSprite2D.play("run")
        State.CHASE: $AnimatedSprite2D.play("run")  # 可加速帧率或加拖影

要点:转移只发生在状态分支内部(如 CHASE 里判断 distance > chase_range 才回 PATROL),状态与状态之间不互相乱改,动画在统一出口更新。

4. 什么时候该升级写法

敌人超过 5 个状态、或状态要嵌套(如 CHASE 下再分「绕路/直线冲」)时,再把每个状态拆成独立类或节点(状态模式)。对第一款游戏来说,enum + match 足够撑到上架,别过早抽象。

🎯 DeepSeek 提示词

提示词 1:生成敌人巡逻+追击 FSM 脚本(适用:想在 Godot 里快速要一个可跑的敌人 AI)

你是资深 Godot 4 游戏开发者。请用 GDScript 为 2D 横版游戏写一个敌人的完整脚本,使用有限状态机(enum + match 实现),包含三个状态:IDLE(待机 2 秒)、PATROL(在起点和终点之间往返巡逻,到达边缘或撞墙掉头)、CHASE(玩家进入 300 像素范围后追击,超过 500 像素放弃并返回巡逻)。
要求:
1. 基于 CharacterBody2D,节点结构为 AnimatedSprite2D(动画名 idle/run/attack)和 Area2D 视野检测,用 @onready 引用。
2. 状态转移只允许:IDLE→PATROL、PATROL→CHASE、CHASE→PATROL、PATROL→IDLE。
3. 每个状态用独立函数 _idle/_patrol/_chase 实现,_physics_process 里用 match 分发。
4. 状态切换时播放对应动画,写清楚移动速度、重力、速度翻转(flip_h)处理。
5. 输出完整可直接粘贴到 enemy.gd 的代码,并注释每个转移条件。

提示词 2:把 if/布尔标志代码重构为 FSM(适用:已有乱成一团的敌人/角色代码,让 DeepSeek 救火)

你是 Godot 4 重构专家。下面这段 GDScript 用多个布尔标志(is_patrolling、is_chasing、is_attacking)和大量 if 控制敌人行为,经常出现状态错乱和动画不同步的 bug。
请把它重构为 enum + match 的有限状态机:
1. 先列出你识别出的所有状态和转移条件,画成文字版状态转移表(当前状态 | 事件 | 新状态)。
2. 再输出重构后的完整脚本,保持原有移动速度和攻击逻辑不变。
3. 指出原代码里 2-3 处导致 bug 的具体写法(如互相矛盾的标志组合)。
4. 用注释标明每个状态的进入(enter)和退出(exit)行为,动画切换放在状态进入处。
代码:
[粘贴你的代码]

提示词 3:让 DeepSeek 设计敌人行为表(适用:设计阶段先定 AI 行为,再写代码)

你是游戏 AI 设计师。我要做一个 2D 平台跳跃游戏的"史莱姆"敌人,请为它设计一份有限状态机行为表,输出 Markdown 表格,列:状态名、行为描述、进入条件(来自哪个状态/什么事件)、退出条件(转到哪个状态)、对应动画名。
要求:
1. 状态不超过 6 个,包含待机、巡逻、追击、攻击、受伤、死亡。
2. 追击状态要有"丢失目标后追查 3 秒再放弃"的机制。
3. 行为要符合"史莱姆"的呆萌感:移动慢、攻击前摇明显(给玩家反应时间)。
4. 最后附一段 10 行以内的 GDScript 伪代码骨架(enum + match)。

学习心得

对「用 DeepSeek 做一款 Steam 游戏」来说,FSM 是性价比极高的第一课:它是敌人 AI 的骨架,也是最适合让 AI 生成代码的结构化模式——因为状态和转移天然就是表格/枚举,DeepSeek 很容易理解并生成不出错的代码。先自己画好状态转移图(10 分钟),再把它贴进提示词里,DeepSeek 生成的质量会远超”帮我写个敌人”这种空泛指令。今天的三条提示词覆盖了「从零生成 → 重构救火 → 设计先行」三个场景,之后任何角色/敌人系统都能直接套用。

明日关注

  • 给敌人加攻击与受伤状态:碰撞伤害、受击闪白( modulate 闪烁)、击退( knockback)
  • 用 Area2D 做视野/听觉检测:圆形范围 vs 扇形范围怎么选
  • 复习 AnimatedSprite2D 的 SpriteFrames:帧率(FPS)、循环与单次播放(attack 播完自动回 idle)

本文由自动化学习流水线每日生成,坚持即学即记。