init with react+axum+spacetimedb
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
2026-04-26 18:06:23 +08:00
commit cbc27bad4a
20199 changed files with 883714 additions and 0 deletions

View File

@@ -0,0 +1,268 @@
# AI Native Visual RPG 开发经验沉淀
## 1. 项目特点判断
这个项目不是单纯的“像素 UI 项目”,而是 4 条链路同时耦合的项目:
1. 叙事链路AI 生成剧情文本与选项
2. 状态链路玩家、怪物、NPC、背包、好感、同伴、场景流转
3. 演出链路:战斗计划、动画播放、投射物、特效、镜头位移
4. 界面链路:选择世界、选择角色、冒险页、背包页、地图弹窗、编辑器页
经验:
- 做功能前先判断它主要影响哪几条链路。
- 如果一个需求同时影响“状态 + 演出 + UI”不要只改一个点。
- 像“初始同伴”这种功能,本质上不是 UI 需求,而是“选角流程 + 初始 encounter + NPC 好感 + 招募状态”的组合需求。
## 2. 先做数据建模,再做 UI
这一类项目最容易犯的错误,是先加按钮、再补状态。
实践下来更稳的顺序是:
1. 先把状态字段补齐
2. 再补工具函数
3. 再接交互入口
4. 最后补展示层
已经验证有效的状态字段包括:
- `playerInventory`
- `npcStates`
- `companions`
- `currentBattleNpcId`
- `currentEncounter`
- `playerActionMode`
- `activeCombatEffects`
经验:
- “能否交易 / 能否招募 / 送礼涨多少好感”都应该由状态和规则函数决定,不能写死在按钮文本里。
- NPC 交互尽量走本地规则,不要依赖 AI 即时决定关键数值。
## 3. 复杂页面一定要拆流程层
`App.tsx` 一旦同时承载:
- 游戏主状态
- 剧情生成
- 战斗播放
- NPC 交互
- 地图
- 选角
就会迅速失控。
当前更合理的分层思路是:
- `useGameFlow`
负责基础游戏状态、世界选择、角色选择、初始进入逻辑
- `useCombatFlow`
负责战斗计划与播放
- `useStoryGeneration`
负责剧情生成、NPC 本地交互分流、选项池管理
- `useNpcInteractionFlow`
负责同伴/NPC 展示态
- `GameShell`
负责主容器与选择流程
- `AdventurePanel`
负责冒险页文本和选项
- `NpcModals`
负责交易 / 送礼 / 放生招募等弹窗
经验:
- 流程层优先按“职责”拆,不按“文件长度”拆。
- 状态修改逻辑尽量集中到 hook 内,不要散落在多个组件按钮回调里。
## 3.1 AI 草稿数据进列表前,要先补本地稳定标识
自定义世界、角色草稿、澄清问题、生成结果卡片这类数据,在草稿态或兼容旧数据时,`id` 可能为空。
经验:
- React 列表的 `key` 不要直接裸用这类可能为空的 `id`
- 当前选中态、草稿缓存、轮播焦点也不要直接绑空 `id`,否则会出现“点了第二张卡,结果还是第一张卡被选中”的错位。
- 更稳的做法是:
- 业务数据层尽量补齐真实 id
- UI 层再补一层本地稳定 `selectionKey` / fallback render key
- fallback 至少带上 `index + 名称种子`,保证当前列表内唯一
## 4. AI 只适合生成叙事,不适合决定关键规则
实践中最稳定的策略是:
- AI 负责:
- `storyText`
- 非 NPC 关键规则的普通探索选项文案
- 本地规则负责:
- NPC 交互选项
- 交易合法性
- 礼物好感值
- 招募阈值
- 战斗掉落
- 帮助奖励
经验:
- 凡是会影响数值平衡、背包物品、好感、队伍成员的部分,都不要交给 AI 即时决定。
- AI 生成内容要能被本地规则兜底,否则体验会不稳定。
## 5. NPC 系统要“角色型 NPC”和“普通 NPC”分开处理
项目里 NPC 实际上有两种:
1. 普通场景 NPC可用通用 Medieval NPC 渲染
2. 角色型 NPC应该复用玩家角色对应的立绘和动画
经验:
- 只看 `encounter.kind === 'npc'` 不够,还要看 `encounter.characterId`
-`characterId` 的 NPC应该优先走 `CharacterAnimator`
- 否则就会出现:
- 选了某个同伴,但开场看到的是另一套通用 NPC 外观
- 战斗里角色型 NPC 看起来像普通路人
## 6. 位置与朝向必须统一到一套坐标规则
这是这类项目里最容易反复返工的点。
实践中踩过的坑:
- `sceneMonsters` 用一套坐标逻辑
- `currentEncounter` 用另一套坐标逻辑
- 结果开场 NPC 和遇怪站位不一致
- 角色型 NPC 立绘比怪物更容易出现“脚没落地”“太小”“翻转方向错”
最终经验:
- 对面实体的横向定位必须统一到“怪物那套 world-space 逻辑”
- 也就是:
- 位置统一用怪物侧的 anchor
- 相机平移时统一跟随同一套计算
- 角色型 NPC 的垂直位置不能偷懒固定
- 应该结合角色自身 `groundOffsetY`
- 朝向规则要统一使用 `getFacingTowardPlayer`
一句话总结:
- 角色型 NPC 不应该单独发明一套站位系统,而应该尽量复用怪物对位系统。
## 7. “新增流程”不要破坏原有选择 UI
这次初始同伴功能就是一个典型经验:
- 用户原本对“选择扮演角色”的视觉和交互已经形成预期
- 如果为了加“初始同伴选择”直接把角色选择页改成另一种样式,会造成明显割裂
经验:
- 新流程优先插在旧流程后面,而不是重写旧流程
- “确认角色 -> 选择初始同伴 -> 进入冒险”比“把原选角页改成全新样式”风险小很多
- 如果必须改 UI也要尽量保留旧页面的视觉结构和交互节奏
## 8. 冒险页布局要优先保证画面和选项完整可见
冒险页真正的优先级是:
1. 上方画布要在一屏内正常显示
2. 下方 3 个选项要在一屏内正常显示
3. 剧情文本框剩余空间自适应
经验:
- 文本框不能无限长撑开
- 正确做法是:
- 文本框高度自适应剩余空间
- 文本超长时内部滚动
- 不能让 `storyText` 把战斗画面和选项挤出首屏
## 9. 编辑器页和玩家页要明确隔离
当前项目里存在编辑器页面:
- `PresetEditor`
- `NpcVisualEditor`
- `StateFunctionEditor`
玩家页和编辑器页的需求完全不同。
经验:
- 像字体切换、视觉统一这种全局改动,不要直接打到整个站点
- 应该只挂在非编辑器根容器上
- 比如 `fusion-pixel-app` 这种类,只挂在正式游玩界面,不挂在编辑器根节点
## 10. 构建环境问题要项目内消化
实际踩到的构建问题:
- Node 16 环境下Vite 构建会因为 `crypto.getRandomValues` 缺失报错
沉淀出的解决方式:
- 不强依赖开发机立刻升级 Node
- 在项目内增加 Vite 启动包装脚本
- 统一让 `dev / build / preview` 都走这层 shim
经验:
- 环境兼容问题如果能在项目内吸收,就尽量不要把负担转移给每个协作者
- 文档里要明确记录“为什么这样做”
## 11. 比较稳的开发顺序
后续继续扩展功能时,建议遵守这个顺序:
1. 写状态字段
2. 写规则工具函数
3. 写流程 hook
4. 接 UI
5.`npm run lint`
6.`npm run build`
7. 再做视觉微调
不要反过来做:
- 先做 UI
- 再补状态
- 最后硬修流程
这种顺序在状态复杂的项目里会越改越乱。
## 12. 当前最值得继续坚持的原则
- 保持 AI 生成和本地规则分工清晰
- 保持角色型 NPC 与普通 NPC 的渲染分流
- 保持“怪物 / encounter / 战斗 NPC”统一坐标系
- 保持新增功能不破坏既有核心 UI 体验
- 保持编辑器页与玩家页隔离
- 每次大改后都用 `lint + build` 双重验证
## 13. 后续建议
下一阶段最值得继续沉淀的方向:
1. 把 NPC 交互逻辑继续从 `useStoryGeneration` 中独立成更纯粹的 `useNpcInteractionFlow`
2. 把角色型 NPC 的位置、缩放、贴地参数做成可配置规则,而不是继续散落在画布里微调
3. 针对初始同伴流程补一份单独的状态图 / 时序图
4. 对大 chunk 警告做代码分包
## 14. SpacetimeDB 绑定桥接层要做同名去重
`server-rs/crates/spacetime-client` 里有一部分内容是围绕 SpacetimeDB 生成绑定补的手写桥接层。
经验:
- 新增 procedure、input type 或 mapper 时,先全局确认 `module_bindings/mod.rs``mapper.rs`、业务封装文件里是否已经存在同名声明
- `module_bindings/mod.rs` 同一个模块只保留一条 `pub mod` 和一条 `pub use`,不要同时放在 reducer 区和 procedure 区
- `mapper.rs` 的字符串枚举解析函数、API 入参结构只保留一个权威定义,业务侧统一复用
- 业务封装文件里同一个 procedure 只暴露一个客户端方法,避免 Rust 在编译期出现 E0428、E0252、E0119、E0592 这类重复定义错误
- 修复重复绑定时优先删除后追加的重复块,不要重写整文件,避免影响中文注释和生成绑定附近的大段内容
## 15. 一句话总结
这个项目真正的开发经验不是“怎么多写一个按钮”,而是:
- 在 AI 叙事、像素演出、战斗状态、NPC 规则、选择流程和编辑器体系同时存在的情况下,始终让每条链路各归其位。