Files
SpineParticlesWeb/Document/粒子系统开发阶段一.md
2026-09-02 10:45:10 +08:00

6.6 KiB
Raw Permalink Blame History

粒子系统开发 · 阶段一总结

定位:一个网页端粒子发射编辑器(Vue3 + Vite + TS + Pinia + PixiJS),负责「粒子发射系统的参数化编辑 + 实时预览」。不依赖 Spine 运行时,Spine 只是未来导出的目标格式,未做烘焙导出(按决定后置)。

目录:/Users/tianmokeji/Desktop/SpineParticle(dev server: http://localhost:5173)。


一、产出

核心文件

  • src/core/particleEmitter.ts — 自研池化粒子发射器。排放模式(持续/爆发)、发射形状(点/圆/矩形/锥体)、速率、寿命、速度、缩放、旋转、颜色、混合、重力、风力、阻尼、随机种子、透明度曲线插值 curveAt
  • src/store/particleStore.ts — Pinia 状态(多粒子系统、activeId 选中、时间轴、画布设置、settings 坐标轴配置)。
  • src/views/ParticlePanel.vue — 左侧参数面板,7 个可折叠分组:场景对象/发射模式/发射器形状/粒子属性/外观与资源/修改器/导出设置(预留)。
  • src/views/Stage.vue — Pixi 画布(网格背景、滚轮缩放、左键拖拽、无限坐标轴、系统设置面板、发射点标记)。
  • src/views/Timeline.vue — 底部时间轴(播放/暂停/回到起点/循环、帧计数、滚轮缩放、每系统轨道条、拖动调延迟/时长)。
  • src/views/CurveEditor.vue — 透明度生命周期曲线(横 0-1 时间、纵 0-1 透明度,节点拖动、双击新建/删除)。
  • src/views/NumSlider.vue — 滑块+数字框复用控件。
  • public/particles/star.png — 默认贴图。

已确认的关键行为

  • Y 轴采用数学坐标系:向上为正、向下为负
  • 位置X/位置Y 是锚点(黄点),偏移X/偏移Y 让粒子相对黄点额外位移,物理/力场源锚在锚点。
  • 一个粒子系统 = 一个坐标点;无系统时画布无边线;选中系统高亮黄色,其余淡灰蓝。
  • totalFramesdelay + duration + lifeMax 按生命周期自动算出,只在改这些属性时重算(签名监听,不卡)。
  • 默认:发射速率 10/s、透明度用曲线(默认平台曲线 (0,0)(0.2,1)(0.8,1)(1,0))、贴图 star.png、英文命名 ParticleSystem1 递增、画布默认 100%(最小 1%、无上限)、默认不显示骨骼连线、进入页面空白、默认循环播放。
  • 设置面板「启用刻度」是坐标轴唯一主开关,默认关闭,内部含字号/线宽/颜色/显示刻度数值。

明确未做(预留)

导出 Spine JSON 烘焙、轨道路径/碰撞体/导出设置(界面占位)、真实 .atlas+.png 骨骼贴图。


二、踩过的坑

1. 方向性坑(代价最大)

  • 一开始把 sample.json 当"要预览的素材",被纠正为"用来创建粒子系统",再被纠正为"用参数创建、json 是最终导出物",最后"不要强行匹配 spine,只负责粒子发射编辑器"。多轮方向错误导致大面积返工(一度引入 spine-pixi 依赖、做骨骼对象池架构)。
  • 做了"加载示例骨骼"按钮,被否定(粒子系统创建应由编辑器参数决定)。
  • 进页面自动建"系统1"、预置 item_11,被要求默认空白。

2. 技术坑

  • Pixi7 Color API:copyFromsetValue(r,g,b) 不存在,渐变插值需手动拼 RGB 再 .toNumber()
  • 坐标轴文字放大变糊:Text 放在被缩放的 world 容器会位图模糊;移到独立 axisLabelLayer(不随 world scale),用世界→屏幕坐标定位。
  • SVG 曲线编辑器点变椭圆/viewBox 拉伸:preserveAspectRatio="none" 使 100×100 viewBox 拉成非正方形,圆变椭圆且命中区小;改为像素坐标系 + 加大命中圈。
  • 曲线编辑器 dblclick 不触发:透明命中圆上的 dblclick 被 pointer 捕获抑制;改为在 pointerdown 里检测两次点击(300ms 内、位移<8px)。
  • 双击删除又新建:删除节点后同一次双击的 dblclick 冒泡到 SVG 触发"双击空白新建";用删除抑制时间戳窗口解决。
  • 录制→回放使面板改动不实时:进入回放读旧帧,改参数(速率/颜色/曲线)不更新;把触发重录的 watch 改为对整个 config 做 JSON.stringify(排除 texture)的签名监听。
  • deep watch 引发每帧重算卡死:之前 watch(config, {deep:true}) 监听整个 config,emitter 每帧把 _elapsed 写进 config 导致深比较触发;修法:仅监听生命周期签名 + 把 _elapsed 移出 config 改为 emitter 私有字段。
  • 发射点与位置X/Y 错位:setEmitterPos 返回 centerX/centerY,而 spawnOrigin 又加一次 centerX,等于 2×centerX+offsetX;把锚点来源统一为 _emitterPos(),再加 offset。
  • Y 轴方向反了:坐标轴标签是数学坐标(上正),world 用屏幕坐标(下正),位置Y 为正反而画向下;在发射点初始化处把锚点 Y 取负。

3. 协作/流程坑

  • 反复来回挪同一设置面板里的选项(坐标轴颜色、显示刻度数值),属"反复折腾同一功能"。
  • 有明确方向时仍多次用 ask_user_question 确认 A/B 方案,拖慢节奏。

三、经验

  1. 先对齐"坐标系/数据模型"设计,再写编辑器代码。最贵的返工来自语义理解偏差,而非语法/API 问题。
  2. 区分"一次功能 vs 一次架构"。小参数改动要最小侵入;方向级错误要敢于回滚,不要继续堆。
  3. 性能敏感的循环里别 deep watch 可变的引用对象。粒子每帧改状态,任何 deep 监听都会拖垮帧率;用序列化签名 + 把每帧变化的场移到组件私有字段。
  4. 坐标系不统一是渲染 bug 高发地。屏幕坐标(下正)与数学坐标(上正)混用,导致"位置反了/文字糊了/缩放不对";最好在数据入口一次转换,渲染层保持单一约定。
  5. headless(puppeteer)自测有效。浏览器工具受限时,headless 截图 + read_image 能自主验证"位置/方向/颜色/数值是否生效"。
  6. 事件优先级要兜底。曲线编辑器这类点击/拖动/双击/删除并存的小组件,事件易互相干扰(删了又建、拖了误删),需时间戳/位移阈值/抑制窗口做防抖。

四、现状与风险

  • 还没做导出,一切围绕"调参 + 预览";导出 JSON(含烘焙 Spine)是后续大工程,现有"锚点 + 偏移 + 曲线 + 生命周期"数据模型已为此留接口。
  • 发射方向(direction)/力场/移动噪声这些修改器参数,物理方向在 Y 翻转后个别(发射方向角度、力场基准点 base)可能仍需按数学坐标复核;本次只改了黄点和粒子发射起点,base(力场)默认关闭未动,开启时需再验证。
  • 爆发模式下 totalFrames 算法(一次全出、无持续窗口)可能与预期不同,值得确认。
  • 代码是反复迭代出来的,个别函数较长、命名一般;若有长期维护打算,建议做一轮小清理(非必须)。