Phaser 横版射击项目架构草案(轻量剧情版)
目标:做一个 战斗像《爆枪英雄》、带一点 gal 味剧情展示、并且 方便做模组/创意工坊 的项目。
先把一句话说死:
这不是 galgame 引擎。
这只是一个 2D 横版射击游戏,额外带一个 轻量剧情展示层。
别为了“像 galgame”把系统设计得像税法,那纯属给自己找麻烦。
一、项目定位
项目应该定成:
Phaser 3 驱动的 2D 横版射击 + 轻量剧情展示 UI + 局外商店 + 模组化内容结构
核心原则还是那句:
代码是引擎,内容是包。
也就是说:
- 代码负责跑规则
- 数据负责定义剧情、角色、武器、地图、商店内容
- 模组作者尽量只碰内容,不碰底层代码
二、剧情系统重新定义
不是 VN 引擎,只是剧情展示层
你真正要的东西非常简单:
- 背景图
- 左 / 中 / 右 三个固定立绘槽位
- 某个人说话时,该角色高亮
- 其他角色自动变暗
- 立绘可以切换
- 底部名字框
- 底部对话框
- 点击继续
- 可选分支
这就够了。
没必要一开始做:
- 自由角色移动
- 一堆复杂转场脚本
- 超细粒度 pose / expression 状态机
- 真正意义上的 galgame runtime
那些都不是现在的重点。
三、剧情展示层结构
固定槽位
剧情界面只做 3 个预设槽位:
leftcenterright
每个槽位只需要维护这些状态:
- 当前角色
characterId - 当前立绘
portrait - 是否显示
- 是否高亮
说话时的高亮规则
当脚本里出现:
{ "type": "say", "speaker": "anon", "text": "前面有动静。" }运行时自动做:
anon所在槽位高亮- 其他已显示角色变暗
这个规则应该由 渲染层自动处理,不要让脚本作者手动控制明暗。
脚本负责内容,渲染负责表现。别混在一起。
四、简化后的剧情脚本格式
第一版脚本只保留这几个指令:
bg:切背景char:在指定槽位显示/切换角色立绘say:说话hide:隐藏槽位choice:选项jump:跳转到另一段剧情battle:进入战斗
就这些。够用了。
示例
{
"id": "intro_001",
"steps": [
{ "type": "bg", "image": "street_day" },
{ "type": "char", "slot": "left", "character": "anon", "portrait": "normal" },
{ "type": "char", "slot": "right", "character": "sakiko", "portrait": "calm" },
{ "type": "say", "speaker": "anon", "text": "前面不太对劲。" },
{ "type": "say", "speaker": "sakiko", "text": "先别冲,观察一下。" },
{ "type": "char", "slot": "right", "character": "sakiko", "portrait": "serious" },
{ "type": "say", "speaker": "sakiko", "text": "要么先进商店补给,要么直接开打。" },
{
"type": "choice",
"options": [
{ "text": "去商店", "jump": "shop_intro_001" },
{ "text": "直接战斗", "jump": "battle_intro_001" }
]
}
]
}为什么这样更对
因为这格式:
- 你自己看得懂
- mod 作者一眼会写
- 后面还能扩展
- 现在不会过度设计
这才叫实用。
五、主角设定
当前先把两个核心角色定下来:
anon→ 爱音sakiko→ 祥子
推荐角色定位
爱音(Anon)
- 更主动
- 更冲一点
- 更像玩家代入入口
祥子(Sakiko)
- 更冷静
- 更收着一点
- 更像负责判断和压节奏的人
这样对话会有张力,不会两个人说话一个味。
六、角色数据格式
角色配置也不要搞得太复杂。
anon.json
{
"id": "anon",
"name": "爱音",
"portraits": {
"normal": "portraits/anon/normal.png",
"happy": "portraits/anon/happy.png",
"serious": "portraits/anon/serious.png"
}
}sakiko.json
{
"id": "sakiko",
"name": "祥子",
"portraits": {
"normal": "portraits/sakiko/normal.png",
"calm": "portraits/sakiko/calm.png",
"serious": "portraits/sakiko/serious.png"
}
}就这样已经够了。
别一开始就上 pose -> expression -> variation 三层嵌套,不然文档马上变臭。
七、战斗和剧情的关系
项目主循环建议是:
MainMenu -> DialogueScene -> BattleScene -> ShopScene -> DialogueScene -> BattleScene意思很直接:
DialogueScene负责讲事BattleScene负责打架ShopScene负责成长
三块拼起来,游戏就完整了。
八、商店系统:只做局外商店
这个决定是对的。
第一版只做局外商店,不做局内商店。
因为局外商店:
- 结构更清晰
- 更像《爆枪英雄》这类成长体验
- 更适合做长期 progression
- 对模组作者也更友好
局外商店负责什么
- 解锁武器
- 升级基础属性
- 购买常驻强化
第一版建议卖这些
武器解锁
- 手枪(默认)
- 冲锋枪
- 步枪
- 霰弹枪
属性升级
- 最大生命
- 武器伤害
- 换弹速度
- 移动速度
常驻强化
- 暴击率提升
- 金币获取加成
- 弹匣容量提升
这就够构成一个像样的成长循环了。
商店数据示例
[
{
"id": "hp_upgrade_1",
"name": "生命强化 I",
"type": "upgrade",
"price": 100,
"effect": {
"stat": "maxHp",
"value": 10
}
},
{
"id": "rifle_unlock",
"name": "步枪解锁",
"type": "weapon",
"price": 300,
"weaponId": "rifle_001"
}
]九、项目目录建议
project-root/
├─ public/
│ ├─ content/
│ │ └─ base/
│ │ ├─ characters/
│ │ ├─ portraits/
│ │ ├─ weapons/
│ │ ├─ enemies/
│ │ ├─ maps/
│ │ ├─ story/
│ │ ├─ shop/
│ │ └─ audio/
│ └─ mods/
│
├─ src/
│ ├─ main.ts
│ └─ game/
│ ├─ scenes/
│ │ ├─ MainMenuScene.ts
│ │ ├─ DialogueScene.ts
│ │ ├─ BattleScene.ts
│ │ ├─ ShopScene.ts
│ │ └─ GameOverScene.ts
│ ├─ engine/
│ │ ├─ content/
│ │ ├─ save/
│ │ ├─ modding/
│ │ ├─ dialogue/
│ │ └─ battle/
│ ├─ entities/
│ ├─ rigs/
│ └─ ui/
│
└─ docs/结构原则
src/
放引擎和逻辑。
public/content/base/
放官方内容。
public/mods/
放模组和创意工坊内容。
这样你后面扩展时,才不会把项目堆成一坨。
十、模组友好原则
如果你希望别人方便做创意工坊,就必须控制复杂度。
第一版优先开放
- 剧情脚本
- 角色配置
- 立绘资源
- 武器参数
- 地图配置
- 商店内容
暂时不要乱开放
- 核心战斗底层逻辑
- 存档底层结构
- 网络层
- 太深的脚本 API
不然兼容性一定炸。
模组作者应该能做到的事
- 新增一段剧情
- 新增一个角色立绘包
- 新增一把武器
- 新增一个商店物品
- 新增一个关卡
如果这些都能不碰核心代码,那这套结构就算成功。
十一、人物骨架建议
既然角色未来可能换武器、换皮肤、扩内容,人物仍然建议用:
分件拼接假骨架
推荐最小结构:
headbodyarm_backarm_frontweaponthigh_backcalf_backthigh_frontcalf_front
这套方案的好处是:
- 比整张逐帧灵活
- 比真骨骼省命
- 更适合做武器切换
- 更适合后续模组扩展
十二、当前最合理的第一版范围
必做
- 爱音 / 祥子 两个核心角色
- 轻量剧情展示层
- 左中右立绘槽位
- 说话高亮、其他角色变暗
- 2D 横版射击战斗
- 局外商店
- 数据驱动内容结构
暂缓
- 真正完整的 galgame 系统
- 超复杂条件分支
- 局内商店
- 联机
- 超深度模组 API
别什么都一口吃。那会把项目拖死。
十三、一句话总结
这项目不是要做一个 galgame,而是要做一个“类爆枪英雄”的 2D 横版射击游戏,只是剧情展示看起来更舒服一点,并且结构足够干净,方便以后做模组。
这条路才对。别再把自己绕进过度设计里。
