# 从游戏循环理解 Godot 的场景与状态

原文: https://yishto.com/learn/application-platforms/godot-small-game-loop/

作者: [Yishto](https://yishto.com/about/)

用收集方块练习组织Godot场景、输入动作、物理更新和游戏状态，验证暂停、重复碰撞与重开清理。

首次发布: 2026-11-10
内容更新: 2026-11-10

收集方块练习有一个可移动角色、几枚目标和分数。按下重开后，角色回到起点，目标重新出现，分数清零。这个小范围足以观察游戏循环、场景实例和碰撞事件，而不用先准备复杂美术。

案例使用自制几何图形，不沿用官方入门游戏的角色、关卡或资源。第一版做2D俯视移动，不增加敌人、排行榜和联网。所有数值都是教学设置，不能用于评估真实游戏的手感或性能。

## 场景树与一局游戏的数据

Godot 的场景由节点组成，保存后可以重复实例化。主场景管理一局游戏，角色场景处理移动，目标场景发出被收集事件，UI 场景展示分数和按钮。场景组合让职责明确，节点名却不是业务唯一身份；多枚目标需要独立ID。

游戏状态包括 playing、paused 和 ended。分数和剩余目标归本局控制器拥有，角色和UI只发送或接收事件。点击重开创建新局标识，旧节点的晚到信号必须被忽略，避免上一局目标给新局加分。

目标记录有未收集与已收集状态。碰撞可能重复触发，先检查并标记已收集，再发计分事件和安排节点移除。节点从树中释放的时机不一定与信号触发同一瞬间，不能只依赖“稍后删除”阻止重复计分。

## 输入与移动属于不同步骤

在项目输入映射中定义上、下、左、右和暂停动作，将键盘绑定到动作。游戏代码读取动作而不绑定某个固定按键字符串，以后加入手柄或触屏时可以复用运动意图。

逐帧 `_process` 的间隔会变化，物理更新有其固定调度。按速率更新位移要考虑 delta，但使用物理移动 API 时也要遵守其单位约定。不能把需要速度的参数先乘 delta 后再由API处理一次时间，造成不同帧率速度变化。

碰撞检测交给选定物理节点和API，视觉图形与碰撞形状分别设置。可见方块不意味着具有碰撞范围。对斜向输入规范化或限制速度，避免两轴叠加让对角线移动更快。

## 在编辑器建立最小工程

用 Godot 4 创建2D工程，记录具体版本与渲染选项。创建主场景，加入角色、目标容器和UI。角色可用 CharacterBody2D 配合可见图形和 CollisionShape2D；目标用 Area2D 配合图形与形状，碰撞层和 mask 明确指向角色。

先放一枚目标，配置输入动作，通过物理更新设置角色速度并调用对应运动方法。使用安装版本的文档确认 `move_and_slide` 等接口语义。运行当前场景检查动作与碰撞，再将主场景设为项目入口，防止只测试某个子场景。

随后建立目标被收集信号，连接到本局控制器。控制器维护剩余ID集合、分数和结束判断，UI根据状态更新。最后加入暂停与重开按钮，重开清除本局节点、重建目标并重置角色。本文没有运行Godot工程，读者需要按步骤实际编辑、运行和调试。

## 用离线模型检查速率计算

下面Python将恒定速度乘以每段时间并累加。两种时间拆分都有一秒总时长，因此位移相同；暂停状态不移动。这只是数值模型，不包含Godot调度、碰撞、物理积分或真实输入。

```python
def distance(velocity, steps):
    return sum(velocity*delta for delta in steps)
assert abs(distance(10, [0.1]*10)-10) < 1e-9
assert abs(distance(10, [0.25]*4)-10) < 1e-9
state = "paused"
def tick(position, delta):
    return position if state != "playing" else position + 10*delta
assert tick(0, 0.1) == 0
print(round(distance(10, [0.1]*10), 2))
```

实际输出 `10.0`。断言分别检查十段0.1秒和四段0.25秒的结果，也检查暂停时位置不变。浮点比较有容差，不能用这个例子证明复杂碰撞在所有帧率下一致。

真实工程分别测试物理运动与UI动画。改变窗口负载时角色的目标速度应仍符合约定，但具体碰撞和帧调度可能不同。需要性能结论时记录场景、设备、引擎版本和实际测量，不能把模型位移当实测帧率。

## 暂停、结束和重开

暂停后决定哪些节点继续工作：游戏运动停止，菜单和继续按钮仍可操作。按节点 process mode 与安装版本的暂停机制配置，不要仅隐藏角色。结束状态拒绝再次收集，显示结果并提供重开。

重开先让旧局停止接收事件，再移除旧节点和创建新局。重置信号连接、分数、计时与剩余目标，检查是否重复连接导致一次碰撞加两分。引用已释放节点时应安全失败，避免缓存节点引用跨局使用。

输入状态也需要考虑。暂停时仍按住方向，恢复后角色是否继续移动是产品选择，要实际验证。目标生成位置不应与起点重叠，否则刚开局就可能触发收集；加入确定的测试布局便于复现。

## 从几何资源扩展到2D与3D

像素风需要处理分辨率、过滤和缩放，普通2D也要关注坐标与摄像机。改为3D需要重新设计摄像机、光照、空间输入与碰撞，不是替换图片就自动完成。先使规则可玩，再选择表现形式。

使用AI生成资源时检查来源与使用权，生成代码也需调试。碰撞形状应根据玩法确定，不必逐像素复制视觉轮廓。导出前确认资源路径与主场景，开发编辑器能运行不代表安装产物一定包含全部内容。


得分规则可以先写为目标ID集合的状态转换：只有playing且目标未收集时才能移除ID并增加分数。UI按钮不直接改目标节点，统一让控制器执行。这样键盘重开与按钮重开共用同一路径，也便于检查重复事件。

测试场景还应包含边界墙与不同目标位置，确认角色不会穿出可见区域。窗口缩放时UI保持可读，暂停按钮不被场景图形遮挡。导出前检查输入映射、碰撞层和资源导入设置，尤其是大小写路径差异。使用确定布局时记录坐标，便于另一位读者复现一次收集和重开；随机布局需要固定测试种子或保存实际生成结果。

## 练习：让重开可以重复验收

放三枚不同ID目标，一局每枚只加一分，全部收集后结束。分别测试站在目标上、暂停后输入、连续点击重开和旧信号晚到，分数不能重复或跨局累积。

改变时间步模型，再在Godot中检查实际运动。导出到目标桌面环境测试入口、输入与资源。验收记录说明Python仅验证恒定速率，场景、碰撞和重开由实际引擎运行确认；接着才增加音效和关卡。

## 资料来源与延伸阅读

- [Godot scenes and nodes](https://docs.godotengine.org/en/stable/getting_started/step_by_step/nodes_and_scenes.html)
- [A first 2D game](https://docs.godotengine.org/en/stable/getting_started/first_2d_game/index.html)

资料核验：2026-11-10。教学示例不代表线上实测。

## 相关文章

- [从查找问题理解算法与数据结构](https://yishto.com/learn/computing/algorithms-and-data-structures/)
- [为产品准备图像、动画与交互素材](https://yishto.com/learn/web-interface/graphics-assets-and-motion/)
