前言
“车万空寂花”最开始只是想做一个仿东方 Project 的弹幕游戏。项目往后做,角色与难度选择、完整关卡、Boss、分数记录、PC 和移动端控制都加了进来,原本单独运行的弹幕代码也要和关卡、输入、资源、UI 接起来。
最近正好在整理这个项目的资料,于是先把目前已经跑通的流程写下来。本文主要看整体结构,后面两篇再分别展开弹幕管理和关卡加载。
项目源码:qian488/Danmaku;试玩页面:itch.io。
这个系列
- 本文:从可玩成品倒推整体架构
- 集中更新、对象池与碰撞判定
- 时间轴关卡、资源预热与加载流程
这个项目目前做到了什么
项目当前的完整游戏循环如下:
主菜单选择角色、章节和难度
↓
加载关卡资源并预热高频对象
↓
玩家移动、射击、擦弹、收集道具
↓
时间轴生成敌人、波次和 Boss
↓
胜利或失败结算,记录最高分
为了支撑这条循环,项目需要处理六项工作:推进关卡;生成、更新和回收弹幕;把主菜单选择传入战斗场景;准备首次使用的贴图、音效和预制体;统一键盘与触摸输入;集中管理玩法数值。下面的五条链路说明这些工作在项目中的归属。
我现在整理出的五条运行链路
1. 一局游戏的选择数据
主菜单选择角色、章节和难度后,将本局需要的数据写入 DanmakuRunSettings。战斗场景读取这些会话数据,再创建对应玩家、加载关卡并应用难度倍率。
DanmakuRunSettings 只保存本局选择。默认数值由配置提供,历史成绩由持久化模块保存。三类数据分开后,各自的创建和清理时机更清楚。
2. 键盘和触摸输入
PlayerController 只消费统一的移动、低速、射击和炸弹意图。键盘与触摸组件分别采集输入,再转换成相同语义。
键盘 / 触摸
↓
移动向量、低速状态、炸弹请求
↓
PlayerController
↓
移动、射击、受伤和 Bomb
这样做的好处是,修改虚拟摇杆不会影响受伤逻辑,增加手柄输入也不需要复制玩家控制器。
3. 关卡时间轴
StageRunner 推进关卡时间,读取 StageData,在时间到达时触发敌人波次、弹幕或 Boss。具体怎样发射,则交给 shooter 或 pattern。
时间轴提供任务触发时间,发射器计算弹幕参数,BulletManager 更新生成后的子弹。调整关卡节奏时主要修改任务时间与 pattern 参数。
4. 弹幕的生成与回收
敌弹进入 BulletManager 后,由统一的活跃列表更新位置、做碰撞和判断越界。对象从 GameObjectPool 取得,结束后回池,而不是频繁 Instantiate/Destroy。
集中管理后,当前活跃弹幕、判定顺序和回收规则都可以从 BulletManager 追踪。排查未消失的子弹时,检查范围也随之收敛。
5. 进入战斗前的加载
主菜单与战斗场景分别维护预加载清单。DanmakuLauncher 和关内控制器异步加载资源、更新进度,并在开战前预热核心对象池和子弹贴图索引。
与其让第一次开火随机卡顿,不如在玩家已经接受等待的 Loading 阶段完成资源准备。
数值放在哪里
弹幕游戏的手感来自许多相互影响的数值:
- 玩家高速与低速移动速度
- 受击半径与擦弹半径
- 子弹速度和难度倍率
- 火力档位与升级经验
- 敌人生命、Boss 倍率和掉落权重
- 击坠、擦弹、时间奖励的分数组成
项目用 DanmakuGameplayBalance 一类的配置承载这些调节项。同一套平衡规则因此有明确入口,关卡逻辑也能读取配置,减少散落在脚本中的魔法数字。
判断一个字段是否应该进入配置,可以问三个问题:
- 它是否可能在不改玩法代码的情况下调整?
- 它是否需要在多个系统中保持一致?
- 它是否属于会被反复调整的设计规则?
三者满足其一,就值得考虑从代码常量中抽离。
框架和玩法是怎么接起来的
工程中还有 UI、音频、场景、资源、存档和对象池等通用模块。我采用以下边界:
- 通用层提供加载、回收、页面切换和数据保存能力;
- 弹幕层定义玩家、敌人、子弹、关卡和计分语义;
- 入口负责把两者装配起来。
例如 BulletManager 可以使用通用对象池,但对象池不需要理解“擦弹”;UI 框架可以打开结算窗口,但不负责计算最终分数。
目前还没解决好的地方
项目已经能完成一局游戏,但不代表架构已经到终点:
- 资源仍以
Resources为主,依赖和卸载粒度有限; - GameObject 弹幕适合当前规模,再向数千级扩展会受 Transform 与 Renderer 成本限制;
BulletManager集中管理后容易继续膨胀,需要警惕变成新的 God Object;- 关卡配置需要更直观的编辑器预览,单看时间数字不利于调节节奏;
- 公开仓库还缺少稳定的性能基准,现阶段只能说明代码采用了对象池和集中更新,尚无可公开的性能数据。
下一步应先建立可重复的性能场景,再根据测量结果决定是否迁移 Addressables、数据导向弹幕或批量渲染。
写在最后
重新整理一遍以后,我觉得这个项目目前有四个比较清楚的点:
- 完整游玩闭环决定了系统的拆分范围。
- 时间轴管时机,发射器管模式,BulletManager 管高频对象生命周期。
- 平台输入、游戏规则和通用框架通过明确接口连接。
- 对象池和预加载将一部分运行时成本转移到可观察、可控制的流程中。
下一篇接着看 BulletManager。这部分代码更具体,包括活跃弹幕列表、对象池状态重置、受击和擦弹判定。