← 跨平台应用开发 · 第 11 篇

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

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

学完这一篇,你能做到

完成可重开的2D收集练习,验收运动速率、一次计分、暂停停止和旧场景事件不污染新局。

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

案例使用自制几何图形,不沿用官方入门游戏的角色、关卡或资源。第一版做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调度、碰撞、物理积分或真实输入。

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仅验证恒定速率,场景、碰撞和重开由实际引擎运行确认;接着才增加音效和关卡。

资料来源与延伸阅读

资料核验:2026-11-10。本文为原创工程讲解,教学示例不代表线上实测。