谁负责真正的游戏结果,谁只负责把结果表现出来?
这次从一次真实的开火入手:按键进入游戏、关卡判断射线与墙、目标扣血、镜头震动、粒子发射。下面的调用追踪由上方小游戏的实际操作生成,不是额外编排的时序模拟器。
① 开火:哪些是游戏事实,哪些只是反馈?
📱 手机:按住左右按钮移动,点击「开火」;也可以直接轻点场景开火。
电脑:A/D 或 ←/→ 移动,空格、F 或点击游戏场景开火。
只关闭表现,不改变规则
关闭后镜头仍然可以跟随角色,只是不再添加开火震动。
关闭后不发射粒子;子弹是否撞墙、目标扣多少血,仍然由 Level/Entity 决定。
推荐:先在关卡 A 开一枪命中目标;关闭两个反馈再开一枪,看看伤害是否仍然发生。接着切关卡 B,观察墙挡住射线时为什么不会扣血。
刚才那一枪经过了哪些系统?
每按一次开火,这里自动重建调用追踪。它读取刚才那次命中判定、血量变化、镜头反馈和粒子申请数量。
先在上方游戏点击「开火」,这里就会显示“输入 → Game → Level → Entity → Camera → Fx → Render”的实际处理结果。
注意:“Game 调度”是课程构造的显式调用链。原版 Game 持有系统并调度更新,但没有通用的统一事件总线,也不是所有开火效果都由 Game 转发。
切换关卡:谁清理旧东西,谁创建新东西?
关卡 B 初始有一道墙,它会挡住射线。切换时课件模拟原版 Game.startLevel() 的主要清理职责,并记录真实发生的顺序。
点击上方「打开 / 关闭墙」:关卡会先将地图标记为 dirty,下一帧才重新构建绘制数据,碰撞规则则立即生效。
本课关卡数据是手写的教学网格;原版 Level 使用 LDtk 的 World_Level、MarkerMap,并在 postUpdate() 中按需重建关卡绘制。
性能审计:系统拆开后,成本在哪里?
这些数字来自本课实际运行,原版的容量与实现单独标注。
原版 Fx 和 Level 的真实成本结构
Fx:Fx.new() 创建容量为 2048 的 ParticlePool,以及前景/背景 × Normal/Add 四组 SpriteBatch,Fx.update() 会调用 pool.update(game.tmod)。这意味着表现系统既要考虑粒子生命周期,也要考虑批次与透明覆盖。
Level:invalidate() 将关卡标记为需要重绘;postUpdate() 才调用 render(),避免每次查询碰撞都重建地图。但原版的 render() 会移除旧子对象并重新创建 TileGroup,因此高频 invalidate 仍然不免费。
Camera:每帧根据焦点、边界、缩放、震动更新 scroller。镜头效果不应通过修改 Entity 的物理坐标来实现,否则可能污染碰撞、AI 和联网同步。
架构代价:系统拆分也可能带来调用与状态同步成本,不能只从类的数量判断性能;应通过 profiler 分别测量逻辑更新、地图重建、批量粒子和过度绘制。
原版 Haxe:职责归属到底是什么?
源文件均来自 Deepnight 的固定 GameBase 快照,非本课教学代码。
Game.hx · Level.hx · Camera.hx · Fx.hx · 详细笔记
持有 Level、Camera、Fx、Hud、scroller,启动/销毁关卡并分发 Entity 更新。
保存 LDtk 关卡、碰撞查询、格子标记;按需重建地形显示。
跟随 Entity、限制地图边界、视口坐标变换、缩放和冲击反馈。
管理粒子池、SpriteBatch 和各类效果分配;明确表现与游戏事实的边界。
Game.startLevel() 的真实顺序
// 原版主要步骤的职责摘录(教学重排)
if (level != null) level.destroy();
fx.clear();
for (e in Entity.ALL) e.destroy();
garbageCollectEntities();
level = new Level(l);
camera.centerOnTarget();
hud.onLevelStart();
dn.Process.resizeAll();这解释了为什么关卡切换属于 Game 编排,而不是让 Camera 或 Fx 自己销毁整张地图。原版同时有注释提示:销毁所有实体是模板做法,后续可以按项目要求保留玩家。
原版 Level 如何处理碰撞与重绘?
public inline function hasCollision(cx, cy) : Bool {
return !isValid(cx,cy)
? true
: marks.has(M_Coll_Wall, cx,cy);
}
public inline function invalidate() {
invalidated = true;
}碰撞依据数据/MarkerMap,而不是去询问 Camera 是否看得见,也不是根据粒子是否播放来判定。
四个系统是不是严格互相不依赖?
不是。真实源码中 Camera 会读取 Game.level 和 scroller;Fx 会引用 Game.scroller,也能通过 level.hasCollision 查询粒子碰撞;Game 则直接持有并编排它们。所谓“系统边界”不是绝对隔离,而是弄清谁拥有权威状态,谁负责查询,谁负责表现,谁负责生命周期。
三道自测:新功能应该放哪儿?
直接点击答案,看是否能根据职责定位代码。
学习进度仅保存在本机浏览器。