今日速览
- 为什么要 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)
本文由自动化学习流水线每日生成,坚持即学即记。
评论