文件清理

This commit is contained in:
tianmo
2026-09-02 10:45:10 +08:00
parent 8c0e512333
commit 63e4f61631
19 changed files with 0 additions and 469 deletions
+249
View File
@@ -0,0 +1,249 @@
# 网页粒子系统 · 阶段二
> 一个纯前端的 **Spine 粒子发射编辑器**(参考 喵喵 Spine Particle / Spine Particle Studio)。
> 本阶段聚焦「粒子发射编辑器的系统设计与显示」,不强行绑定 Spine 运行时;Spine 仅是未来导出格式的参考,`spine/sample.json` 只作素材,不做为编辑器模板。
> 工程目录:`/Users/tianmokeji/Desktop/SpineParticle`
---
## 一、项目定位与技术栈
**定位**:可视化配置粒子特效、实时预览,最终输出导出所需的数据模型(导出为后续阶段,现阶段为预览/编辑铺路)。
**技术栈**
- 前端框架:Vue 3 + TypeScript + Vite
- 状态管理:Pinia
- 渲染:PixiJS 7.4(`@esotericsoftware/spine-pixi-v7` **已卸载**,当前不依赖任何 Spine 运行时;`pixi.js` 用于画布)
- 粒子:自研粒子发射器 `ParticleEmitter`(池化,Sprite 池)
**明确不做(预留)**:导出 Spine JSON 烘焙、骨骼对象池匹配 Spine 运行时。(本阶段核心 = 编辑器 + 预览。)
---
## 二、已完成功能清单
### 2.1 面板(ParticlePanel)架构
按参考站分组、全部可折叠:
| 分组 | 内容 |
|---|---|
| **场景对象** | 树形层级(●粒子系统 / ◇轨道路径【预留】 / ◇碰撞体【预留】),名称、位置X、位置Y、偏移X、偏移Y、**旋转、缩放X、缩放Y** |
| **发射模式** | 持续(发射速率) / 爆发(单次数量) |
| **发射器形状** | 形状(点/圆/矩形/锥体/轨道),圆/锥(半径)、矩形(宽/高)、锥形开口角度、发射角度、扩散角度 |
| **粒子属性** | 速度下限/上限、生命下限/上限、缩放下限/上限、旋转速度、随机种子 |
| **外观与资源** | 透明度(曲线/固定)、曲线模式(跟随导出/线性/贝塞尔)、图片资源(子文件夹/名称)、颜色类型(固定色/渐变)、混合(正常/相加/相乘/滤色)、占比与缩放(占比/缩放/轴心/独立透明度) |
| **修改器 (Modifiers)** | 移动噪声、重力、风力、力场、拖尾、碰撞【预留】、路径跟随【预留】、阻尼 |
| **导出设置** | 预留(占位) |
**数值调节统一为滑块 + 数字框并存**(`NumSlider` 组件)。
### 2.2 画布(Stage)
- 网格背景(可切 ▦),深色棋盘格。
- 滚轮缩放画布:`默认 100%`,`最小 1%`,**无上限**;以鼠标为中心缩放(鼠标下的世界点不漂移)。
- 左键长按拖动画布(平移)。
- 顶部工具栏:
- **根变换工具**:✋ 拖动画布(默认)、✥ 位移、⟳ 旋转、⤢ 缩放(切换后用 gizmo 拖拽根节点)。
- 视图工具:+/- 缩放、⤢ 适配画布、▦ 网格、⟳ 重置视图。
- 开关:发射点、骨骼连线、⚙ 系统设置。
- 坐标轴:`center+center` 无限长 x/y 轴 + 自适应整数刻度,数字标签放**独立不缩放图层**(避免放大变糊);由系统设置"启用刻度"主开关控制。
- **发射点(黄点)**:每个系统一个,绑 位置X/位置Y(centerX/centerY);选中高亮黄,其余淡蓝灰;无系统不画。
- **发射器形状范围**:选中对应形状时绘制绿圆(圆形)、蓝矩形(矩形)、绿锥形(锥体),随参数实时响应;点/轨道不绘制。
- **根变换 gizmo**:选中工具后在 root 中心亮青色手柄(位移十字箭头 / 旋转圆环 / 缩放方框角点)。
### 2.3 时间轴(Timeline)
- 控制按钮:❚❚/▶ 播放暂停、⏮ 回到起点、⟳ 循环。
- 帧显示 `当前帧 / 总帧数`,动画名下拉。
- 标尺:整数帧刻度;**滚轮缩放只改变显示范围(视口帧数,左端恒为第 0 帧),不改变播放时长**;刻度为整数、最小 0。
- 每系统一条轨道;播放头可拖动滑动。
- **粒子条**:位置 = 延迟(delay)起点,宽度 = 发射时长(duration)+粒子生命(lifeMax) = 总时长;左端之前空白 = 延迟时长。可整体拖动(调 delay)、拖右缘(调 duration)。
### 2.4 系统设置(⚙ 面板)
- **启用刻度** 主开关(默认关闭):开启才显示坐标轴并展开其余配置项。
- 刻度文字大小(默认 12px)、刻度线粗细、坐标轴颜色、显示刻度数值(均随"启用刻度"显隐)。
### 2.5 默认值(按用户给定的截图定版)
| 项 | 默认 |
|---|---|
| 发射模式 | 持续,发射速率 = 20 |
| 爆发 | 单次数量 = 50 |
| 延迟时间 | 0 |
| 发射时长 | 0(= 无限,已在 UI 移除该项) |
| 持续爆发开关 | 已移除 |
| 形状 | 点;发射角度 0、扩散角度 360 |
| 圆形 | 半径 100,方向 -90,扩散 0 |
| 矩形 | 宽 200,高 100,方向 -90,扩散 0 |
| 锥体 | 半径 100,锥形开口 90,朝向 90 |
| 系统命名 | ParticleSystem1(英文,同名自动 +1) |
| 贴图 | star.png |
| 透明度 | 曲线模式,默认点 (0,0)(0.2,1)(0.8,1)(1,0) |
| 随机种子 | 0(每次不同) |
---
## 三、核心架构设计
### 3.1 数据流(单向)
```
ParticlePanel(Template) ⇄ Pinia Store (systems[]) ⇄ Stage.loop (emitterMap)
▲ │
└────── 面板 v-model 改 config ──────┘
```
- **`sys.config` 按引用保持**:面板直接改 config 字段,Pixi 发射器读到的是同一对象,改动实时生效。
- `emitterMap: Map<systemId, ParticleEmitter>` 由 Stage 维护,每帧 render。
### 3.2 坐标系(关键)
编辑器统一采用**数学坐标系:Y 向上为正、向下为负**。Pixi 内部是屏幕坐标系(Y 向下为正),因此:
- 发射点/黄点位置:`cy = -centerY`;
- 发射器容器 `em` position:`em.position.set(centerX, -centerY)`;
- 初始速度 y:`vy = -sin(ang)`(锥体);
- 形状范围、gizmo 绘制均遵循 `cy 取负`
**统一手法**:显示层(黄点、形状、gizmo、容器 position)都做 Y 取负转换;而**粒子物理/运动方向**保持屏幕坐标,二者在"发射方向(锥体)"这一处需显式对齐。
### 3.3 发射器(ParticleEmitter extends Container)
粒子是发射器容器的子项(Sprite 池),坐标在**发射器局部坐标**。因此对容器设 `position/rotation/scale` 即整体变换整簇粒子 = **"root 变换"**。
角色划分:
- **root 位移** = `centerX/centerY` → 容器 `position`;
- **root 旋转** = `rootRotation` → 容器 `rotation`(数学上正,顺时针为负);
- **root 缩放** = `rootScaleX/rootScaleY` → 容器 `scale`
**发射局部原点**:粒子发射位置 = `中心(centerX) + 偏移(offsetX/offsetY)`,其中 `spawnOrigin` 的局部原点只含 offset,中心由容器 position 补回。这样旋转/缩放绕 root 中心,且最终世界坐标不变。
### 3.4 发射模式与形状
- **持续(stream)**:`accumulator += dt * rate`,累积满 1 发射一个。
- **爆发(burst)**:一次性 `emitBurst()``burstCount` 个。
- **发射形状**(`spawnOrigin`):
- point:中心;
- circle:半径内圆散布;
- rect:宽高矩形散布;
- cone:**从锥形顶点(中心)发射,方向在 `direction ± coneAngle/2` 扇形内随机**、半径 0~radius(与圆不同,只限锥体弧度范围)。
- **速度方向**(`spawnOne`):锥体用 coneAngle 散布 + `vy=-sin`;其余用 spread + 屏幕坐标。
### 3.5 根变换 gizmo
- 3 种工具:位移 / 旋转 / 缩放,切换后画布按住拖动改 `config.centerX/centerY``rootRotation``rootScaleX/ScaleY`
- 与场景对象面板参数**双向联动**。
- 计算基于**按下时的快照 + 相对按下点的总增量**(位移/旋转/缩放都不累积),保证"跟手"。
### 3.6 录制 → 回放
- Stage.loop 每帧 `em.update(dt)`(固定 `dt = 1/fps` 保证帧对齐),产生的 `ParticleState[]` 深拷贝存到 `sys.frames[frame]`
- 录完后进入回放:`em.apply(frames[frame])`,到尾帧若 loop 则回 0,否则停末帧。
- 每帧也收集活动粒子状态 `collected`(供 HUD 数字化/骨骼连线)。
### 3.7 时间轴总帧数(自动计算)
- `totalFrames = ceil( max(对所有系统 (delay + duration + lifeMax)) * fps )`
- 即"最后一个粒子消失的时刻";改 delay/duration/lifeMax 时重算。
- 通过 **字段签名 watch** 触发重录(见踩坑点 #1)。
---
## 四、踩坑点(重要)
1. **签名 watch 粒度**
- 起初 `watch(store.systems.map(config), {deep:true})` 监听整个 config;但发射器每帧改 `_elapsed` 等内部字段 → 每帧触发 → 每帧重算 totalFrames + 清空重录 → **卡死**
- 修复:`JSON.stringify(s.config, 排除 texture)` 生成**签名字符串**比较,只有"影响渲染的参数"变化才触发;并把 `_elapsed/_delayLeft` 从 config 移至 emitter **私有字段** `_elapsed`,避免污染响应式。
2. **坐标轴文字放大变糊**
- Text 放 world 容器随 viewScale 放大变为位图模糊。
- 修复:文字放到**独立 `axisLabelLayer`(不随 world 缩放)**,用世界→屏幕坐标定位;字号由"系统设置"手动控制。
3. **曲线编辑器椭圆节点/选不中**
- SVG `viewBox + preserveAspectRatio="none"` 把圆拉伸成椭圆、命中区过小。
- 修复:改按实际像素绘制(ResizeObserver 量尺寸),节点为正圆,叠加大命中圈,Pointer 捕获拖动。
4. **曲线节点双击删除又新建**
- dblclick 冒泡到 SVG 触发"双击空白新建"。
- 修复:删除节点记录时间戳,`onDbl`(新建)在删除后 400ms 内忽略(抑制窗口)。
5. **Y 轴方向反**
- 坐标轴标签用数学坐标(上正),发射器/黄点用屏幕坐标(下正),方向相反。
- 修复:黄点/发射器容器 position 的 y 取负,统一到数学坐标系。
6. **锥体朝向与粒子方向相反**
- 粒子发射用 `-sin`(数学上正),形状范围绘制用 `+sin`,二者符号不一致。
- 修复:drawShapeRange 逐点做 `pt` 变换时统一 `Y = -ly - oyo`(与粒子一致)。
7. **锥体粒子散布在扇区内 vs 从中心发射**
- 初版把粒子位置散布在扇形区域(半径 × coneAngle),与圆的"中心全向"不同。
- 按用户意图修正:粒子**从锥形顶点(中心)发射,方向限 `direction ± coneAngle/2` 扇形**,类似"圆心向各方向,但只限锥体弧度范围"。
8. **偏移不影响形状范围**
- 形状范围中心画在 root 中心,没用 offset;粒子发射源 = root 中心 + 偏移,导致偏移时粒子相对形状范围偏移。
- 修复:drawShapeRange 的 `pt` 把偏移 `(oxo, oyo)` 并入局部原点;且 `offsetY` 转屏幕取负(`Y = -ly - oyo`)。
9. **发射时长 1 秒后停发**
- `duration` 默认 1.0,爆发/持续 1 秒后 `withinDuration=false` 停发,粒子随后消亡 → "没粒子"。
- 修复:配合 UI 移除"发射时长/持续爆发",`duration` 默认改 0(=无限),`burstLoop` 默认 false(一次性爆发)。
10. **gizmo 拖拽不跟手**
- 位移用 `cfg.centerX + dx`(基于当前值累加总位移 dx),鼠标一动重复叠加 → 越拖越偏。
- 修复:存按下时基准 `sCx/sCy`,位移 = 基准 + 总位移;旋转/缩放同样基于基准快照,并去掉 `Math.round` 取整抖动。
11. **默认贴图/命名等默认值**
- 曾把默认 textureName 设为 dot、并在用户改图后仍用旧缓存;后按用户要求默认 `star.png`,硬刷新即可。
- 系统命名:默认英文 `ParticleSystem1`,同名自动 +1(正则 `^ParticleSystem(\d+)$` 取最大值 +1)。
12. **新增系统不显示贴图**
- 初期只给 onMounted 时存在的系统赋 texture,后续 `addSystem` 新增系统 texture 为 null → Sprite 画不出。
- 修复:在 `syncEmitters()`(每帧)里,给 `dotTex 已加载但 sys.config.texture 为空` 的系统补上默认纹理。
---
## 五、根节点变换的坐标正确性(易错综述)
- 发射器容器(position/rotation/scale)承载 root 变换。
- **必须**把发射局部原点改为"只含 offset"(去 centerX),centerX 由容器 position 补回;否则 position 与粒子局部坐标双重叠加 → 位移重复。
- 旋转/缩放绕 root 中心成立,前提是"root 中心 = 容器局部原点(0,0)"。
- 数学坐标系下,一切显示层 y 取负;粒子运动方向(y 向)在锥体方向处需与形状绘制对齐。
---
## 六、响应式与性能要点
- **`sys.config` 保持引用**,面板 v-model 直接改属字段 → 实时生效,无深拷贝/重建。
- **避免 deep watch** 整个 config;用签名字符串监听 + 内部计时字段移出 config。
- **录制固定步长** `dt = 1/fps`,保证回放与实时帧对齐、可复现。
- **确定性随机(种子)**:`mulberry32`;`seed>0` 时可复现;**仅在种子变化时重建 RNG**,`reset()` 时若种子>0 重置到序列头,保证同种子从头录制结果一致。
---
## 七、文件结构
```
src/
main.ts # 入口,注册 Pinia
App.vue # 布局:左面板 / 右上画布 / 右下时间轴(宽度/高度可拖拽)
style.css # 全局样式(深色主题)
core/
particleEmitter.ts # 发射器:config + defaultConfig + 池化更新/捕获/回放/重置/确定性随机
store/
particleStore.ts # Pinia:systems[] + activeId + timeline + settings + recalcTotalFrames
views/
Stage.vue # 画布:world/粒子/坐标轴/黄点/形状范围/gizmo + 录制回放 + 根变换
ParticlePanel.vue # 参数面板(分组折叠 + NumSlider + 形状默认值切换)
Timeline.vue # 时间轴(控制/标尺缩放/轨道/粒子条拖拽)
CurveEditor.vue # 透明度曲线编辑器(节点可拖/双击删/双击空白新建)
NumSlider.vue # 滑块 + 数字框 双控组件
public/
particles/star.png # 默认粒子贴图(另有 dot.png / spark.png)
spine/sample.json # 仅作素材参考,不作编辑器模板
```
---
## 八、当前遗留 / 预留(后续阶段)
- **导出 Spine JSON 烘焙**(用户确认后置):现阶段编辑/预览,导出的数据模型(每粒子一根骨骼 `boneName=p_<i>`、ParticleState)已为此铺路。
- **轨道路径 / 碰撞体**:场景对象中为预留占位(`collision`/`pathFollow` 字段已存在)。
- **导出设置**:分组占位。
- **修改器实现程度**:重力/风力/力场/移动噪声/阻尼已接入 `emitter.update`;拖尾/碰撞/路径跟随为占位/预留。
---
## 九、常见调整指引
- **默认参数**集中在本文件 §2.5(来自 `particleEmitter.ts``defaultConfig()` 与面板 `SHAPE_DEFAULTS`)。
- **形状范围跟随 root 旋转/缩放/偏移**:改 `Stage.vue``drawShapeRange`(逐点 `pt` 变换)。
- **根变换 gizmo**:改 `Stage.vue``drawGizmo` / `onGizmoDown`
- **时间轴总帧**:改 `store.recalcTotalFrames`
- **坐标系**:凡涉及显示 y 方向,遵循 `Y 取负` 的数学坐标约定。