文件清理

This commit is contained in:
tianmo committed 2026-09-02 10:45:10 +08:00
1 parent 8c0e512333
commit 63e4f61631
19 files changed
-469

No files matched your search

+712
View File
@@ -0,0 +1,712 @@
# SpineParticle · Spine JSON 导出设计标准规范
> 文档性质:导出功能开发、验收与后续维护的唯一对齐基线
> 规范状态:已完成产品规则对齐,导出设置面板已实现,JSON 导出逻辑尚未开始
> 目标格式:Spine JSON 4.2
> 主要用途:将导出 JSON 重新导入 Spine 编辑器继续编辑
> 最后更新:2026-08-31
---
## 一、规范目的
本规范用于固定 SpineParticle 的 Spine JSON 导出范围、骨架结构、动画烘焙、资源引用、拖尾蒙皮、关键帧压缩和导出设置语义,避免后续开发因上下文丢失或实现人员变化而偏离已确认规则。
后续实现若与本文冲突,应先重新确认并更新本文,不能仅在代码中改变既定语义。
---
## 二、导出目标与边界
### 2.1 导出目标
导出结果是一个能够由 Spine 4.2 识别,并能重新导入 Spine 编辑器继续编辑的 JSON 骨架动画文件。
导出数据必须满足:
1. 粒子表现不依赖 SpineParticle 编辑器运行时;
2. 粒子、序列帧、颜色、透明度、缩放、旋转、拖尾等表现被转换为 Spine 原生骨骼、插槽、附件与动画时间轴;
3. 与其他场景模块发生关联的粒子表现必须烘焙为独立动画数据;
4. 文件结构优先兼顾可编辑性、动画精度和合理体积;
5. 导出后使用 Spine 4.2 Runtime 进行结构解析验证。
### 2.2 导出内容
只导出场景中的粒子系统相关内容:
- 粒子系统骨骼;
- 粒子图片插槽和 Region 附件;
- 粒子位移、旋转、缩放动画;
- 粒子颜色和透明度动画;
- 粒子图片与序列帧附件切换;
- 拖尾骨骼链;
- 拖尾加权 Mesh、颜色、透明度和资源切换;
- 项目中的“所属动画”及其粒子时间轴。
### 2.3 不导出的内容
以下场景对象本身不进入 JSON:
- 碰撞体;
- 路径;
- 已加载的 Spine 对象;
- 原 Spine 对象中的骨骼、插槽、皮肤、图片和动画;
- 编辑器辅助线、网格、背景图、骨骼预览点和调试显示。
这些模块若影响了粒子运动,必须只烘焙其最终结果,不能在导出 JSON 中保留对原对象的引用。
### 2.4 场景显隐规则
- 场景对象小眼睛关闭的粒子系统不导出;
- 显示状态正常的粒子系统参与导出;
- 碰撞、路径和 Spine 对象无论是否显示都不作为对象导出;
- 若可见粒子系统的模拟依赖有效的碰撞、路径或 Spine 跟随,其最终影响仍应被烘焙。
---
## 三、格式依据与版本规则
### 3.1 格式依据
实现时依次以以下内容作为格式依据:
1. Spine 官方 GitHub 仓库中的 Spine 4.2 JSON 示例;
2. 项目当前使用的 `@esotericsoftware/spine-core 4.2.119` JSON 解析器;
3. 项目已有的 Spine 4.2 官方导出样本;
4. Spine 官方 JSON 格式说明。
官方中文说明中的部分示例仍使用 Spine 3.8,不能直接照搬旧版字段。实现必须重点核对 Spine 4.2 的:
- `skins` 结构;
- `rgba` 插槽时间轴;
- `attachments` 动画结构;
- 加权 Mesh 顶点编码;
- 附件切换;
- 多动画结构;
- Draw Order 字段命名;
- Sequence 附件及时间轴结构。
### 3.2 版本范围
第一版只支持:
```text
Spine 4.2
```
设置面板保留下拉框,以便以后扩展版本,但第一版不得提供没有真实兼容实现的 4.0、4.1 或其他版本选项。
---
## 四、整体 JSON 结构
导出 JSON 的主要结构为:
```text
skeleton
bones
slots
skins
animations
```
不输出无关约束、碰撞体或路径附件。
推荐骨架层级:
```text
root
├─ ParticleSystem1
│ ├─ _particle_000
│ ├─ _particle_001
│ ├─ _particle_000_trail_1_0
│ │ └─ _particle_000_trail_1_1
│ │ └─ ...
│ └─ ...
└─ ...
```
每个粒子系统必须作为 `root` 下的独立父骨骼,粒子骨骼统一使用简洁的 `_particle_000` 样式并归入对应系统。所有骨骼、插槽和附件名称仍须全局唯一;多系统发生重名时只为后续同名骨骼追加最短编号后缀。
---
## 五、Setup Pose 标准
### 5.1 普通骨骼
除拖尾子骨骼链外,所有导出骨骼的 Setup Pose 使用统一默认值:
```text
x = 0
y = 0
rotation = 0
scaleX = 1
scaleY = 1
```
粒子系统在编辑器中的位置、旋转和缩放不写入普通骨骼 Setup Pose,必须进入对应动画时间轴。
### 5.2 拖尾骨骼链例外
拖尾是加权 Mesh 的绑定骨骼链。为了保证导入 Spine 后初始蒙皮结构正确:
- 拖尾链保持正常父子层级;
- 子骨骼保留合理的绑定位置和骨骼长度;
- 子骨骼不能全部重叠在原点;
- 动画帧中的拖尾世界状态转换为相对父骨骼的局部位移和局部旋转;
- 拖尾链根独立于粒子图片骨骼的旋转和缩放,避免粒子图片变换意外污染拖尾蒙皮。
### 5.3 Setup Pose 可见性
所有蒙皮绑定完成后的粒子 Region 和拖尾 Mesh 在 Setup Pose 中默认透明度为 0,避免导入 Spine 后干扰已有图像显示。
动画开始后,由附件切换和 `rgba` 时间轴恢复正确图片、颜色与透明度。
---
## 六、粒子到 Spine 数据的映射
| SpineParticle 数据 | Spine 4.2 数据 |
|---|---|
| 粒子系统 | 系统根骨骼 |
| 粒子池槽位或独立出生粒子 | 粒子骨骼 |
| 粒子图片 | Region 附件 |
| 粒子图片显示层 | Slot |
| 粒子位置 | Bone Translate Timeline |
| 粒子旋转 | Bone Rotate Timeline |
| 粒子缩放 | Bone Scale Timeline |
| 粒子颜色与透明度 | Slot RGBA Timeline |
| 粒子出生、死亡、假死 | Attachment 与 RGBA Timeline |
| 多图片资源 | 同一 Slot 下的多个 Region 附件 |
| 序列帧 | Attachment Timeline 切换 Region |
| 混合模式 | Slot Blend |
| 拖尾 | 加权 Mesh + 拖尾骨骼链 |
| 拖尾颜色与透明度 | 拖尾 Slot RGBA Timeline |
| 所属动画 | `animations` 下的动画名称 |
---
## 七、粒子骨骼与对象池
### 7.1 开启骨骼对象池
开启时:
- 按粒子系统最大同时存活数量建立固定粒子骨骼池;
- 粒子死亡后骨骼可供后续粒子复用;
- 图片 Slot 和对应拖尾骨骼链随粒子骨骼一起复用;
- 文件骨骼数量和时间轴体积应显著小于不复用模式;
- 复用前后通过 Attachment 和 RGBA 关键帧明确隐藏、显示及切换资源。
### 7.2 关闭骨骼对象池
关闭时:
- 每一次逻辑粒子出生建立独立骨骼;
- 已死亡粒子的骨骼不再分配给后续粒子;
- 动画较长或发射率较高时允许产生大量骨骼;
- 界面无需阻止,但导出前可以提示预计骨骼数量和文件体积。
### 7.3 循环动画例外
纯循环持续动画必须保证循环衔接帧两端的粒子身份稳定。即使关闭对象池,也必须为跨越循环接缝的粒子维持稳定骨骼映射,避免循环首尾出现身份跳变。
---
## 八、图片与序列帧
### 8.1 图片文件
导出功能不复制、不下载、不嵌入 PNG,也不生成 Atlas。
JSON 只负责输出正确的图片资源路径。用户将在 Spine 中重新扫描并指定图片文件夹。
禁止把以下编辑器内部地址写入附件路径:
- Data URL;
- Blob URL;
- 浏览器临时对象地址;
- 本机绝对文件路径。
### 8.2 图片路径
设置中的图片路径,例如:
```text
./images/
```
写入 `skeleton.images`。
附件 `path` 只写相对于图片文件夹的资源路径,不重复附加 `./images/`。附件路径应:
- 保留资源子文件夹结构;
- 统一使用 `/`;
- 清理重复的 `./`;
- 使用稳定文件名;
- 默认使用不含图片扩展名的 Spine 资源路径;
- 检测并提示重复路径。
### 8.3 图片缺失
图片缺失不阻止导出。
- 仍然输出对应 Region 或 Mesh 附件;
- 保留用户可识别的文件名和路径;
- Spine 中允许显示缺失图片,用户可根据文件名补充文件;
- 能取得图片原始尺寸时写入真实尺寸;
- 完全无法取得尺寸时写入安全占位尺寸并给出提示;
- 缺图、路径重复或占位尺寸属于警告,不直接取消 JSON 下载。
### 8.4 图片尺寸、缩放和锚点
- Region `width/height` 使用图片原始像素尺寸;
- 粒子资源缩放已经体现在粒子骨骼 Scale 动画中,不得重复乘到图片尺寸;
- Pixi 锚点转换为 Region 相对骨骼中心的偏移;
- 转换时必须处理 Spine 数学坐标 Y 向上与 Pixi 屏幕坐标 Y 向下的差异;
- 实现后必须使用非中心锚点和旋转状态进行视觉对照。
### 8.5 序列帧
一组序列帧始终只使用一根粒子骨骼,不因图片数量增加骨骼。
第一版不依赖 Spine 原生 Sequence 命名约束,而采用:
- 每张序列图片建立独立 Region 附件;
- 使用 Attachment Timeline 切换当前 Region;
- 按烘焙后的最终图片索引输出,不在导出端重新推演播放模式。
需要完整支持:
- 单次-尾帧消失;
- 单次-尾帧固定;
- 正向循环;
- 反向循环。
---
## 九、拖尾加权 Mesh 标准
### 9.1 禁止项
拖尾不得使用逐帧 `attachments → deform` 时间轴作为正式导出方案。
### 9.2 正式结构
拖尾直接转换为:
```text
拖尾骨骼链 + 加权 Mesh + 骨骼动画
```
复用项目现有拖尾数据:
- `trailGridRows / trailGridCols` 生成网格拓扑;
- 当前三角形索引写入 `triangles`;
- 当前 UV 与图片旋转写入 `uvs`;
- `trailSkinWeights` 中的 `boneA / boneB / weight` 转换为 Spine 加权 Mesh 顶点编码;
- `trailState.bones` 转换为拖尾骨骼链动画;
- 拖尾资源切换使用同一拖尾 Slot 下的多个 Mesh 附件和 Attachment Timeline;
- 拖尾颜色和透明度使用拖尾 Slot 的 RGBA Timeline。
### 9.3 拖尾动画映射
- 拖尾长度由骨骼链各节点位置体现;
- 拖尾方向由骨骼旋转体现;
- 拖尾宽度由骨骼垂直缩放体现;
- 拖尾形状曲线进入 Mesh 的绑定顶点分布;
- 拖尾图片旋转进入 UV;
- 拖尾头部必须与粒子最终渲染位置一致;
- 每帧世界状态转换为父子骨骼局部状态后再写入时间轴。
### 9.4 权重标准
- 每个顶点使用项目已有的相邻骨骼影响关系;
- 每个顶点权重和必须在允许误差内等于 1;
- 骨骼索引必须对应最终 `bones` 数组顺序;
- 不允许引用不存在或被排除的骨骼;
- 导出后需要检查弯曲、宽度变化、资源旋转和粒子旋转情况下的拖尾形态。
---
## 十、外部关联烘焙
### 10.1 通用规则
所有导出动画先执行一次独立、确定性的离线烘焙:
1. 使用当前粒子系统随机种子重置模拟;
2. 按导出 FPS 逐帧推进;
3. 计算路径跟随、Spine 骨骼跟随、碰撞和全部修改器;
4. 记录粒子和拖尾最终渲染状态;
5. 将外部对象影响转换为系统根或粒子骨骼动画;
6. JSON 中不保留任何外部对象引用。
### 10.2 碰撞
- 不导出碰撞体;
- 碰撞造成的位置、速度方向、暂停、销毁和反弹结果进入最终粒子动画;
- 碰撞突变前后必须保留关键帧,不能跨越突变进行平滑压缩。
### 10.3 路径跟随
- 不导出路径对象或 Path Constraint;
- 本地空间跟随烘焙为系统根运动与粒子局部运动;
- 世界空间跟随烘焙为粒子最终世界运动,已出生粒子不能错误继承后续路径移动;
- 路径方向和旋转表现按画布最终结果写入。
### 10.4 Spine 骨骼跟随
- 不导出原 Spine 对象或原目标骨骼;
- 跟随位置和用户设置的偏移进入最终动画;
- 不额外继承原 Spine 骨骼的缩放和旋转,保持编辑器现有语义;
- 目标失效时按编辑器当前默认回退逻辑处理。
### 10.5 坐标空间
不能只读取粒子局部快照直接输出。每帧必须组合:
```text
粒子局部状态
+ 系统根变换
+ 当前帧跟随目标产生的有效变换
= 最终可导出状态
```
同时统一处理 Pixi Y 向下与 Spine Y 向上的坐标转换、旋转方向和父子局部变换。
---
## 十一、动画归属与时长
### 11.1 多动画
一个 JSON 包含项目中的多个“所属动画”:
```json
"animations": {
"animation": {},
"animation2": {},
"start": {},
"loop": {},
"end": {}
}
```
- 每个粒子系统只写入它所属的动画;
- 多个粒子系统可以属于同一动画;
- 同一动画时长取参与导出的粒子系统中最长者;
- 较短系统到达末帧后保持符合自身模式的末帧状态;
- Spine、碰撞体和路径自身时长不参与导出动画时长计算。
### 11.2 空动画
空动画是否保留遵循“排除无内容动画”设置:
- 设置关闭:所属动画列表中的空动画仍输出为 `{}`;
- 设置开启:没有任何可见粒子动画内容的动画不写入 JSON。
### 11.3 循环持续三段动画
- 启动段、纯循环段和结束段按用户当前保存的状态导出;
- 循环衔接帧的粒子身份和视觉状态必须稳定;
- 纯循环段保留假死粒子的骨骼身份,并通过附件隐藏或透明度 0 表现死亡;
- 循环首尾接缝不得因关键帧精简产生卡顿或突变;
- 延迟时间保持现有循环持续语义;
- 所属动画命名继续沿用编辑器已有逻辑,不由导出器自动改名。
### 11.4 时间端点
- 时间为 `frameIndex / exportFps`;
- 时间轴包含可选择的最后端点帧;
- 循环动画允许保留首尾相同的末端点;
- 时长不足完整导出帧时按规则向上取整,不能截断最长寿命粒子或拖尾。
---
## 十二、关键帧烘焙与压缩
### 12.1 总体策略
采用:
```text
完整逐帧烘焙 → 分类 → 安全精简 → 误差验证
```
不能先减少采样再模拟,也不能简单每隔固定帧保留一帧。
### 12.2 离散数据
以下数据不能进行数值插值,只保留变化点并使用阶跃语义:
- 粒子出生和死亡;
- 图片显示和隐藏;
- 粒子图片资源切换;
- 序列帧附件切换;
- 拖尾资源切换;
- 混合模式变化;
- 假死状态切换。
连续多帧相同附件或相同状态只输出第一次变化关键帧。
### 12.3 连续数据
以下数据先逐帧烘焙,再进行误差受控精简:
- 粒子位置;
- 粒子旋转;
- 粒子缩放;
- 粒子颜色和透明度;
- 拖尾骨骼位置;
- 拖尾骨骼旋转;
- 拖尾宽度缩放。
精简器检查删除中间关键帧后,Spine 插值结果是否仍在误差范围内。采用以下视觉容差:
| 数据 | 建议容差 |
|---|---:|
| 粒子位置 | 0.05–0.1 像素(当前实现取 0.1) |
| 拖尾骨骼位置 | 0.05–0.1 像素(当前实现取 0.1) |
| 旋转 | 0.01° |
| 缩放 | 0.01 |
| 透明度 | 0.01 |
| RGB | 单通道 1/255 |
出生、死亡、附件切换、碰撞突变和循环接缝仍强制保留,不允许跨边界压缩。
### 12.4 强制保留点
以下位置不得跨越压缩:
- 粒子出生帧;
- 粒子死亡帧;
- 附件显示、隐藏和切换帧;
- 碰撞、反弹、暂停和销毁前后;
- 运动方向突然变化处;
- 拖尾出现和消失处;
- 循环首尾衔接区域;
- 延迟段和正式运动段边界;
- 旋转角度展开产生的边界。
### 12.5 旋转处理
压缩前先进行角度展开。例如:
```text
358° → 1° → 4°
```
应转换为:
```text
358° → 361° → 364°
```
避免 Spine 插值时反向旋转一整圈。
### 12.6 插值曲线设置
- `线性 (Linear)`:连续数据使用线性插值和线性误差精简;
- `平滑贝塞尔 (Smooth Bézier)`:对连续段拟合贝塞尔,并逐帧检查误差;
- 贝塞尔拟合不满足容差时必须增加关键帧,不能牺牲精度;
- 离散数据、突变点、碰撞边界和循环接缝不受平滑贝塞尔设置影响。
---
## 十三、导出设置标准
导出设置是全局编辑器设置,不属于单个粒子系统。必须参与:
- 保存配置;
- 加载配置;
- 预设加载;
- 撤回;
- 旧配置默认值补齐。
### 13.1 设置项与默认值
| 设置 | 默认值 | 规则 |
|---|---|---|
| 图片路径 | `./images/` | 写入 `skeleton.images` |
| Spine 版本 | `Spine 4.2` | 第一版唯一选项 |
| 关键帧插值曲线 | `线性 (Linear)` | 可切换平滑贝塞尔 |
| 导出 FPS | `30` | 支持 1~240 的正整数 |
| 不写 fps | 关闭 | 只省略 `skeleton.fps` 元数据 |
| 整数帧对齐 | 开启 | 关键帧位于导出 FPS 的整数帧网格 |
| 骨骼对象池 | 开启 | 复用粒子骨骼和拖尾链 |
| 排除无内容动画 | 关闭 | 关闭时保留空动画 `{}` |
| 默认文件名 | `SpineParticle.json` | 浏览器处理重复下载名称 |
### 13.2 导出 FPS 语义
修改导出 FPS 时保持动画实际秒数不变。
例如:
```text
30f / 30 FPS = 1 秒
60f / 60 FPS = 1 秒
```
不能在导出 60 FPS 时仍只输出 30 帧并把动画缩短为 0.5 秒。
“不写 fps”仅省略 Spine JSON 中的 FPS 元数据,内部烘焙、采样和时间换算仍使用导出 FPS。
### 13.3 整数帧对齐
开启时:
```text
time = integerFrame / exportFps
```
不能产生小数帧位置。无法整除的尾端时长向上取整,同时保持需要完成的粒子生命周期和拖尾状态。
---
## 十四、命名与文件规则
### 14.1 默认文件名
```text
SpineParticle.json
```
### 14.2 名称安全
导出器必须:
- 为系统、粒子、拖尾、Slot 和 Attachment 生成稳定名称;
- 处理空名称、重复名称和非法路径分隔符;
- 保留用户所属动画名称;
- 不在没有必要时修改用户可见的图片文件名;
- 保证同一配置、同一种子和同一导出设置产生稳定命名。
---
## 十五、代码结构标准
导出功能必须使用独立文件,不能把大量逻辑继续堆入 `ParticlePanel.vue`、`Stage.vue` 或 Store。
推荐结构:
```text
src/export/
├─ spineExportSettings.ts
├─ spineJsonTypes.ts
├─ particleExportBaker.ts
├─ spineMeshBuilder.ts
├─ spineKeyframeReducer.ts
├─ spineJsonExporter.ts
└─ spineJsonValidator.ts
```
职责:
- `spineExportSettings.ts`:导出设置类型、默认值和旧配置补齐;
- `spineJsonTypes.ts`:Spine 4.2 JSON 类型;
- `particleExportBaker.ts`:确定性逐帧模拟和外部关联解除;
- `spineMeshBuilder.ts`:拖尾骨骼链、权重、UV 和三角形生成;
- `spineKeyframeReducer.ts`:离散合并和连续时间轴误差精简;
- `spineJsonExporter.ts`:组装 Skeleton、Bones、Slots、Skins 和 Animations;
- `spineJsonValidator.ts`:导出前后结构验证和警告。
界面中的“导出Spine Json”按钮只负责调用导出入口、显示结果和下载文件。
---
## 十六、验证与验收标准
### 16.1 结构验证
导出前检查:
- 骨骼名称唯一;
- 父骨骼先于子骨骼;
- Slot 引用的骨骼存在;
- Skin 引用的 Slot 存在;
- Attachment Timeline 引用的附件存在;
- Mesh 骨骼索引有效;
- Mesh 权重和正确;
- UV、顶点和三角形索引数量匹配;
- 动画时间递增;
- 颜色为有效 RGBA;
- 所有数字有限,不出现 `NaN` 或 `Infinity`。
### 16.2 Runtime 验证
生成 JSON 后,使用项目内 Spine 4.2 Runtime 进行一次解析验证。解析失败时不应下载伪成功文件,应显示明确错误位置。
图片缺失不属于 JSON 结构失败,只显示警告。
### 16.3 视觉验证
至少对以下帧进行编辑器画布与导出结果对照:
- 第一帧;
- 粒子第一次出生帧;
- 最大粒子数量附近;
- 图片或序列帧切换帧;
- 碰撞发生前后;
- 路径或 Spine 跟随运动中间帧;
- 拖尾明显弯曲帧;
- 最后一帧;
- 纯循环首帧和末帧。
对照内容包括:
- 粒子数量;
- 粒子位置;
- 旋转与缩放;
- 图片资源;
- 颜色与透明度;
- 绘制顺序;
- 拖尾位置、方向、宽度和弯曲;
- 循环接缝。
### 16.4 文件体积验收
- 优先通过对象池、重复状态合并和误差受控关键帧精简减小文件;
- 拖尾必须使用加权 Mesh,不能用逐帧 Deform 换取实现简单;
- 不能为了缩小文件破坏碰撞、噪声、序列帧或循环精度;
- 对象池关闭时允许文件显著增大,但应给出预计规模提示。
---
## 十七、当前实施状态
截至本文落盘:
1. 导出范围、格式目标和关键规则已对齐;
2. 导出设置面板已实现;
3. 导出设置已接入保存、加载、预设、撤回和旧配置补齐;
4. 默认文件名已定义为 `SpineParticle.json`;
5. “导出Spine Json”按钮尚未接入 JSON 生成;
6. 正式导出代码必须等待用户明确的“开始”指令后实施;
7. 开始实现前应下载并核对 Spine 官方 GitHub 仓库中的 Spine 4.2 加权 Mesh 和多动画 JSON 示例。
---
## 十八、禁止偏离项摘要
以下规则未经重新确认不得改变:
1. 只导出可见粒子系统;
2. 不导出碰撞体、路径和原 Spine 对象;
3. 外部关联必须烘焙为独立动画;
4. 第一版只支持 Spine 4.2;
5. 图片文件不导出,只写正确路径;
6. 缺图不阻止导出;
7. 序列帧只使用一根粒子骨骼;
8. 拖尾必须使用加权 Mesh 和拖尾骨骼链;
9. 拖尾不得正式导出为逐帧 Deform;
10. 先逐帧烘焙,再安全精简关键帧;
11. Setup Pose 普通骨骼位移和旋转为 0、缩放为 1;
12. 拖尾子骨骼保留正确绑定链位置;
13. Setup Pose 图片和 Mesh 默认透明度为 0;
14. 修改 FPS 时保持实际秒数不变;
15. 空动画是否保留遵循“排除无内容动画”;
16. 默认文件名为 `SpineParticle.json`;
17. 导出功能必须拆分为独立代码模块;
18. 导出主要用途是重新导入 Spine 编辑器继续编辑。
+168
View File
@@ -0,0 +1,168 @@
# Spine 网页端粒子系统 · 开发计划(定稿 v1.0,已确认)
> **已确认的决策**
> - 产品形态:**独立编辑器/演示站**
> - 技术栈:**Vue 3 + Vite + TypeScript + Pinia**
> - Spine 预览运行时:**@pixi/spine + PixiJS 8**(主打 4.2,兼容 3.8/4.0/4.1 预览)
> - 粒子系统:自研轻量粒子池(预览用,勿黑盒库)
> - 导出:**A 方案 —— 工程配置 JSON**(不产出 Spine 动画 JSON;先做 A,MVP 优先,架构为后续 B 预留)
> - 部署:**纯前端静态构建 → Netlify/Vercel/GitHub Pages**,零后端
---
## 一、目标
让没有粒子系统的 Spine 动画,能在网页端**可视化地叠加粒子特效**。用户在网页里:
1. 上传/加载 Spine 骨骼动画(`.json + .atlas + .png`)或内置示例;
2. 在骨骼上配置粒子发射器(可**跟随骨骼**、调各种参数、应用预设);
3. **实时预览**效果;
4. 保存为"工程配置 JSON",下次可再次加载编辑。
> 对标:喵喵 Spine Particle / Spine Particle Studio。**注意**:本次范围是"编辑器+预览+存配置",暂不做那两个站的"导出 Spine 动画 JSON"核心烘焙功能(那属 B 方案,列为后续扩展,见第八节)。
---
## 一·补充、参考站实测核对(已于浏览器读取确认)
### 站1:喵喵 Spine Particle v0.14.1
- 保存/加载配置;**场景对象=粒子系统**(名称/所属动画/系统X/Y/旋转/ScaleX/ScaleY/**参与碰撞 Collision Filter+碰撞标签**)
- 发射:爆发/持续、延迟(秒/帧)、数量、形状(点/圆/矩形/锥体/**轨道**)、方向(随机/固定角度世界/局部/跟随轨道)
- 属性:初始速度/生命周期/缩放(统一与XY分离)/旋转(各支持 固定/随机/**曲线**)、生命周期内变化
- 外观:透明度(固定/曲线,跟随导出/强制线性/强制贝塞尔)、图片(单图/序列/子文件夹)、颜色(固定色/生命周期)、混合模式(正常/相加/相乘/滤色)、缩放/轴心/独立透明度
- 修改器:**发射器跟随**/移动噪声/重力/风力/力场(吸斥)/拖尾
- **导出 Spine JSON**(Spine 3.8/4.0/4.1/4.2、关键帧插值线性/贝塞尔/阶跃、导出FPS、整数帧对齐、**骨骼对象池**、导出骨架、排除无内容动画、图片路径)
- 操作:中键平移、Ctrl+滚轮缩放、Ctrl+Z 撤销、G/L 全局局部坐标、时间轴
- 附:页面头部含**官方飞书文档** <https://ecn2wbmd52pg.feishu.cn/docx/I0rVdOWMoofdzDxZGxDcKOqZnVb>
### 站2:Spine Particle Studio
- 系统列表/名称/粒子位置X/Y/发射器旋转
- 贴图:模式(单图/序列帧/雪碧图)、**内置贴图**(发光星/柔光点/发光环/六角雪花/十字火花/闪耀菱形)+ 自定义上传
- 发射:爆发/持续、方向/吸收(向外/向内)、数量、延迟、总时长、循环/无缝循环/**预热**、形状(点/圆/矩形/扇形)、发射角度/偏移
- 粒子属性:速度/生命/透明度/缩放(固定/随机/曲线,且带**曲线绘制器** 基础值/重置/START/LIFE TIME/END)、初始旋转模式(固定/随机/方向)、旋转模式(速度/曲线)、颜色(固定/生命周期)、混合模式
- 物理与随机:速度抑制/重力/抖动/移动噪声/吸引力场/**碰撞与阻碍物**/随机种子/路径跟随/死亡触发/**拖尾子粒子**(主粒子<20)
- 背景:参考图/上传背景/显示背景/透明度
- **导出 Spine JSON**(Spine 3.8/4.0/4.1/4.2、导出FPS、图片路径)、UI 主题、撤回0/10
### 两站共性(确认)
> 都是一个**粒子特效编辑器**:可视化配置 → 实时预览 → **导出为 Spine 骨骼动画 JSON** 供并入 Spine 工程。站1 更强调"骨骼跟随+骨骼对象池+曲线插值";站2 更强调"内置贴图+物理+拖尾子粒子+曲线绘制器"。
### 对本次 A 方案的定位(重要)
> 本次(A 方案)做的是完整"编辑器+实时预览+工程配置 JSON 存取",**不含**上述"导出 Spine JSON 烘焙"。两者的功能清单(属性/发射/修改器/贴图)在**预览层**基本一致,只是导出层不同;因此做 A 时,除导出外其余功能均按上述清单实现,架构为 B(导出)预留,见第八节。
---
## 二、核心功能清单
| 模块 | 明细 |
|---|---|
| 资源加载 | 上传 `.json+.atlas+.png` / 内置示例(`sample.json` 4.2.43 可当测试数据) |
| 动画控制 | 播放/暂停/停止、循环、动画切换、时间缩放、皮肤切换、骨架缩放/位移 |
| **粒子系统** | 发射器(爆发/持续)、发射形状(点/圆/矩形/扇形/锥体)、发射方向/角度、粒子数量、延迟、时长 |
| 粒子属性 | 速度/生命/透明度/缩放/旋转(各支持 固定/随机/曲线)、颜色(固定/生命周期)、混合模式 |
| **骨骼跟随** | 发射器绑定到某根骨骼/插槽,原点随骨骼逐帧更新(核心特性) |
| 修改器(可选增强) | 重力、风力、移动噪声、拖尾、力场 |
| 贴图资源 | 内置(发光星/柔光点/发光环/雪花/火花/菱形)+ 用户上传;支持 单图/序列帧/雪碧图 |
| 编辑器 UI | 场景对象(粒子层)管理、参数面板(滑块/颜色选择/曲线编辑)、预设模板 |
| 保存/加载 | 导出/导入**工程配置 JSON**;支持撤销/重做、随机种子 |
| 操作习惯 | 中键平移、滚轮缩放 |
---
## 三、技术选型
| 层 | 选型 |
|---|---|
| 前端 | Vue 3 + Vite + TypeScript + Pinia |
| Spine 渲染(预览) | @pixi/spine(runtime-4.2 主,兼容 3.8/4.0/4.1)+ PixiJS 8 |
| 粒子预览 | 自研粒子池(Pixi ParticleContainer / 自定义 WebGL 精灵) |
| 状态 | Pinia |
| 部署 | Vite 静态构建 → 静态托管 |
---
## 四、核心设计(预览层)
- 编辑器画布 = Spine 骨骼渲染 + 若干 `ParticleEmitter`。
- `ParticleEmitter` 管理一个池化粒子容器;每帧:发射原点 = 所属骨骼世界坐标 + 偏移 → 发射粒子 → 更新(位置/速度/重力/寿命/颜色/旋转/alpha)→ 渲染。
- 参数用**归一化数据模型**描述(固定 = 定值 / 随机 = [min,max] / 曲线 = 贝塞尔控制点),同一份模型驱动预览渲染与将来导出。
- 贴图:内置一组 + 用户上传;支持单图/序列帧/雪碧图。
---
## 五、代码结构(建议)
```
SpineParticle/
├─ index.html
├─ vite.config.ts
├─ package.json
├─ src/
│ ├─ main.ts
│ ├─ App.vue
│ ├─ core/
│ │ ├─ spineLoader.ts # json/atlas/png 加载解析、多版本
│ │ ├─ particleEmitter.ts # 预览粒子发射器(池化)
│ │ ├─ particleSystem.ts # 多发射器/帧更新/骨骼跟随
│ │ ├─ curves.ts # 固定/随机/贝塞尔 求值
│ │ ├─ presets.ts # 内置贴图 + 预设模板
│ │ └─ types.ts # 数据模型 + 工程配置结构
│ ├─ views/
│ │ ├─ Stage.vue # 场景/预览画布
│ │ ├─ AnimationPanel.vue # 动画控制
│ │ ├─ ParticlePanel.vue # 粒子层列表 + 参数面板
│ │ └─ PresetPicker.vue # 预设模板
│ └─ store/
│ └─ particleStore.ts # spine 资源 + 粒子层状态
└─ 开发计划.md
```
---
## 六、开发阶段(先 MVP 后增强)
| 阶段 | 内容 | 产出 |
|---|---|---|
| **0 需求确认** | 完成(本计划定稿) | — |
| **1 技术验证 Spike** | pixi-spine 加载 `sample.json` 并播放;**一个粒子点跟随 `symbols` 骨骼** | 最小可演示 |
| **2 Spine 预览核心** | 资源加载(上传+内置)、动画/皮肤/缩放/控制、时间轴 | 可操作现有骨骼 |
| **3 粒子预览引擎** | 粒子池、发射器、基础参数、曲线求值、**骨骼跟随** | 骨骼上长出粒子 |
| **4 编辑器 UI** | 场景对象管理、参数面板(滑块/颜色/曲线)、内置贴图+预设 | 可视化编辑 |
| **5 保存/加载** | 导出/导入工程配置 JSON、撤销/重做、随机种子 | 可保存/分享 |
| **6 部署上线** | Vite 构建 → 静态托管,验证 URL | 线上可用 |
> 每阶段都有可运行成果,可随时停下验收。
---
## 七、验收标准(阶段 6 完成时)
- 打开网页 → 加载内置示例(或用你的 `sample.json`)→ 播放动画;
- 在任意骨骼上添加粒子层 → 粒子**跟随骨骼**运动;
- 通过面板调节速率/寿命/速度/颜色/缩放/alpha/重力等 → 实时可见;
- 切换预设 vs 自定义 → 生效;
- 保存工程配置 → 刷新后加载还原;
- 部署到静态托管,公网可访问。
---
## 八、后续扩展(本次不做,已明确后置)
**用户决策:不做烘焙导出,本次只做预览。烘焙这一"大头功能"放后续做。**
- **烘焙导出(后置大目标,即 B 方案)**:把粒子效果**烘焙导出为 Spine 骨骼动画 JSON**(对象池骨骼、逐帧采样、曲线插值线性/贝塞尔/阶跃、fps/整数帧对齐、多版本 3.8/4.0/4.1/4.2 schema)。本项目的"归一化数据模型 + 骨骼跟随"已为此预留,将来平滑接入,无需推翻现有架构。
- 物理系统补充:碰撞体/阻碍物、路径跟随、死亡触发、拖尾子粒子、物理吸引力场。
- 多版本**导出**支持(预览层面本项目已支持多版本)。
> 本次 A 方案与 B 方案只在"导出层"不同:预览层的功能清单(属性/发射/修改器/贴图)两者一致,因此当前实现的每一项都会成为将来 B 方案的直接输入。
---
## 附:交付物
- 一个可运行、可部署的 Vue 3 单页应用(代码 + `package.json`);
- 支持上传 Spine 资源、可视化配置粒子、骨骼跟随、实时预览、存/取配置;
- 开发计划本文件。
---
> ⚠️ 由于需在浏览器里实际调 spine + 粒子渲染与交互,**建议边开发边在浏览器里目视验收**,因此在阶段 1 用你的 `sample.json` 做技术验证(Spike)是关键第一步。
+74
View File
@@ -0,0 +1,74 @@
# 粒子系统开发 · 阶段一总结
定位:一个网页端粒子发射编辑器(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 让**粒子相对黄点额外位移**,物理/力场源锚在锚点。
- 一个粒子系统 = 一个坐标点;无系统时画布无边线;选中系统高亮黄色,其余淡灰蓝。
- `totalFrames` 由 `delay + 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**:`copyFrom`、`setValue(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 算法**(一次全出、无持续窗口)可能与预期不同,值得确认。
- 代码是反复迭代出来的,个别函数较长、命名一般;若有长期维护打算,建议做一轮小清理(非必须)。
+368
View File
@@ -0,0 +1,368 @@
# 网页粒子系统 · 阶段七
> 阶段定位:**Spine 4.2 骨架资源接入、统一场景变换与碰撞体路径跟随定版**
> 完成日期:2026-08-29
> 工程目录:`/Users/tianmokeji/Desktop/SpineParticle`
> 技术栈:Vue 3 + TypeScript + Pinia + PixiJS 7 + Spine Runtime 4.2 + Vite
---
## 一、阶段结论
阶段七完成了 Spine 骨架场景对象的第一版完整接入,并补齐了时间轴、场景变换和碰撞体路径跟随能力。Spine 资源现在可以离线加载、显示、选择动画、查看骨骼层级,并与粒子系统共同受统一的 30 FPS 时间轴控制。
本阶段主要完成:
1. 接入 Spine 官方 4.2 PixiJS 7 运行时并离线落盘;
2. 实现 Spine 骨架场景对象及独立属性面板;
3. 支持 JSON、SKEL、Atlas 和多张纹理资源加载;
4. 自动读取动画列表、动画时长和骨骼层级;
5. Spine 动画关闭自身循环,改由编辑器时间轴统一驱动;
6. 时间轴增加紫色 Spine 动画条和逐帧控制按钮;
7. 修复动画按浏览器刷新次数推进导致的播放过快问题;
8. 统一画布变换工具,使其作用于场景树当前选中的对象;
9. 为碰撞体和路径补充缩放变换;
10. 实现碰撞体沿路径移动、速度曲线和方向跟随;
11. 统一“路径”术语,移除重复的移动轨迹显示开关;
12. 完成旧场景字段补齐和热更新兼容处理。
---
## 二、Spine 官方运行时接入
### 2.1 运行时来源
项目使用 Spine 官方仓库中的 4.2 兼容版本:
| 项目 | 内容 |
|---|---|
| 官方仓库 | `https://github.com/EsotericSoftware/spine-runtimes` |
| 兼容分支 | `4.2` |
| 固定提交 | `b81e5a58ed38704aee4f866f0e0ac672623ce914` |
| `spine-core` | `4.2.119` |
| `spine-pixi-v7` | `4.2.119` |
| 渲染目标 | PixiJS 7 |
运行时代码放置在 `vendor/spine/`,通过 `file:` 依赖接入:
```text
@esotericsoftware/spine-core -> vendor/spine/spine-core
@esotericsoftware/spine-pixi-v7 -> vendor/spine/spine-pixi-v7
```
因此项目构建时不需要从 npm 在线下载 Spine 运行时代码。版本来源、提交号和许可提示记录在 `vendor/spine/SOURCE.md`,官方许可证保存在同目录中。
### 2.2 代码拆分
Spine 功能没有继续堆叠到主面板和主画布文件中,而是拆分为:
| 文件 | 职责 |
|---|---|
| `spineTypes.ts` | Spine 场景对象、动画和骨骼元数据类型 |
| `spineAssetLoader.ts` | 读取骨架、Atlas 和纹理并生成 SkeletonData |
| `spineAssetRegistry.ts` | 管理运行时资源和纹理释放 |
| `SpineRuntimeLayer.ts` | 创建 Spine 显示对象并按编辑器帧同步动画 |
| `SpineObjectPanel.vue` | 资源选择、属性、动画列表和层级树界面 |
这种拆分让主粒子系统逻辑与第三方骨架运行时保持边界,便于后续增加插槽、皮肤、混合和导出功能。
---
## 三、Spine 场景对象
### 3.1 创建与默认状态
点击场景对象模块中的 `+骨架` 会创建一个 Spine 对象,并自动成为当前选中对象。
| 参数 | 默认值 |
|---|---:|
| 名称 | `SpineSkeleton1` |
| 启用 | 开启 |
| X / Y | 0 / 0 |
| 旋转 | 0° |
| Scale X / Y | 1 / 1 |
| 开始时间 | 0 秒 |
| 资源状态 | 未加载 |
| 当前动画 | 无 |
Spine 对象与粒子系统、碰撞体和路径统一显示在场景对象树中,支持选择和删除。删除对象时会同步释放已注册的 Spine 资源和纹理引用。
### 3.2 支持的资源
加载一套 Spine 资源时需要选择:
- 骨架数据:`.json` 或 `.skel`;
- 图集描述:`.atlas`;
- 纹理图片:`.png`、`.jpg`、`.jpeg`、`.webp`,允许多选;
- 兼容文件选择器中的 `.wepb` 拼写。
加载器按 Atlas 页面的文件名匹配所选纹理。如果 Atlas 引用了未选择的纹理,会给出明确错误,不会创建不完整的骨架对象。
### 3.3 测试资源
项目 `public/spine/` 中的 Spine 4.2 测试资源用于本阶段验证:
- `bingo.json`;
- `bingo.atlas`;
- `bingo.png`;
- `bingo_2.png`。
测试覆盖了 JSON 骨架、多纹理 Atlas、动画列表读取、画布渲染和骨骼层级展示。
---
## 四、动画与骨骼层级
### 4.1 动画列表
资源加载成功后,面板会从 SkeletonData 中读取全部动画:
- 显示动画名称;
- 显示真实动画时长,单位为秒;
- 支持选择“无”或任意动画;
- 默认选择资源中的第一个动画;
- 切换动画后重新计算时间轴总长度并回到第 0 帧。
Spine 动画自身不循环播放。动画到达末尾后停留在末帧,是否重新从头播放由编辑器总时间轴的循环按钮决定。
### 4.2 骨骼层级树
加载成功后同时读取骨骼名称、父级关系和深度,按层级显示为树结构。
层级树支持:
- 点击父骨骼前的箭头折叠或展开子级;
- “全部折叠”和“全部展开”;
- 折叠父级后隐藏其全部后代;
- 长层级树在固定高度区域内滚动。
该树当前用于查看资源结构,尚未提供骨骼选择、单骨骼变换或插槽编辑。
### 4.3 Spine 资源显示设置
原属性面板中容易误解的“启用骨架显示”已改为系统设置中的“启用 Spine 资源显示”。
- 已加载包含动画的 Spine 资源时,可以全局显示或隐藏 Spine 资源;
- 没有可用 Spine 动画时,该设置置灰;
- 该开关只控制画布中的 Spine 资源,不影响编辑器自身的粒子拖尾骨骼、骨骼连线或蒙皮网格调试显示;
- 对象自身的 `enabled` 状态仍会独立参与最终可见性判断。
---
## 五、统一时间轴
### 5.1 真实时间驱动
时间轴继续使用统一的 **30 FPS** 基准:
```text
1 秒 = 30 帧
当前动画时间 = 当前帧 / 30 - Spine 开始时间
```
修复前,Spine 动画会在浏览器每次刷新画面时前进一帧。高刷新率屏幕上动画会明显快于 Spine 编辑器中的同一动画。
现在使用实际经过时间累积,并按 `1/30` 秒的固定步长推进显示帧。浏览器刷新率只影响画面刷新平滑度,不再改变动画播放速度。
### 5.2 Spine 紫色动画条
选择有效 Spine 动画后,时间轴新增紫色动画条:
- 条带长度来自 Spine 动画的真实时长;
- 长度不可拖动修改;
- 整条可以左右拖动;
- 左侧空白表示延迟播放时间;
- 拖动以 1 帧为单位吸附;
- 没有动画数据或当前动画为“无”时不显示。
Spine 动画、粒子系统和碰撞体路径移动共同参与总时间轴长度计算:
```text
总时长 = max(
粒子系统最晚消失时间,
Spine 开始时间 + Spine 动画时长,
碰撞体路径移动时长
)
```
### 5.3 播放控制
时间轴控制区现在包括:
| 控制 | 行为 |
|---|---|
| 播放 / 暂停 | 切换整体时间轴播放状态 |
| 回到第一帧 | 跳转到第 0 帧 |
| 前一帧 | 自动暂停并后退 1 帧 |
| 后一帧 | 自动暂停并前进 1 帧 |
| 移动到最后一帧 | 自动暂停并跳转到最后帧 |
| 循环 | 控制整体时间轴到达末尾后的行为 |
这些操作统一影响粒子模拟、Spine 动画和碰撞体路径移动。
---
## 六、场景对象变换统一
### 6.1 当前选中对象
画布左上角的位移、旋转和缩放工具不再固定操作粒子系统,而是读取场景树中的当前选中对象。
支持的对象包括:
- 粒子系统;
- 碰撞体;
- 路径;
- Spine 骨架。
选择 Spine 对象后,变换工具只修改该 Spine 对象;选择碰撞体或路径后,同样只影响选中对象。
### 6.2 缩放补齐
碰撞体新增 `scaleX`、`scaleY`,路径继续使用独立的 `scaleX`、`scaleY`。两类对象均可通过属性面板或画布缩放工具修改。
路径的位移、旋转和缩放会同时作用于:
- 路径曲线显示;
- 锚点和贝塞尔手柄;
- 发射器路径采样;
- 碰撞体路径跟随。
碰撞体缩放同时影响画布轮廓和实际粒子碰撞计算。
---
## 七、碰撞体路径跟随
阶段六预留的“跟随路径移动”在本阶段完成运行时实现。
### 7.1 默认参数
| 参数 | 默认值 |
|---|---:|
| 跟随路径移动 | 关闭 |
| 目标路径 | 未选择 |
| 移动时长 | 2 秒 |
| 曲线模式 | 跟随导出 |
| 移动进度曲线 | 0→1 线性 |
| 方向沿路径 | 关闭 |
只有开启跟随并选择一条已启用且包含锚点的路径后,碰撞体才会开始移动。无效或已删除的目标路径不会改变碰撞体原始位置。
### 7.2 移动计算
```text
归一化时间 = clamp(当前时间 / 移动时长, 0, 1)
路径进度 = 移动进度曲线(归一化时间)
碰撞体位置 = 路径按长度采样(路径进度)
```
路径采样会考虑路径对象自身的位移、旋转和缩放。每段三次贝塞尔曲线先离散采样并累计实际距离,再按总长度查找位置,减少不同曲线段参数长度不一致造成的明显速度变化。
面板提供三种曲线模式:
- 跟随导出;
- 线性;
- 贝塞尔。
曲线编辑器横轴表示实时除以移动时长,纵轴表示路径进度。到达移动时长后,碰撞体停留在路径终点;编辑器时间轴循环时再从起点开始。
### 7.3 方向沿路径
“方向沿路径”默认关闭:
- 关闭时,碰撞体移动过程中保持自己的旋转值;
- 开启时,根据路径当前位置前后采样点计算切线方向;
- 碰撞体属性中的旋转值作为附加角度叠加,可继续微调朝向。
圆形碰撞体视觉上不容易观察旋转变化,矩形、椭圆和多边形更适合检查该功能。
### 7.4 碰撞运行时
路径跟随不仅改变画布上的碰撞体预览位置,也会把同一份动态位置和旋转传给粒子碰撞检测。因此粒子会与移动中的碰撞体发生真实交互,物理、暂停和销毁三种响应保持有效。
运行时使用预览副本计算动态位置,不会持续覆盖碰撞体存储的基础 X、Y 和旋转值。关闭跟随后,碰撞体会恢复到属性面板中设置的基础变换。
路径对象本身始终在画布显示,因此移除了重复的“显示移动轨迹线”开关和额外轨迹覆盖层。
---
## 八、术语与兼容性
### 8.1 路径术语统一
用于贝塞尔对象和跟随功能的界面术语统一为“路径”:
- 跟随路径移动;
- 目标路径;
- 路径进度;
- 路径名称;
- 闭合路径;
- 路径颜色;
- 发射器跟随路径。
发射器形状中的“轨道”仍表示粒子发射形状 `orbit`,不是场景路径对象,因此保留原名称。
### 8.2 旧数据补齐
旧场景或热更新状态中的碰撞体可能缺少路径跟随字段。面板会即时补齐:
- 目标路径;
- 移动时长;
- 曲线模式;
- 移动进度曲线;
- 方向沿路径。
曲线组件同时提供空值回退,避免旧数据加载或热更新瞬间因曲线为空导致界面渲染失败。
---
## 九、验证结果
本阶段完成以下验证:
- Spine 官方 4.2.119 本地运行时依赖和生产构建;
- JSON、Atlas 和两张 PNG 测试资源加载;
- 动画名称、真实时长和骨骼层级读取;
- Spine 动画非循环播放及末帧保持;
- 30 FPS 真实时间推进,不受浏览器刷新率影响;
- 紫色 Spine 时间轴条长度和开始时间拖动;
- 前一帧、后一帧和最后一帧自动暂停;
- 骨骼层级单节点折叠和全部折叠;
- Spine 资源全局显示开关和无动画时置灰;
- 当前选中粒子、碰撞体、路径和 Spine 对象的画布变换;
- 碰撞体和路径缩放与运行时表现同步;
- 碰撞体目标路径、移动时长、进度曲线和方向开关;
- 移动碰撞体参与实际粒子碰撞;
- 路径术语统一及重复轨迹开关移除;
- 旧碰撞体字段和曲线空值兼容;
- 页面面板交互检查;
- `npm run build` 生产构建通过;
- `git diff --check` 通过。
构建仍有 Vite 对主产物体积超过 500 kB 的提示,不影响当前功能和构建结果,后续可通过按模块动态加载进一步优化。
---
## 十、当前限制
1. Spine 资源通过本地文件选择器加载,刷新页面后需要重新选择,尚未纳入工程保存格式;
2. Spine 层级树当前只读,不能编辑骨骼、插槽、皮肤或约束;
3. Spine 动画尚未支持混合、播放速度、反向和多轨道叠加;
4. `.skel` 已接入官方二进制解析器,但本阶段主要使用 JSON 测试资源验证;
5. 碰撞体路径跟随当前为单次起点到终点,尚未提供往返或独立循环模式;
6. 自定义碰撞体仍使用规则多边形轮廓,尚未接入自由顶点编辑;
7. 场景对象和资源尚未形成完整的保存、导入和导出格式。
---
## 十一、阶段八建议
1. 建立场景工程保存格式,持久化粒子、路径、碰撞体和 Spine 对象配置;
2. 将 Spine 资源复制到工程资源目录,支持刷新后自动恢复;
3. 增加 Spine 皮肤、插槽、混合时长和播放速度控制;
4. 为碰撞体路径跟随增加循环、往返、反向和结束行为;
5. 实现碰撞标签过滤和碰撞事件输出;
6. 实现自定义碰撞体自由顶点编辑;
7. 对 Spine 和曲线编辑器进行动态加载,降低主包体积;
8. 建立可复现的场景回归测试,覆盖统一时间轴和多对象组合。
+279
View File
@@ -0,0 +1,279 @@
# 网页粒子系统 · 阶段三
> 阶段定位:**粒子属性与外观资源定版**
> 完成日期:2026-08-28
> 工程目录:`/Users/tianmokeji/Desktop/SpineParticle`
> 技术栈:Vue 3 + TypeScript + Pinia + PixiJS 7 + Vite
---
## 一、阶段结论
阶段三已完成以下两块核心能力:
1. **粒子属性面板**:初始生命周期、速度、缩放、旋转,以及生命周期内速度、缩放、旋转,均已形成统一、可复用的调节结构,并接入粒子计算。
2. **外观与资源面板**:支持多图片资源混合发射、每张图片独立的占比、缩放、轴心、颜色、混合模式和透明度;生命周期颜色已升级为可编辑的多色渐变。
当前项目已经具备较完整的“参数编辑 → 实时预览 → 固定帧录制/回放”闭环。接下来不建议继续扩充同一面板,而应进入**修改器完善、工程保存/加载和导出准备**。
---
## 二、粒子属性模块完成情况
### 2.1 粒子初始参数
| 属性 | 已支持模式 | 补充说明 |
|---|---|---|
| 初始生命周期 | 固定、随机 | 曲线选项已按最终需求移除 |
| 初始速度 | 固定、随机 | 实时影响粒子初速度 |
| 初始缩放 | 固定、随机 | 支持统一缩放与 XY 分离 |
| 初始旋转 | 固定、随机、发射方向 | 发射方向模式可设置角度偏移 |
初始参数使用统一的 `ParticleAttributeControl.vue`,避免每种属性重复实现模式按钮、数值范围和曲线区域。
### 2.2 生命周期内属性
- 生命周期内速度:独立开关,支持固定、随机、曲线倍率。
- 生命周期内缩放:独立开关,支持固定、随机、曲线,并支持统一与 XY 分离。
- 生命周期内旋转:独立开关,支持固定、随机、曲线旋转速度。
- 曲线调节结果已经接入 `ParticleEmitter.update()`,不再是只改变界面的空配置。
- 每个曲线编辑器右侧均提供重置按钮,可恢复该属性的默认曲线。
- 随机种子支持手动输入和快速生成;它是场景对象下的全局属性,控制初始属性、资源选择和修改器随机采样,包括 0 在内的任意整数种子均可复现相同发射序列。
### 2.3 曲线编辑器
当前曲线编辑器支持:
- 拖动关键点;
- 双击空白处新增关键点;
- 双击关键点删除;
- 最少保留两个关键点;
- 按属性传入不同默认曲线并单独重置;
- 生命周期横轴与归一化数值纵轴求值。
---
## 三、外观与资源模块完成情况
### 3.1 系统透明度
- 支持固定透明度和生命周期曲线透明度。
- 固定模式直接使用固定值,不再受到透明度曲线影响。
- 曲线模式复用 `CurveEditor.vue`,支持关键点编辑和重置。
### 3.2 多图片资源
每个粒子系统默认使用 `star.png`,并支持:
- 点击缩略图选择本地 PNG、JPEG、WebP 或 GIF 图片;
- 通过右上角 `+` 添加多张图片资源;
- 每次发射按资源占比随机选择图片;
- 单资源时占比强制为 100%;
- 多资源时总占比始终保持 100%;
- 可锁定某张图片的当前占比,调整其他资源时只重新分配未锁定资源;
- 每张图片独立设置缩放和轴心 X/Y;
- 删除资源后自动重新分配占比;
- 重置资源卡后恢复默认图片、名称、缩放、轴心、颜色、混合模式、透明度和占比状态。
粒子状态中记录 `resourceId`,录制和回放时能够恢复对应的贴图与轴心,不会全部退回默认图片。
### 3.3 每张图片独立颜色
每张资源卡独立支持:
- 固定色;
- 生命周期颜色;
- 正常、相加、相乘、滤色四种混合模式。
固定色可通过系统颜色选择器或十六进制输入框精确设置。输入支持:
- `#71ffae`;
- `71ffae`;
- `#fff` 等三位简写。
无效颜色值不会覆盖当前颜色。
### 3.4 生命周期多色渐变
新增 `ColorGradientEditor.vue`,生命周期颜色不再局限于起始色和结束色,而是支持任意数量色标:
- 点击 `+` 在当前最大色标间隔中添加颜色;
- 双击色带,在指定生命周期位置添加颜色;
- 拖动色标调整位置;
- 输入 0~100 的百分比精确设置位置;
- 使用颜色选择器或十六进制输入框精确调色;
- 双击色标或点击删除按钮移除色标;
- 最少保留两个色标;
- 粒子颜色按生命周期依次经过全部色标。
预览阶段使用**线性 RGB 插值**计算相邻色标间的颜色。
### 3.5 每张图片独立透明度
每张资源卡支持三种模式:
| 模式 | 行为 |
|---|---|
| 关闭 | 跟随粒子系统透明度设置 |
| 固定 | 使用该图片自己的固定透明度,不受系统透明度曲线影响 |
| 曲线 | 使用该图片自己的生命周期透明度曲线 |
独立曲线同样支持关键点编辑、曲线模式选择和恢复默认曲线。
---
## 四、界面与交互修正
- 左侧属性面板、右上画布和右下时间轴固定占满浏览器视口。
- 页面根节点禁止产生额外纵向滚动,只有各自面板内部滚动。
- 修复展开属性内容后页面下方出现大面积空白的问题。
- 曲线重置按钮统一放在曲线面板右侧,不覆盖绘图区。
- 所有数字输入框隐藏浏览器自带的上下微调箭头。
- 数值仍可通过滑块、直接输入和键盘方向键调节。
- 图片占比锁定、删除、重置等状态均提供禁用和高亮反馈。
---
## 五、核心数据与运行逻辑
### 5.1 `ParticleImageResource`
图片资源已经从简单路径扩展为完整的独立配置单元,主要包含:
- 资源标识、纹理、名称、目录、预览地址;
- 占比与锁定状态;
- 缩放与轴心;
- 固定色或生命周期渐变;
- 混合模式;
- 独立透明度模式、固定值和曲线。
### 5.2 发射与更新
1. `spawnOne()` 根据占比选择图片资源。
2. 创建粒子时应用该资源的纹理、轴心、缩放、初始颜色和混合模式。
3. `update()` 根据粒子的 `resourceId` 查找资源配置。
4. 每帧更新该资源的生命周期颜色、独立透明度和混合模式。
5. `capture()` / `apply()` 记录并恢复资源标识,保证时间轴回放一致。
### 5.3 旧配置兼容
`ensureEmitterConfig()` 会为热更新对象或旧工程配置补齐新增字段,包括图片权重、锁定、颜色渐变和独立透明度,避免 `undefined` 进入粒子计算。
---
## 六、本阶段新增或重点调整的文件
| 文件 | 作用 |
|---|---|
| `src/views/ParticleAttributeControl.vue` | 复用固定、随机、曲线和发射方向属性结构 |
| `src/views/ColorGradientEditor.vue` | 多色生命周期渐变编辑器 |
| `src/views/CurveEditor.vue` | 曲线关键点编辑与默认曲线重置 |
| `src/views/NumSlider.vue` | 滑块与数字输入,支持禁用状态 |
| `src/views/ParticlePanel.vue` | 粒子属性及外观资源面板 |
| `src/core/particleEmitter.ts` | 属性、图片权重、颜色、透明度的实际运行逻辑 |
| `src/views/Stage.vue` | 默认纹理注入、多资源录制/回放配置监听 |
| `src/style.css` | 视口滚动约束、数字微调箭头统一隐藏 |
---
## 七、验证结果
本阶段进行了浏览器交互测试和生产构建验证,已确认:
- 三个生命周期属性同时展开不会产生页面级额外滚动;
- 固定透明度不受曲线影响;
- 多图片能够同时发射并正确恢复贴图;
- 单图片为 100%,多图片总占比为 100%;
- 三资源场景下锁定 33%,将第二项调为 50%,第三项自动变为 17%;
- 资源卡重置可恢复缩放 1、轴心 0.5/0.5 和全部外观默认值;
- 不同资源可分别使用不同颜色、混合模式和透明度;
- 多色渐变可新增第三个色标并精确修改颜色与位置;
- 固定色和色标颜色的十六进制输入可同步到颜色控件;
- `npm run build` 通过;
- `git diff --check` 通过。
Vite 仍会提示主包大于 500 kB,这是体积警告,不影响当前运行。
---
## 八、当前边界与已知风险
1. **本地图片只存在于当前运行内存**:目前使用 Data URL 和 Pixi Texture,尚未实现工程文件持久化、重新加载或资源打包。
2. **仅支持单张静态纹理资源**:序列帧、雪碧图和视频纹理尚未实现。
3. **曲线模式目前主要是配置语义**:界面提供“跟随导出 / 线性 / 贝塞尔”,当前预览求值仍以关键点间线性插值为主,贝塞尔求值和导出规则需要后续统一实现。
4. **渐变使用线性 RGB 插值**:暂未提供颜色空间选择、独立 Alpha 色标或 HDR 颜色。
5. **修改器完成度不一致**:重力、风力、力场和阻尼已有运行逻辑;移动噪声、拖尾、碰撞、路径跟随仍需实现或补齐。
6. **粒子池上限固定为 400**:复杂多系统场景需要性能压测和可配置上限。
7. **工程配置没有正式版本协议**:当前仅通过字段补齐兼容旧对象,进入保存/加载阶段前应定义 schema version 和迁移规则。
8. **缺少自动化测试**:当前以 TypeScript 构建和浏览器交互验证为主。
---
## 九、下一步开发建议
### 优先级 P0:工程保存与加载
建议优先建立正式的工程配置格式,这是后续预设、分享和导出的基础。
- 定义 `ProjectSchema` 和 `schemaVersion`;
- 将 Pixi Texture 等运行时对象与可序列化配置分离;
- 保存图片资源信息和颜色/曲线/渐变数据;
- 支持导出、导入工程 JSON;
- 明确本地图片的持久化方案:Data URL 内嵌、ZIP 工程包或 File System Access API;
- 为旧版本配置提供迁移函数。
### 优先级 P1:修改器模块定版
按现有面板顺序继续开发,避免跨模块扩散:
1. 移动噪声:确定噪声类型、频率、强度和种子关系;
2. 重力、风力、力场:复核数学坐标系下的方向与单位;
3. 拖尾:先定义数据结构与性能上限,再做渲染;
4. 碰撞:从简单矩形/圆形碰撞体开始;
5. 路径跟随:与场景对象中的轨道路径统一设计。
### 优先级 P2:撤销/重做与预设
- 建立配置级历史记录,不记录每帧粒子状态;
- 支持 Ctrl/Cmd + Z、Ctrl/Cmd + Shift + Z;
- 为火花、烟雾、雪花、光点等建立预设;
- 预设只保存可序列化配置和资源引用。
### 优先级 P3:曲线与渐变能力统一
- 抽离统一的曲线求值模块;
- 真正实现线性、贝塞尔、阶跃插值;
- 明确“跟随导出”在预览阶段的行为;
- 为颜色渐变增加可选 Alpha 色标;
- 让曲线和渐变共享键盘操作、选中和删除规则。
### 优先级 P4:导出准备
- 固化 `ParticleState` 到逻辑骨骼关键帧的映射;
- 明确图片资源、混合模式和颜色时间轴在 Spine 版本中的表达;
- 先输出内部中间格式,再适配 Spine 3.8 / 4.0 / 4.1 / 4.2;
- 对固定帧采样、整数帧对齐和对象池骨骼数量进行验证。
### 工程质量建议
- 将 `EmitterConfig`、资源类型、曲线工具拆分到独立文件;
- 为占比重分配、曲线求值、颜色渐变和配置迁移增加单元测试;
- 对 1、5、20 个系统及 400 粒子上限进行性能测试;
- 在进入导出阶段前处理 Vite 主包体积和按需加载。
---
## 十、下一阶段推荐验收目标
建议把阶段四定义为:**修改器完善 + 工程配置保存/加载**。
阶段四完成时应满足:
1. 当前工程可保存为带版本号的配置文件;
2. 刷新页面后导入配置,可恢复全部系统、图片资源、颜色、曲线和渐变;
3. 自定义图片有明确的持久化方案,不依赖旧页面内存;
4. 移动噪声、重力、风力、力场和阻尼行为经过坐标方向验证;
5. 拖尾、碰撞和路径跟随明确为“已实现”或“正式后置”,不再处于含糊占位状态;
6. 关键数据算法具备基础自动化测试。
完成这些后,再进入 Spine JSON 烘焙导出会更稳妥,且无需反复修改当前已经定版的粒子属性与外观资源面板。
+404
View File
@@ -0,0 +1,404 @@
# 网页粒子系统 · 阶段九
> 阶段定位:**撤回与快捷键、编辑器配置持久化、可复用预设体系定版**
> 完成日期:2026-08-30
> 工程目录:`/Users/tianmokeji/Desktop/SpineParticle`
> 技术栈:Vue 3 + TypeScript + Pinia + PixiJS 7 + Spine Runtime 4.2 + Vite
---
## 一、阶段结论
阶段九在阶段八场景编辑、Spine 骨骼跟随和对象复制能力的基础上,补齐了编辑器日常使用所需的撤回、快捷键、工程保存、工程加载和预设加载流程。
编辑器现在可以把粒子系统、碰撞体、路径、Spine、系统设置、画布视图和动画归属等数据保存为可移植 JSON,并能把 Spine 骨架、Atlas 与多张纹理一并内嵌。配置重新加载后不需要再次选择 Spine 原始文件,画布、动画列表和骨骼树可以直接恢复。
本阶段主要完成:
1. 实现默认 20 步、最大 200 步的撤回历史;
2. 在系统设置中增加最大撤回次数设置;
3. 支持 `Ctrl/Cmd + Z` 撤回;
4. 增加画布工具、时间轴和保存配置快捷键;
5. 实现完整编辑器配置 JSON 下载保存;
6. 实现本地 JSON 配置加载和数据恢复;
7. 将 Spine 原始资源以内嵌 Data URL 形式纳入配置;
8. 修复 Spine 配置恢复后的运行时资源注册和渲染;
9. 实现 `public/Preset/` 预设目录与自动索引;
10. 实现预设列表、空状态和一键加载;
11. 建立纯粒子、路径、碰撞体、Spine 和空场景测试预设;
12. 修复包含 `+` 的预设文件名无法通过开发服务器读取的问题;
13. 为预设加载错误增加文件命名检查提示;
14. 修复同一配置第二次加载后停在第 0 帧的问题;
15. 完成普通配置和含 Spine 配置的保存—加载往返验证。
---
## 二、撤回功能
### 2.1 历史模型
编辑器通过快照记录可撤回数据。默认保留 20 步,用户可以在系统设置中修改,允许范围为 1~200 步。
快照覆盖:
- 全部粒子系统及其面板参数;
- 全部碰撞体;
- 全部路径与路径点;
- 全部 Spine 场景对象;
- 所属动画列表;
- 除撤回上限自身以外的系统设置;
- 对象显隐、背景图数据及背景偏移和透明度。
以下内容不作为普通撤回快照保存:
- 粒子逐帧录制缓存;
- PixiJS `Texture` 实例;
- 拖尾运行时纹理实例;
- 当前时间轴帧和正在播放状态;
- 撤回上限本身。
这些数据属于运行时缓存或撤回系统自身配置,恢复快照后由编辑器重新计算或重建。
### 2.2 操作合并
参数连续拖动或连续输入不会为每个微小变化创建独立历史。编辑器在最后一次变化后等待约 250ms,再把连续变化合并为一步操作。
该设计适用于:
- 滑块拖动;
- 数值连续输入;
- 曲线调整;
- 场景对象变换;
- 背景图偏移和透明度调整。
### 2.3 Spine 资源安全
Spine 场景对象的可序列化属性保存在快照中,已解析的 SkeletonData 和纹理通过资源注册表引用计数保留。
撤回删除、复制或资源替换操作时:
- 快照仍引用的 Spine 资源不会被提前销毁;
- 恢复快照时重新注册对应 Spine ID 的运行时资源;
- 超出撤回上限的旧快照会释放自己持有的资源引用;
- 粒子录制缓存清空并重新生成。
### 2.4 撤回后的状态修复
执行撤回后会:
1. 恢复场景对象和设置;
2. 修复当前选择,避免选择已不存在的对象;
3. 恢复粒子系统所属动画;
4. 清空旧录制帧;
5. 重新计算时间轴总长度;
6. 更新撤回按钮的剩余步数和可用状态。
当前阶段只实现“撤回”,尚未实现“重做”。
---
## 三、快捷键
### 3.1 快捷键表
| 快捷键 | 作用 | 生效范围 |
|---|---|---|
| `Ctrl/Cmd + Z` | 撤回上一步 | 全局 |
| `Ctrl/Cmd + S` | 下载保存当前编辑器配置 | 全局 |
| `Q` | 切换为画布拖动模式 | 画布已选中 |
| `W` | 切换位移工具 | 画布已选中 |
| `E` | 切换旋转工具 | 画布已选中 |
| `R` | 切换缩放工具 | 画布已选中 |
| `D` | 播放/暂停时间轴 | 全局非输入状态 |
| `S` | 回到时间轴第 0 帧 | 全局非输入状态 |
### 3.2 浏览器快捷键覆盖
`Ctrl/Cmd + S` 和 `Ctrl/Cmd + Z` 会阻止浏览器默认行为,分别交给编辑器保存和撤回,不再弹出浏览器网页保存或执行浏览器输入历史撤销。
### 3.3 输入控件保护
当焦点位于以下控件时,普通字母快捷键不会触发:
- `input`;
- `textarea`;
- `select`;
- `contenteditable` 内容。
因此输入名称、搜索骨骼或编辑参数时,可以正常键入 `q/w/e/r/d/s`。带 `Ctrl/Cmd` 的保存与撤回仍按编辑器全局快捷键处理。
---
## 四、编辑器配置保存
### 4.1 保存入口
场景对象面板上方增加:
- `保存配置`;
- `加载配置`;
- `选择预设`。
点击“保存配置”或按 `Ctrl/Cmd + S`,浏览器会下载:
```text
SpineParticle-YYYYMMDD-HHMMSS.json
```
### 4.2 文件格式
当前格式标识与版本:
```text
format: SpineParticle.EditorConfig
version: 1
```
顶层数据结构:
```text
editor 画布缩放与平移
settings 系统设置、背景图及撤回上限
timeline FPS、循环状态和所属动画列表
selection 当前场景对象类型与 ID
scene 粒子、碰撞体、路径和 Spine 数组
savedAt 保存时间
```
### 4.3 粒子与图片资源
粒子系统保存全部可编辑参数,包括:
- 发射模式、持续时间和延迟;
- 发射器形状和根变换;
- 生命周期、速度、缩放、旋转与曲线;
- 颜色、透明度、混合模式;
- 修改器;
- 碰撞、路径和骨骼跟随;
- 图片资源与拖尾资源;
- 对象显隐和所属动画。
PixiJS 纹理实例和粒子逐帧缓存不会写入 JSON。图片资源通过自身的预览 URL 或 Data URL 保存,配置加载时重新创建纹理。
### 4.4 Spine 原始资源内嵌
Spine 对象除属性、动画选择和骨骼元数据外,还保存原始资源副本:
```text
sourceBundle.skeleton
sourceBundle.atlas
sourceBundle.textures[]
```
每个资源同时保存原文件名和 Data URL。恢复时重新创建同名 `File`:
- JSON 使用原 JSON 文件名;
- SKEL 使用原二进制文件名;
- Atlas 保留原 Atlas 文件名和内容;
- 纹理保留 Atlas 中引用的准确文件名。
保持文件名一致是 Atlas 再次匹配多张纹理的关键。Data URL 只是文件内容载体,不会替代资源之间的文件名引用关系。
Spine 面板在首次加载成功后异步生成 `sourceBundle`,避免内嵌资源转换干扰当前画布中的实时 Spine 解析结果。
---
## 五、编辑器配置加载
### 5.1 校验
加载本地文件时只接受 `.json`,并依次检查:
1. 是否为有效 JSON;
2. `format` 是否为 `SpineParticle.EditorConfig`;
3. 配置版本是否为 1;
4. 场景是否包含粒子、碰撞体、路径和 Spine 数组。
失败时保持当前编辑器状态,并显示具体错误。
### 5.2 原子恢复流程
配置加载先在临时数据中完成资源准备:
1. 深度复制配置数据;
2. 补齐旧粒子参数默认值;
3. 重建粒子和拖尾纹理;
4. 还原 Spine 原始文件;
5. 解析骨架、Atlas 和多纹理;
6. 全部解析成功后再替换当前场景;
7. 注册新的 Spine 运行时资源;
8. 修复对象选择和所属动画;
9. 重新计算时间轴长度。
如果内嵌 Spine 资源解析失败,已临时创建的纹理会释放,不会把半完成场景覆盖到编辑器中。
### 5.3 Spine 运行时刷新
重复加载配置时,Spine 对象 ID 可能相同。加载流程会提升 `assetVersion`,确保运行时层识别为新资源并重新创建 Spine 显示对象,而不是继续使用旧实例。
资源加载完成后会恢复:
- Spine 画布渲染;
- 动画列表和动画时长;
- 当前选择的 Spine 动画;
- 骨骼层级树;
- 当前选中骨骼;
- 粒子系统保存的 Spine ID 与骨骼名称绑定。
### 5.4 加载后自动播放
配置和预设加载完成后统一执行:
```text
frame = 0
recorded = false
playing = true
```
因此首次加载和连续加载同一份配置都会从头自动播放,不再依赖场景深度监听是否检测到数据变化。
---
## 六、预设体系
### 6.1 预设目录
预设文件放置于:
```text
public/Preset/
```
每个预设都是与“保存配置”完全相同的 `SpineParticle.EditorConfig` JSON,因此预设和本地工程配置共用一套解析、校验和资源恢复流程。
### 6.2 自动索引
Vite 插件会扫描预设目录中的全部 `.json` 文件并自动生成:
```text
public/Preset/index.json
```
索引生成时:
- 排除 `index.json` 自身;
- 按中文文件名排序;
- 使用文件名去掉 `.json` 作为界面名称;
- 开发服务器监听文件新增和删除并刷新列表;
- 生产构建开始前重新生成索引。
目录中没有配置文件时,下拉框显示“无预设文件”并置灰。
### 6.3 当前预设
当前项目包含:
- `纯粒子.json`;
- `纯粒子-路径.json`;
- `纯粒子-碰撞.json`;
- `纯粒子-spine.json`;
- `空场景.json`;
- `默认粒子.json`。
带 Spine 的预设内嵌 `bingo.json`、`bingo.atlas`、`bingo.png` 和 `bingo_2.png`,加载后可直接显示 Spine、动画列表和骨骼树。
### 6.4 文件命名规则
阶段测试发现,文件名包含 `+` 时,经 URL 编码后开发服务器会返回应用 HTML,而不是目标 JSON,最终表现为“配置文件不是有效的 JSON”。
为确保开发环境和静态托管环境行为一致,组合预设统一使用连字符:
```text
推荐:纯粒子-路径.json
避免:纯粒子+路径.json
```
预设加载失败时,界面在原始错误后追加:
```text
请检查预设文件命名
```
---
## 七、代码结构
本阶段新增或重点调整:
| 文件 | 职责 |
|---|---|
| `src/editor/editorClone.ts` | 安全深度复制编辑器可序列化数据 |
| `src/editor/editorHistory.ts` | 撤回快照、合并、上限和 Spine 资源引用 |
| `src/editor/useEditorShortcuts.ts` | 全局快捷键、画布焦点和输入保护 |
| `src/editor/editorConfig.ts` | 配置创建、下载、校验、加载和资源恢复 |
| `src/spine/spineSourceBundle.ts` | Spine 原始文件 Data URL 内嵌与还原 |
| `src/spine/spineAssetRegistry.ts` | Spine 配置恢复和撤回所需的资源生命周期 |
| `src/views/ParticlePanel.vue` | 保存、加载、预设列表和结果提示 |
| `src/views/Stage.vue` | 快捷键接入、撤回按钮和加载后的运行时刷新 |
| `vite.config.ts` | 预设索引自动生成和 Spine 依赖去重 |
配置、历史与快捷键逻辑已从主画布和主属性面板中拆分,避免继续扩大单文件职责。
---
## 八、验证结果
已完成以下实际验证:
- `Ctrl/Cmd + S` 可以下载编辑器配置;
- 配置文件名按时间生成且 JSON 格式有效;
- 修改粒子名称后保存,再次修改并加载,名称恢复为保存值;
- 系统设置、背景图数据、画布视图和场景对象写入配置;
- 含 Spine 的配置保存后文件约 11MB,资源数据完整内嵌;
- 含 Spine 配置重新加载后,Spine 名称恢复;
- Spine 画布图像正常显示;
- 14 个 Spine 动画及“无”选项正常恢复;
- 骨骼层级树正常恢复;
- 页面未出现资源解析或渲染错误;
- `纯粒子.json` 加载成功;
- `纯粒子-路径.json` 加载成功,场景包含粒子与路径;
- `纯粒子-碰撞.json` 加载成功,场景包含粒子与碰撞体;
- `纯粒子-spine.json` 加载成功,场景包含粒子与 Spine;
- `空场景.json` 可以加载为零对象场景;
- `默认粒子.json` 加载成功;
- 同一 Spine 预设连续加载两次,两次都从第 0 帧自动播放;
- 第二次加载后帧数从 40 推进到 59,播放按钮保持“暂停”状态;
- 浏览器控制台无错误和警告;
- `npm run build` 生产构建通过。
构建仍存在 Vite 主产物超过 500kB 的提示,不影响功能和构建结果。
---
## 九、当前限制
1. 当前只支持单向撤回,尚未实现重做;
2. 撤回历史只存在于当前页面内存中,刷新页面后清空;
3. 撤回不保存当前播放帧、播放状态、画布选择和粒子录制缓存;
4. 配置格式当前为版本 1,尚未建立跨版本迁移器;
5. Spine 和本地图片使用 Data URL 内嵌,资源较大时配置文件体积明显增加;
6. 保存配置始终下载新文件,尚未提供覆盖保存、最近文件和自动保存;
7. 浏览器安全限制下无法记住原本地文件路径;
8. 预设依赖构建时生成的索引,纯静态部署后新增文件需要重新构建;
9. 预设命名目前通过规避 `+` 保证兼容,尚未建立完整的文件名字符校验;
10. 配置导入目前只接受 JSON,尚未支持 YAML;
11. 配置文件没有压缩,包含 Spine 多纹理时下载和解析成本较高;
12. 主包仍包含 Spine 运行时和主要编辑器模块,尚未动态拆包。
---
## 十、阶段十建议
1. 增加重做功能和 `Ctrl/Cmd + Shift + Z` 快捷键;
2. 增加配置格式迁移器,支持未来版本平滑升级;
3. 为预设文件名建立创建、校验和错误定位工具;
4. 提供自动保存、最近配置和页面异常恢复;
5. 支持压缩工程包,把 JSON 与 Spine、粒子图片资源拆分存储;
6. 增加配置导入前预览、差异对比和合并模式;
7. 为配置与预设建立自动化往返测试;
8. 对无效 ID、丢失骨骼、缺失纹理和旧字段提供迁移报告;
9. 增加工程名称、作者、备注和自定义元数据;
10. 开始设计正式导出结构,把所属动画、粒子系统和 Spine 骨骼绑定映射到导出数据;
11. 对 Spine 和编辑器主模块进行动态加载,降低初始包体积;
12. 增加配置资源大小统计和大文件保存提示。
+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 取负` 的数学坐标约定。
+448
View File
@@ -0,0 +1,448 @@
# 网页粒子系统 · 阶段五
> 阶段定位:**粒子拖尾模块定版**
> 完成日期:2026-08-28
> 工程目录:`/Users/tianmokeji/Desktop/SpineParticle`
> 技术栈:Vue 3 + TypeScript + Pinia + PixiJS 7 + Vite
---
## 一、阶段结论
阶段五完成了粒子拖尾模块的第一版完整实现。拖尾不再是独立于粒子的静态图片,而是由每个粒子自己的历史轨迹、拖尾骨骼链和网格蒙皮共同驱动,并与粒子的最终渲染位置保持连接。
本阶段主要完成:
1. 默认 `trail.png` 拖尾资源;
2. 多拖尾图片资源和占比分配;
3. 每张拖尾资源独立的骨骼、尺寸、旋转、颜色、透明度和曲线配置;
4. 按网格行列自动生成蒙皮拓扑;
5. 按拖尾骨骼数生成独立骨骼链;
6. 使用相邻骨骼权重对图片网格进行 CPU 蒙皮;
7. 拖尾跟随粒子最终渲染位置,包含移动噪声等视觉位移;
8. 拖尾状态参与固定帧录制与回放;
9. 画布蒙皮网格调试显示;
10. 卡片全量重置和旧配置迁移。
拖尾模块默认关闭,不会改变未启用拖尾时的粒子表现。阶段三的外观资源和阶段四的修改器功能保持原有行为。
---
## 二、拖尾默认状态
### 2.1 模块默认状态
- “开启拖尾(Trail)”默认关闭;
- 开启后默认创建一张拖尾资源卡片;
- 默认图片为 `public/trails/trail.png`;
- 默认名称为 `trail`;
- 单资源时占比固定为 100%;
- 画布“蒙皮网格”显示默认关闭。
### 2.2 每张拖尾资源的默认参数
| 参数 | 默认值 |
|---|---:|
| 图片 | `trail.png` |
| 名称 | `trail` |
| 占比 | 100%(单资源) |
| 占比锁定 | 关闭 |
| 旋转 | 0° |
| 拖尾骨骼数 | 3 |
| 初始长度 | 100 |
| 初始宽度 | 100 |
| 宽度缩放 | 跟随粒子 |
| 生命周期长度曲线 | 关闭,默认水平线 1 |
| 形状曲线 | 关闭,默认水平线 1 |
| 颜色模式 | 跟随粒子 |
| 混合模式 | 正常 |
| 固定颜色 | `#ffffff` |
| 透明度模式 | 跟随粒子 |
| 固定透明度 | 1 |
| 透明度曲线 | 默认水平线 1 |
### 2.3 网格默认参数
网格拓扑属于拖尾模块的公共结构参数:
| 参数 | 默认值 | 可调范围 |
|---|---:|---:|
| 网格行数 | 4 | 2~16 |
| 网格列数 | 4 | 2~32 |
行数决定图片宽度方向的细分,列数决定图片从头部到尾部方向的细分。
---
## 三、多拖尾图片资源
### 3.1 资源卡片
拖尾图片复用了外观与资源模块的通用 `ImageResourceCard.vue`,因此两类图片资源保持统一的交互方式:
- 点击缩略图选择本地 PNG、JPEG 或 WebP;
- 点击右上角 `+` 新增拖尾资源卡片;
- 修改资源名称;
- 查看当前图片文件名;
- 调整资源占比;
- 锁定或解除锁定当前占比;
- 重置当前资源;
- 删除当前资源,至少保留一张。
拖尾资源不显示“子文件夹”字段,其余通用卡片结构与粒子图片资源一致。
### 3.2 占比规则
- 单张拖尾资源时,占比固定为 100%,锁定按钮不可用;
- 多张拖尾资源时,总占比始终保持 100%;
- 锁定某张资源后,调整其他资源只会重新分配未锁定资源;
- 新增、删除或重置资源后会重新平衡占比;
- 每个粒子出生时,根据全局随机种子和资源占比选择一张拖尾图片;
- 粒子出生后保持已选中的拖尾资源,不会在生命周期中跳换图片。
### 3.3 卡片重置
点击拖尾卡片的重置按钮会恢复该卡片的完整默认状态,不再只替换图片。重置内容包括:
- `trail.png`、名称 `trail`;
- 占比和锁定状态;
- 旋转、骨骼数、长度、宽度和宽度模式;
- 生命周期长度曲线及其开关;
- 形状曲线及其开关;
- 颜色模式、固定色、颜色渐变和混合模式;
- 透明度模式、固定透明度和透明度曲线。
多资源场景中,卡片恢复默认后仍会执行占比平衡,保证全部资源之和为 100%。
---
## 四、每张拖尾资源的独立控制
### 4.1 图片方向
拖尾图片约定:
- 图片左侧为拖尾头部;
- 图片右侧为拖尾尾部;
- 第 0 根拖尾骨骼位于粒子位置;
- 后续骨骼沿粒子历史轨迹向后排列。
“旋转”用于修正所选图片的默认朝向。旋转通过图片中心的 UV 变换完成,不会改变粒子自身旋转。
### 4.2 骨骼与尺寸
每张拖尾资源独立设置:
- **拖尾骨骼数**:决定沿轨迹采样的骨骼节点数量,范围 2~16;
- **初始长度**:决定从粒子头部向历史轨迹回溯的距离;
- **初始宽度**:决定蒙皮网格横向展开宽度;
- **宽度缩放**:可选择固定宽度或跟随粒子当前缩放。
“跟随粒子”模式使用粒子当前 X/Y 缩放绝对值的平均值作为宽度倍率。
### 4.3 生命周期长度曲线
开启后,曲线横轴为粒子归一化生命时间,纵轴为长度倍率:
```text
当前拖尾长度 = 初始长度 × 生命周期长度曲线值
```
曲线默认是值为 1 的水平线。关闭时始终使用初始长度。
### 4.4 形状曲线
形状曲线控制拖尾从首端到尾端的宽度变化:
```text
当前列宽度 = 当前拖尾宽度 × 形状曲线值
```
横轴表示从头部到尾部的位置,纵轴表示宽度倍率。默认曲线为值 1 的水平线,因此首尾等宽。
### 4.5 颜色与混合模式
每张拖尾资源支持三种颜色模式:
| 模式 | 行为 |
|---|---|
| 跟随粒子 | 使用当前粒子图片的实时颜色 |
| 固定 | 使用拖尾资源自己的固定颜色 |
| 曲线 | 按粒子生命周期使用多色渐变 |
固定色支持颜色选择器和十六进制文本输入。曲线模式复用生命周期多色渐变控件,可添加多个颜色标记。
每张资源还可独立选择四种混合模式:正常、相加、相乘、滤色。
### 4.6 透明度
每张拖尾资源支持三种透明度模式:
| 模式 | 行为 |
|---|---|
| 跟随粒子 | 使用粒子当前透明度 |
| 固定 | 使用拖尾资源自己的固定透明度 |
| 曲线 | 按粒子生命周期读取透明度曲线 |
透明度曲线默认是值为 1 的水平线。
---
## 五、拖尾骨骼与蒙皮实现
### 5.1 粒子历史轨迹
每个活动粒子维护自己的轨迹历史:
- 记录粒子每帧的最终渲染位置;
- 相邻位置变化过小时更新首点,避免堆积大量重复点;
- 按配置长度保留约 1.5 倍的历史距离;
- 单粒子历史点设置上限,防止长时间运行无限增长;
- 粒子死亡、拖尾关闭或重新绑定时清空对应历史。
记录的是粒子最终渲染位置,因此移动噪声造成的视觉位移也会进入拖尾轨迹,拖尾头部不会与画面中的粒子脱节。
### 5.2 独立骨骼链
每个粒子根据自己所选拖尾资源的骨骼数生成独立骨骼链:
1. 第 0 根骨骼固定在粒子最终渲染位置;
2. 其余骨骼沿历史轨迹按距离等距采样;
3. 每根骨骼根据前后采样点计算旋转方向;
4. 不同粒子、不同拖尾资源可以使用不同骨骼数量。
### 5.3 网格拓扑
网格根据行列数自动生成:
- 每个交叉点生成一个顶点;
- 相邻四个顶点组成两个三角形;
- 列方向对应图片左侧头部到右侧尾部;
- 行方向对应图片宽度;
- UV 根据资源旋转参数绕中心重新计算。
### 5.4 蒙皮权重
每一列顶点绑定相邻两根拖尾骨骼:
- 根据列在拖尾长度中的归一化位置,计算前后骨骼索引;
- 在两根骨骼之间使用线性权重插值;
- 顶点中心沿骨骼链移动;
- 顶点宽度方向使用轨迹切线的法线展开;
- 形状曲线进一步调节每一列的宽度。
当前使用 CPU 更新 PixiJS `SimpleMesh` 顶点,蒙皮结果实时写回画布。
### 5.5 绘制层级
拖尾网格始终绘制在对应粒子图片下方,避免拖尾覆盖粒子头部。图片不可用、轨迹长度不足或最终透明度为 0 时,拖尾网格自动隐藏。
---
## 六、重新绑定规则
以下参数改变时会触发拖尾网格和蒙皮重新绑定:
1. 网格行数;
2. 网格列数;
3. 当前拖尾资源的骨骼数;
4. 当前拖尾资源的旋转方向;
5. 粒子切换到结构签名不同的拖尾资源。
重新绑定时:
- 保留当前正在使用的图片,不会错误恢复为 `trail.png`;
- 重新生成顶点、索引、UV 和骨骼权重;
- 清空旧轨迹和旧骨骼状态;
- 从粒子当前最终位置重新开始采样;
- 避免旧骨骼位置套用到新网格后产生偏移或拉伸。
只有用户主动点击卡片重置按钮时,图片和参数才会恢复为完整默认状态。
---
## 七、粒子跟随与修改器协作
此前出现的“拖尾像独立图片、没有跟随粒子”问题已经修正。当前拖尾更新顺序为:
1. 计算粒子的基础物理位置;
2. 应用重力、风力、力场、吸附和阻尼;
3. 计算移动噪声等最终视觉位移;
4. 得到粒子最终渲染位置;
5. 将该位置写入拖尾历史;
6. 重新采样拖尾骨骼;
7. 将首骨骼强制对齐粒子最终位置;
8. 更新蒙皮顶点、颜色、透明度和混合模式。
因此拖尾与粒子画面位置使用同一坐标来源。粒子死亡时,对应拖尾历史、骨骼和网格会一起停止并清理。
---
## 八、固定帧录制与回放
粒子实时状态新增 `trailState`,记录:
- 当前拖尾资源 ID;
- 拖尾骨骼位置和旋转;
- 当前宽度;
- 当前透明度;
- 当前颜色。
固定帧录制会保存这些状态,回放时按资源 ID 恢复对应纹理和蒙皮参数,避免多拖尾资源在回放时全部退回第一张图片。
---
## 九、画布蒙皮网格调试
画布左上角新增“蒙皮网格”复选框:
- 默认关闭;
- 开启后使用青色线条绘制正在显示的拖尾网格;
- 显示网格的行列连接和所有顶点;
- 多个粒子的网格可同时显示;
- 只用于编辑器调试,不参与录制和导出;
- 关闭拖尾或关闭开关时立即清空辅助线。
该功能用于检查图片方向、网格密度、骨骼变形和拖尾是否与粒子头部正确连接。
---
## 十、核心数据结构
### 10.1 `TrailImageResource`
每张拖尾图片已经成为完整的独立资源配置,主要包含:
- 资源 ID、纹理、名称和预览地址;
- 占比和锁定状态;
- 图片旋转;
- 颜色模式、固定色、多色渐变和混合模式;
- 透明度模式、固定透明度和曲线;
- 骨骼数、初始长度、初始宽度和宽度模式;
- 生命周期长度曲线;
- 首端到尾端的形状曲线。
### 10.2 `TrailState`
`TrailState` 是实时预览与时间轴之间的拖尾状态载体,包含骨骼链、宽度、透明度、颜色和拖尾资源 ID。
### 10.3 兼容迁移
`ensureEmitterConfig()` 会:
- 把早期单拖尾字段迁移为 `trailResources`;
- 为旧资源补齐旋转、混合模式、独立骨骼尺寸和曲线字段;
- 把旧版非水平拖尾曲线迁移为当前默认水平曲线;
- 把旧网格默认值迁移为 4 行、4 列;
- 保证热更新和旧内存配置不会把 `undefined` 传入运行逻辑。
---
## 十一、本阶段新增或重点调整的文件
| 文件 | 本阶段职责 |
|---|---|
| `public/trails/trail.png` | 默认拖尾图片资源 |
| `src/views/ImageResourceCard.vue` | 粒子图片与拖尾图片共用的资源卡片 |
| `src/views/ParticlePanel.vue` | 拖尾开关、多资源卡片、独立参数、曲线、占比和重置逻辑 |
| `src/core/particleEmitter.ts` | 轨迹记录、骨骼采样、网格生成、CPU 蒙皮、资源选择、录制和回放状态 |
| `src/views/Stage.vue` | 默认拖尾纹理注入、画布蒙皮网格显示及实时刷新 |
---
## 十二、验证结果
本阶段已完成以下检查:
- 默认拖尾资源能加载 `trail.png`;
- 图片左侧为头部、右侧为尾部;
- 拖尾首骨骼与粒子最终渲染位置保持一致;
- 移动噪声开启时,拖尾沿粒子视觉轨迹移动;
- 多张拖尾图片可新增、删除、选择和按占比分配;
- 每张资源可独立设置骨骼、长度、宽度、旋转、颜色、混合模式和透明度;
- 生命周期长度曲线和形状曲线已移动到各自资源卡片;
- 修改行列数、骨骼数或旋转后会重新绑定,并保留当前图片;
- 卡片重置会恢复图片和全部默认参数;
- 网格默认值为 4 × 4;
- 画布“蒙皮网格”开关默认关闭,可正常开启和关闭;
- 本地页面交互过程中没有控制台错误;
- `npm run build` 通过;
- `git diff --check` 通过。
Vite 仍会提示主包体积超过 500 kB,该提示不影响当前功能运行。
---
## 十三、当前边界与已知风险
1. **拖尾不会在粒子死亡后继续残留**:粒子死亡时拖尾一起清理,尚未支持尾迹独立淡出。
2. **蒙皮在 CPU 中逐帧更新**:高粒子数、高网格细分和多拖尾资源同时使用时需要性能压测。
3. **历史轨迹按帧采样**:极端帧率波动下,弯曲细节可能出现密度差异;当前骨骼按距离重新采样可缓解但不能完全消除。
4. **所有拖尾资源共用网格行列数**:骨骼数和外观参数可独立配置,但网格拓扑尚未移动到每张资源卡片。
5. **资源仍依赖运行时纹理对象**:本地上传图片尚未进入正式工程保存、加载和资源打包流程。
6. **旋转使用 UV 中心变换**:非矩形有效内容或带大面积透明边距的图片可能需要额外裁切、翻转或轴向选项。
7. **时间轴保存的是运行状态**:尚未形成独立、版本化、可跨工程交换的拖尾动画格式。
8. **缺少自动化测试**:当前以类型检查、生产构建和浏览器交互验证为主。
---
## 十四、下一步开发建议
### 优先级 P0:拖尾性能与稳定性
- 对 100、200、400 粒子分别测试 4 × 4、8 × 8、16 × 32 网格;
- 记录 CPU 蒙皮耗时、顶点数量和内存占用;
- 将不在视口内或透明度为 0 的拖尾提前跳过;
- 缓存资源 ID 到配置对象的映射,减少每帧数组查找;
- 评估把顶点变形迁移到 GPU Shader 的收益。
### 优先级 P1:拖尾生命周期收尾
- 增加粒子死亡后拖尾残留时间;
- 支持尾迹独立淡出和收缩;
- 明确吸附完成、碰撞死亡和自然死亡时的不同拖尾结束策略;
- 增加拖尾采样间隔或最小距离参数;
- 增加轨迹平滑和尖角处理。
### 优先级 P2:工程保存与资源打包
- 定义正式 `ProjectSchema` 和版本号;
- 将 Pixi Texture 与可序列化拖尾配置分离;
- 保存多拖尾资源、占比、锁定、颜色、曲线和网格参数;
- 采用 Data URL、ZIP 工程包或资源目录保存自定义拖尾图片;
- 为阶段三到阶段五建立明确的配置迁移测试。
### 优先级 P3:导出准备
- 明确拖尾骨骼链在 Spine 中的命名和层级;
- 固化网格顶点、三角形、UV、权重和骨骼数据格式;
- 将 `TrailState` 烘焙为骨骼关键帧或网格变形关键帧;
- 验证多拖尾资源的槽位、附件和混合模式表达;
- 评估不同 Spine 版本对加权网格和变形时间轴的兼容性。
### 优先级 P4:后续模块
- 实现碰撞体和粒子碰撞响应;
- 实现轨道路径与路径跟随;
- 为修改器辅助图形增加统一显示入口;
- 增加撤销/重做、预设和参数复制粘贴。
---
## 十五、阶段五验收标准
阶段五可视为完成,当以下条件持续满足:
- 拖尾默认关闭,开启后默认使用 `trail.png`;
- 每个粒子拥有独立的轨迹、骨骼链和蒙皮网格;
- 拖尾图片左侧连接粒子,右侧沿历史轨迹延伸;
- 多拖尾资源按占比确定性选择,并支持占比锁定;
- 每张拖尾资源可独立调整旋转、骨骼、尺寸、曲线、颜色、混合模式和透明度;
- 网格按行列自动生成,并由相邻骨骼权重驱动;
- 拖尾与粒子最终渲染位置保持连接;
- 修改结构参数后保留当前图片并重新绑定,不产生旧蒙皮偏移;
- 卡片重置可恢复完整默认状态;
- 画布可选择显示实际蒙皮网格,且默认关闭;
- 拖尾状态能够参与时间轴录制和回放;
- 阶段三外观资源和阶段四修改器功能没有发生回归;
- TypeScript 类型检查和生产构建保持通过。
+573
View File
@@ -0,0 +1,573 @@
# 网页粒子系统 · 阶段八
> 阶段定位:**Spine 骨骼选择、粒子独立骨骼跟随、对象显隐、编辑效率与画布参考功能定版**
> 完成日期:2026-08-29
> 工程目录:`/Users/tianmokeji/Desktop/SpineParticle`
> 技术栈:Vue 3 + TypeScript + Pinia + PixiJS 7 + Spine Runtime 4.2 + Vite
---
## 一、阶段结论
阶段八在阶段七 Spine 资源加载和统一时间轴的基础上,完成了骨骼选择到粒子发射器跟随的完整工作流。粒子系统现在可以分别绑定不同 Spine 对象中的不同骨骼,绑定关系彼此独立,并具备目标失效后的自动回退机制。
本阶段同时把场景对象显隐改为对象级控制,并为后续导出建立了粒子系统“所属动画”数据模型。
本阶段主要完成:
1. 将界面中的“骨架”术语统一为 `Spine`;
2. 为 Spine 层级树增加骨骼选择、清除选择、搜索和折叠操作;
3. 在画布中显示当前选中骨骼的位置;
4. 实现粒子发射器跟随 Spine 骨骼;
5. 支持位移、旋转、缩放偏移以及本地/世界空间;
6. 修复 Spine 旋转和缩放被错误传递给粒子的问题;
7. 让多个粒子系统的 Spine 与骨骼绑定彼此独立;
8. 实现目标 Spine 或骨骼失效后的自动回退;
9. 为场景中的粒子、碰撞体、路径和 Spine 增加独立显隐按钮;
10. 移除系统设置中的全局 Spine 资源显示开关;
11. 为时间轴增加 Spine 预览动画快捷选择;
12. 将时间轴“动画”升级为粒子系统“所属动画”管理;
13. 支持所属动画新增、重命名、删除和最少一个动画保护;
14. 移除画布右上角重复的秒数显示;
15. 增加 Spine 资源文件夹一键加载;
16. 修复统计数字、帧数和所属动画菜单的显示稳定性;
17. 为四类场景对象增加独立复制功能;
18. 增加画布背景图上传、偏移、透明度和清除功能;
19. 完成 Spine 4.2 与背景图测试资源下的交互及生产构建验证。
---
## 二、Spine 术语与对象界面
### 2.1 术语统一
面向用户的场景对象术语统一使用 `Spine`,不再把资源对象简称为“骨架”。
主要界面名称包括:
- `+Spine`;
- `Spine 属性`;
- `Spine 名称`;
- `Spine 资源`;
- `Spine 动画`;
- `启用 Spine` 相关提示;
- 场景对象类型 `Spine`。
“骨骼”一词只用于 Spine 资源内部真实的 bone 层级和粒子系统生成的骨骼数据,避免与整个 Spine 场景对象混淆。
### 2.2 画布时间文字清理
画布右上角原有的 `t=0.00s` 文字只是当前时间的重复调试显示。当前帧、总帧数和播放状态已经由底部时间轴完整表达,因此本阶段将该文字移除。
---
## 三、Spine 骨骼层级树
### 3.1 骨骼选择
Spine 资源加载成功后,层级树中的每个骨骼都可以点击选择:
- 当前骨骼使用高亮行显示;
- 默认不选择任何骨骼;
- 同一时间只保留一个层级树骨骼作为“当前选中骨骼”;
- 再次点击同一骨骼可以取消选择;
- 选择另一个 Spine 中的骨骼时,会清除之前的临时层级树选择。
层级树标题旁显示自适应宽度的选择按钮:
```text
未选择时:当前骨骼:未选择
已选择时:清除选中骨骼:boneName
```
点击已选择状态的按钮可以清除层级树选择。按钮宽度会根据骨骼名称和可用空间自动调整,名称过长时使用省略显示。
### 3.2 名称搜索
层级树增加骨骼名称搜索框:
- 输入名称片段即可过滤骨骼;
- 搜索不区分大小写;
- 支持一键清空搜索内容;
- 没有匹配结果时显示空状态提示;
- 搜索只改变显示结果,不修改骨骼数据或当前绑定。
### 3.3 折叠与展开
保留并完善阶段七的层级操作:
- 单独折叠或展开父骨骼;
- 全部折叠;
- 全部展开;
- 搜索状态下直接展示名称匹配结果;
- 长列表在固定区域内滚动。
### 3.4 画布骨骼点预览
在层级树选择骨骼后,画布会立即绘制该骨骼的世界位置点,不需要先开启粒子跟随。
该预览用于确认:
- 是否选择了正确骨骼;
- 骨骼当前动画位置是否正确;
- Spine 对象自身位移、旋转和缩放后的世界位置是否正确;
- 粒子发射器后续应绑定的位置。
清除层级树选择后,单纯的选择预览点会消失;已经绑定到粒子系统的骨骼目标仍按绑定关系保留。
---
## 四、粒子发射器骨骼跟随
### 4.1 跟随模式
开启“发射器跟随”后,跟随模式包括:
| 模式 | 行为 |
|---|---|
| 无 | 不使用路径或骨骼跟随 |
| 跟随路径 | 使用场景路径对象驱动发射器 |
| 选中的骨骼 | 把当前粒子系统绑定到层级树当前选中的 Spine 骨骼 |
只有层级树存在有效选中骨骼时,才可以新建骨骼绑定。已经完成骨骼绑定的粒子系统会继续保留该模式,即使之后清除了层级树临时选择。
### 4.2 一次绑定原则
层级树的“当前选中骨骼”只是一个待绑定目标。选择“选中的骨骼”时,会把以下信息一次性写入当前粒子系统:
```text
emitterFollowSpineId
emitterFollowBoneName
```
绑定完成后,后续在层级树中选择其他骨骼不会自动覆盖已有绑定。
面板显示的是当前粒子系统实际保存的目标:
```text
跟随目标:SpineSkeleton1 / root
```
如果层级树当前选择与已绑定目标不同,面板显示“重新绑定当前选中骨骼”按钮。只有主动点击该按钮,当前粒子系统才会改绑到新目标。
### 4.3 多粒子系统独立绑定
每个粒子系统独立保存自己的 Spine ID 和骨骼名称,因此支持:
- 粒子系统 A 跟随 Spine A 的骨骼 `hand_l`;
- 粒子系统 B 跟随 Spine A 的骨骼 `hand_r`;
- 粒子系统 C 跟随 Spine B 的骨骼 `weapon`;
- 各粒子系统使用不同的偏移和空间模式;
- 修改其中一个粒子系统的目标不会影响其他系统。
运行时逐个读取粒子系统自身保存的目标,并从对应 Spine 运行时对象中查询骨骼世界位置,不再使用全局骨骼目标覆盖全部粒子系统。
### 4.4 参数
骨骼绑定成功后显示:
| 参数 | 默认值 | 说明 |
|---|---:|---|
| 跟随空间 | 世界空间 | 控制已出生粒子是否继续随目标移动 |
| 位移偏移 X / Y | 0 / 0 | 在骨骼世界位置上叠加位置偏移 |
| 旋转偏移 | 0° | 使用粒子系统自身旋转基础上的附加角度 |
| 缩放偏移 X / Y | 1 / 1 | 使用粒子系统自身缩放基础上的倍率 |
绑定没有成功时不会再出现只有“选中的骨骼”下拉值、下方却完全空白的状态。面板会提示尚未绑定有效骨骼,并在存在层级树选择时提供明确的绑定按钮。
### 4.5 本地空间与世界空间
| 空间 | 行为 |
|---|---|
| 本地空间 | 整个粒子发射器随骨骼位置移动,已出生粒子继续随发射器移动 |
| 世界空间 | 只移动新粒子的生成位置,已经出生的粒子保留在生成时的世界位置 |
两种空间只改变粒子位置的跟随判定,不会因为切换空间而改变粒子本身的尺寸。
### 4.6 Spine 变换继承规则
骨骼跟随只读取目标骨骼的世界位置,不继承以下变换:
- Spine 对象的旋转;
- Spine 对象的缩放;
- Spine 骨骼自身的旋转;
- Spine 骨骼自身的缩放。
粒子的旋转和缩放始终来自粒子系统自己的根变换及骨骼跟随偏移参数。因此在世界空间与本地空间之间切换时,粒子大小不会因 Spine 或骨骼缩放而变化。
### 4.7 目标失效回退
每个粒子系统持续校验自己保存的目标。以下情况会使绑定失效:
- 目标 Spine 对象被删除;
- 目标 Spine 资源被清空;
- 重新加载资源后原骨骼名称不存在;
- 配置中的 Spine ID 或骨骼名称无效。
失效后只重置受影响的粒子系统:
```text
emitterFollowMode = none
emitterFollowSpineId = 0
emitterFollowBoneName = ''
```
单纯清除层级树的“当前选中骨骼”不会解除已有绑定,因为骨骼本身仍然存在。
---
## 五、场景对象独立显隐
### 5.1 对象级小眼睛
场景对象列表中的每个对象右侧增加小眼睛按钮:
- 粒子系统;
- 碰撞体;
- 路径;
- Spine。
点击后只切换该对象的 `visible` 状态。新建对象默认显示;旧场景或热更新对象缺少该字段时自动补为显示。
### 5.2 显隐与启用分离
`visible` 只控制编辑器画布显示,不代替对象原有的 `enabled` 或功能开关。
隐藏对象时:
- 不绘制对象主体;
- 不绘制该对象的变换工具;
- 不绘制其发射点、路径锚点、碰撞体轮廓或选择辅助图形;
- 隐藏粒子系统时同时隐藏粒子骨骼调试线;
- 隐藏 Spine 时隐藏 Spine 资源和选择预览点。
隐藏对象不会:
- 停止粒子模拟;
- 关闭碰撞体的实际碰撞;
- 使路径跟随失效;
- 破坏粒子系统保存的骨骼绑定;
- 修改时间轴时长或导出配置。
因此“小眼睛”是编辑器可见性控制,“启用”仍是对象功能状态,两者语义独立。
### 5.3 移除全局 Spine 显示开关
系统设置中的“启用 Spine 资源显示”已移除。每个 Spine 对象使用自己的小眼睛控制显示,不再通过一个全局开关同时隐藏全部 Spine。
该变更替代了阶段七文档中的全局 Spine 显示设置设计。
---
## 六、时间轴 Spine 预览动画
时间轴控制栏新增“Spine 预览动画”下拉框,与 Spine 属性面板中的动画选择并存。
规则如下:
- 当前选中 Spine 有动画时,优先控制当前 Spine;
- 当前对象不是 Spine 时,使用场景中第一个包含动画的 Spine;
- 下拉项显示动画名称和真实时长;
- 切换后同步更新 Spine 属性面板;
- 切换动画后重新计算时间轴总长度并回到第 0 帧;
- 没有可用 Spine 动画时下拉框置灰。
该功能只提供快捷预览入口,不改变 Spine 动画自身非循环、由编辑器统一时间轴驱动的规则。
---
## 七、粒子系统所属动画
### 7.1 数据语义
时间轴原有的“动画”字段改为“所属动画”。它表示当前粒子系统在后续导出结构中归属于哪个动画,而不是当前 Spine 的预览动画。
每个粒子系统新增独立的动画归属字段:
```text
ParticleSystem.animation
```
项目时间轴状态保存可用动画名称列表:
```text
TimelineState.animations
```
默认状态为:
```text
动画列表:['animation']
新粒子系统所属动画:animation
```
### 7.2 动画管理
点击“所属动画”下拉按钮后可以:
- 查看全部动画名称;
- 修改当前粒子系统所属动画;
- 新增动画;
- 重命名当前动画;
- 删除当前动画。
新增动画自动使用不重复的英文名称:
```text
animation2
animation3
...
```
新增后,当前粒子系统自动归入新动画。
### 7.3 重命名与删除规则
动画名称:
- 不能为空;
- 不能与已有动画重名;
- 最长输入限制为 64 个字符;
- 重命名后,所有引用旧名称的粒子系统同步更新。
删除动画时:
- 如果仍有其他动画,引用被删除动画的粒子系统自动归入相邻的剩余动画;
- 当前选择同步切换到回退动画;
- 动画列表至少保留一个项目;
- 只剩一个动画时删除按钮禁用。
### 7.4 多粒子系统
不同粒子系统可以归属不同动画,例如:
```text
ParticleSystem1 -> idle
ParticleSystem2 -> attack
ParticleSystem3 -> hit
```
切换场景对象中的粒子系统后,时间轴“所属动画”会显示该系统自己的归属,不会覆盖其他粒子系统。
当前阶段只建立动画归属关系,尚未按所属动画过滤时间轴轨道或执行最终导出。
---
## 八、数据与兼容性
### 8.1 新增字段
场景对象新增:
```text
ParticleSystem.visible
ParticleSystem.animation
CollisionBody.visible
ScenePath.visible
SpineSceneObject.visible
```
时间轴新增:
```text
TimelineState.animations
```
粒子发射器沿用并完善:
```text
emitterFollowSpineId
emitterFollowBoneName
emitterFollowBoneOffsetX
emitterFollowBoneOffsetY
emitterFollowBoneRotation
emitterFollowBoneScaleX
emitterFollowBoneScaleY
```
### 8.2 旧数据迁移
开发热更新或旧场景缺少字段时:
- 场景对象默认补为显示;
- 动画列表默认补为 `['animation']`;
- 粒子系统缺少或引用无效所属动画时归入 `animation`;
- 骨骼跟随字段通过 `ensureEmitterConfig()` 补齐;
- 骨骼目标不存在时自动回到无跟随模式。
系统设置中的旧 `showSpineResources` 字段不再参与界面和运行时显示判断。
---
## 九、编辑效率与画布参考功能
### 9.1 Spine 文件夹一键加载
Spine 资源面板在原有骨架数据、Atlas 和纹理分别选择的方式之外,增加文件夹加载入口。
选择文件夹后会自动识别:
- 一个 `.json` 或 `.skel` 骨架数据文件;
- 一个 `.atlas` 图集文件;
- 文件夹中的 `.png`、`.jpg`、`.jpeg`、`.webp` 纹理;
- Atlas 实际引用的多张纹理页面。
识别完成后直接执行与原三步选择相同的 Spine 4.2 加载流程,保留原有分文件选择方式作为兼容入口。
### 9.2 当前场景对象独立复制
场景对象标题栏在 `+Spine` 后增加 `+Copy` 按钮。按钮读取场景树当前选中项,支持复制:
- 粒子系统;
- 碰撞体;
- 路径;
- Spine。
复制完成后,新对象自动成为当前选中对象,并使用不重复名称:
```text
ParticleSystem1 Copy
ParticleSystem1 Copy 2
...
```
复制规则如下:
- 面板参数、曲线、标签、路径点和 Spine 元数据进行深度复制;
- 修改副本参数不会反向修改原对象;
- 粒子录制帧属于运行时缓存,不复制旧缓存,由副本按照相同配置重新生成;
- Spine 层级树的临时骨骼选择不复制,副本默认不选中骨骼;
- 已加载的 Spine 解析结果和纹理作为只读资源安全共享;
- Spine 资源注册表使用引用计数,删除任一副本不会提前销毁其他副本仍在使用的纹理;
- 粒子系统原有的 Spine 骨骼跟随目标、所属动画和全部效果参数保持不变。
当场景树不存在有效选中对象时,`+Copy` 自动置灰。
### 9.3 时间轴与统计显示稳定性
本阶段补充修复了以下界面问题:
- 画布左下角系统数量和粒子骨骼数量使用固定宽度与等宽数字,单位数和多位数切换时不再推动后方文字;
- 粒子骨骼数量保持左对齐,数值区域位置固定;
- 时间轴当前帧与总帧作为一个整体固定显示,不再被布局拆开;
- 非循环播放到末帧后再次播放会从头开始;
- 再次播放时,粒子活动数量和粒子骨骼数量持续按当前回放帧更新,不再停留在第一次播放结果;
- “所属动画”菜单改为向上展开,避免靠近屏幕底部时被裁切;
- 修复向上菜单因时间轴父级裁切和层级关系导致点击无效的问题。
### 9.4 画布背景图
画布顶部工具栏增加“背景图”按钮。下拉面板提供:
| 控件 | 行为 |
|---|---|
| 上传图像 / 更换图像 | 选择 `.png`、`.jpg`、`.jpeg` 或 `.webp` 图片 |
| 清除背景 | 移除当前背景并释放纹理资源 |
| X / Y | 调整背景在画布世界坐标中的位置偏移 |
| 透明度 | 在 0%~100% 范围内调整背景显示透明度 |
背景图片锚点位于图片中心,使用与其他场景对象一致的世界坐标方向,并跟随画布整体缩放和平移。
背景 Sprite 是 `world` 容器中的第一个绘制节点,因此显示优先级低于:
- Spine;
- 粒子系统;
- 碰撞体;
- 路径;
- 发射点、变换工具和其他画布辅助图形。
背景图只用于编辑器预览,不参与粒子碰撞、路径采样、时间轴长度或导出数据计算。
“清除背景”在没有图片时置灰。执行清除后:
```text
背景资源:无
X / Y:0 / 0
透明度:100%
```
同时撤销临时对象 URL 并释放 PixiJS 纹理,避免反复更换或清除图片产生资源泄漏。
项目中的测试图片为:
```text
public/background/bg.png
```
---
## 十、验证结果
本阶段使用 `public/spine/` 中的 Spine 4.2 测试资源完成验证:
- `bingo.json`;
- `bingo.atlas`;
- `bingo.png`;
- `bingo_2.png`。
已验证:
- Spine 骨骼名称搜索、折叠、选择和清除;
- 层级树选择后立即绘制骨骼位置点;
- 选择 `root` 后,骨骼跟随空间和偏移参数正常显示;
- 层级树改选 `zong` 后,已有 `root` 绑定保持不变;
- 点击重新绑定后,当前粒子系统单独切换为 `zong`;
- 多粒子系统配置独立保存 Spine ID 和骨骼名称;
- 骨骼目标失效检测及无跟随回退逻辑;
- 本地空间和世界空间不继承 Spine/骨骼旋转与缩放;
- 粒子、碰撞体、路径和 Spine 小眼睛状态切换;
- 全局 Spine 显示设置已经移除;
- Spine 预览动画下拉框与属性面板同步;
- 所属动画默认 `animation`;
- 所属动画新增、重命名和删除;
- 删除最后一个动画被禁止;
- 两个粒子系统分别保存 `animation` 与 `animation2`;
- Spine 文件夹选择可以一次识别骨架数据、Atlas 和多纹理资源;
- `+Copy` 可以分别复制粒子、碰撞体、路径和 Spine;
- 修改粒子副本名称后,原粒子系统名称保持不变;
- Spine 副本可以安全共享已加载资源;
- 背景图下拉面板正常显示上传、清除、X/Y 和透明度控件;
- `public/background/bg.png` 可以成功加载到画布底层;
- 背景图 X/Y 偏移与透明度可以实时调整;
- 未加载背景时“清除背景”按钮正确置灰;
- 统计数字和时间轴帧数显示不再因位数变化产生布局跳动;
- 页面交互过程中无控制台错误;
- `npm run build` 生产构建通过;
- `git diff --check` 通过。
构建仍存在 Vite 主产物超过 500 kB 的体积提示,不影响构建结果和本阶段功能。
---
## 十一、当前限制
1. Spine 资源仍通过本地文件选择器加载,刷新后需要重新选择;
2. Spine 皮肤、插槽、约束和动画混合尚未开放;
3. 骨骼跟随当前只使用骨骼位置,不提供继承骨骼旋转或缩放的可选模式;
4. 骨骼跟随目标使用 Spine ID 和骨骼名称,工程保存与导入格式尚未落地;
5. 所属动画当前只保存分组关系,尚未影响时间轴轨道筛选、播放范围或导出结果;
6. 场景对象小眼睛状态尚未纳入持久化工程文件;
7. Spine 预览动画当前一次控制一个预览对象,尚未提供多 Spine 联动选择界面;
8. 主包仍包含 Spine 运行时和主要编辑器模块,尚未进行动态拆包;
9. 背景图片来自浏览器本地文件选择,刷新页面后需要重新上传;
10. 背景图当前只提供位置和透明度,不提供旋转、缩放、适配画布或锁定操作;
11. 背景图仅用于编辑器显示,尚未纳入工程保存格式和最终导出;
12. 场景对象复制当前在内存场景中完成,工程保存与跨项目粘贴仍待后续实现。
---
## 十二、阶段九建议
1. 建立正式场景工程保存格式,持久化对象显隐、所属动画和骨骼绑定;
2. 实现项目打开、保存、另存为和自动恢复;
3. 把 Spine、Atlas 和纹理复制到工程资源目录并建立相对路径引用;
4. 实现按“所属动画”筛选时间轴轨道和播放范围;
5. 设计导出数据结构,输出动画列表、粒子系统归属和 Spine 骨骼绑定;
6. 为骨骼跟随增加可选的旋转继承、缩放继承和轴向过滤;
7. 增加 Spine 皮肤、插槽和动画混合控制;
8. 建立多 Spine、多粒子系统、多骨骼绑定的自动回归测试;
9. 对 Spine 面板和曲线编辑器进行动态加载,降低主包体积;
10. 补充资源丢失、骨骼改名和动画删除后的导入迁移提示;
11. 将背景图路径、位置和透明度纳入工程文件,并支持重新定位丢失资源;
12. 为背景图增加适配画布、原始尺寸、缩放和锁定操作;
13. 为场景对象复制增加撤销/重做,以及跨工程复制粘贴支持。
+361
View File
@@ -0,0 +1,361 @@
# 网页粒子系统 · 阶段六
> 阶段定位:**场景对象、碰撞体、路径编辑与发射器路径跟随定版**
> 完成日期:2026-08-29
> 工程目录:`/Users/tianmokeji/Desktop/SpineParticle`
> 技术栈:Vue 3 + TypeScript + Pinia + PixiJS 7 + Vite
---
## 一、阶段结论
阶段六完成了碰撞功能所需的场景对象基础,并把粒子系统、碰撞体和路径统一纳入同一套场景对象树。碰撞体已经参与粒子运行时计算;路径具备可视化锚点、贝塞尔手柄和预设形状编辑能力;粒子系统新增发射器路径跟随修改器。
本阶段主要完成:
1. 统一场景对象创建入口和对象列表;
2. 新增碰撞体对象、属性面板、画布显示和实时碰撞响应;
3. 新增路径对象、锚点及贝塞尔手柄编辑;
4. 新增圆形、方形、多边形和星形路径预设;
5. 新增发射器路径跟随修改器;
6. 支持局部空间和世界空间两种跟随方式;
7. 整理画布左上角系统设置入口;
8. 修复新模块与既有粒子属性、固定帧缓存和时间轴的兼容问题;
9. 修复时间轴粒子条拖动时反复重置模拟的问题。
Spine 骨架创建入口在本阶段仅保留占位,尚未创建实际骨架对象。碰撞体面板中的“跟随轨道移动”也保留为后续碰撞体动画接口,本阶段未接入路径采样。
---
## 二、场景对象统一管理
### 2.1 创建入口
原编辑器顶部的“+系统”入口已合并到“场景对象”模块。场景对象标题右侧提供:
- `+粒子`:创建粒子系统;
- `+碰撞`:创建碰撞体;
- `+路径`:创建路径;
- `+骨架`:Spine 骨架占位入口。
即使场景中没有粒子系统,场景对象模块仍可独立创建碰撞体或路径。
### 2.2 对象树
创建后的对象统一显示在场景对象树中,不再保留顶部独立的粒子系统列表。对象树支持:
- 粒子系统、碰撞体和路径混合显示;
- 点击对象切换当前属性面板;
- 当前对象高亮;
- 单独删除对象;
- 删除当前对象后自动选择可用的回退对象。
默认对象名称使用英文编号:
| 对象类型 | 默认名称 |
|---|---|
| 粒子系统 | `ParticleSystem1` |
| 碰撞体 | `Collider1` |
| 路径 | `Path1` |
---
## 三、碰撞体对象
### 3.1 默认参数
| 参数 | 默认值 |
|---|---:|
| 名称 | `Collider1` |
| 标签 | `Default` |
| 类型 | 圆形 |
| 启用 | 开启 |
| X / Y | 0 / 0 |
| 旋转 | 0° |
| 半径 | 50 |
| 椭圆 X / Y 半径 | 70 / 45 |
| 矩形宽 / 高 | 120 / 80 |
| 多边形边数 | 6 |
| 碰撞响应 | 物理 |
| 弹性 | 0.8 |
| 摩擦 | 0.3 |
| 跟随轨道移动 | 关闭,后续接入 |
### 3.2 类型
碰撞体支持五种类型:
1. 圆形;
2. 椭圆;
3. 矩形;
4. 多边形;
5. 自定义。
圆形使用半径;椭圆使用 X/Y 半径;矩形、多边形和自定义形状使用宽高,多边形与自定义形状还可设置 3~16 的边数。
当前“自定义”采用可调边数的规则多边形作为运行时轮廓,为后续接入自由顶点编辑保留类型。
### 3.3 标签
- 新建碰撞体默认拥有 `Default` 标签;
- 点击标签可设为当前标签;
- 点击 `+` 自动创建 `Tag N`;
- 标签数据归属于各碰撞体对象。
本阶段标签已完成编辑和存储,尚未作为粒子碰撞过滤条件。
### 3.4 碰撞响应
| 响应 | 行为 |
|---|---|
| 物理(弹跳/摩擦) | 把粒子推出碰撞体,根据法线反射速度,并应用弹性和切向摩擦 |
| 暂停(停止不动) | 粒子停留在碰撞边界,速度清零,但自身生命周期继续消耗 |
| 消失(立即销毁) | 粒子进入碰撞体后立即死亡并回收到粒子池 |
弹性和摩擦限制在 `0~1`:
- 弹性 `1` 表示完全弹跳,`0` 表示不保留法线反弹速度;
- 摩擦 `1` 表示停止切向滑动,`0` 表示保留切向速度。
### 3.5 坐标与运行时
碰撞体使用场景世界坐标。每帧把粒子局部位置和速度转换到世界空间完成碰撞检测,再把修正后的位置和速度变换回粒子系统局部空间。因此粒子系统经过位移、旋转、缩放或发射器路径跟随后,仍能与固定世界碰撞体正确交互。
原修改器面板中的全局“碰撞体开关”已移除。是否参与碰撞由每个碰撞体自己的“启用”属性控制。
---
## 四、路径对象与画布编辑
### 4.1 路径属性
每条路径独立保存:
- 英文名称 `PathN`;
- 启用状态;
- 对象位置和旋转;
- 是否闭合;
- 轨道颜色;
- 锚点及入/出贝塞尔手柄;
- 当前预设类型。
路径使用三次贝塞尔曲线连接相邻锚点。闭合路径会额外连接最后一个锚点与第一个锚点。
### 4.2 锚点操作
- 双击空白处或曲线:新建锚点;
- 双击锚点:删除节点;
- 双击第二下按住拖动:新建锚点并拉出对称手柄;
- 拖动锚点:移动节点;
- 拖动手柄:调整曲线;
- `Shift + 拖动手柄`:两侧手柄长度与方向完全对称;
- `Alt + 拖动手柄`:独立控制单侧手柄;
- `Ctrl/Command + 点击或框选`:加选节点;
- `Alt + 点击或框选`:减选节点;
- `Alt + 单击空白处`:清空选择;
- 选中多个节点后拖动:整体移动所选节点。
路径锚点、手柄、连线和选择状态均直接绘制在画布中。
### 4.3 路径预设
| 预设 | 生成结果 |
|---|---|
| 圆形 | 4 个锚点和贝塞尔手柄组成的闭合圆 |
| 方形 | 4 个直线锚点组成的闭合方形 |
| 多边形 | 6 个锚点组成的闭合规则多边形 |
| 星形 | 10 个交替半径锚点组成的闭合星形 |
“应用预设”会替换当前路径锚点,“清空锚点”只清除节点,不删除路径对象。
---
## 五、发射器路径跟随
### 5.1 默认状态
| 参数 | 默认值 |
|---|---:|
| 开启发射器跟随 | 关闭 |
| 跟随模式 | 无 |
| 跟随轨道 | 无 |
| 发射器角度模式 | 固定角度 |
| 移动进度曲线 | 0→1 线性 |
| 移动时长 | 2 秒 |
| 跟随空间 | 世界空间 |
| 移动方向 | 正向 |
| 偏移起始点 | 0 |
开启修改器后,跟随模式仍默认为“无”;只有主动选择“跟随轨道”并指定有效路径后,发射器才开始移动。
### 5.2 移动进度
路径按实际采样长度计算进度,避免不同长度曲线段使用相同参数步长后产生明显速度差异。
```text
归一化时间 = 当前时间 / 移动时长
路径进度 = 移动进度曲线(归一化时间)
```
偏移起始点限制在 `0~1`,用于跳过路径开头部分。反向模式从终点向起点采样,并反转移动方向角度。
### 5.3 发射器角度
| 模式 | 行为 |
|---|---|
| 固定角度 | 保持粒子系统原有根旋转 |
| 跟随移动方向 | 根据路径当前位置前后的采样点计算切线方向 |
角度会继续叠加到粒子的发射方向和锥形散布计算中。
### 5.4 跟随空间
| 空间 | 行为 |
|---|---|
| 局部空间(GEN 移动) | 移动整个发射器容器,已经出生的粒子随发射器整体移动 |
| 世界空间(粒子生成点移动) | 只移动新粒子的出生点,已经出生的粒子保留在原世界位置 |
碰撞、力场和吸附等使用世界坐标的模块同步使用当前模拟变换,避免启用路径跟随后发生坐标错位。
---
## 六、系统设置与画布辅助显示
原画布左上角的“发射点、骨骼连线、蒙皮网格”三个复选项已移动到系统设置面板。系统设置入口移动到画布左上角,并直接显示文字“系统设置”。
系统设置当前包括:
- 发射点显示,默认开启;
- 骨骼连线显示,默认关闭;
- 蒙皮网格显示,默认关闭;
- 坐标轴刻度开关;
- 刻度文字大小;
- 刻度线粗细;
- 坐标轴颜色;
- 刻度数值显示。
### 6.1 模块级重置
以下面板标题右侧统一增加“重置”按钮:
- 发射模式;
- 发射器形状;
- 粒子属性;
- 外观与资源;
- 修改器;
- 导出设置占位。
重置只恢复当前模块在 `defaultConfig()` 中的默认参数,不修改粒子系统名称、场景位置、旋转、缩放、随机种子或其他模块。外观与资源会恢复默认 `star.png` 资源,修改器会恢复全部关闭状态及默认 `trail.png` 拖尾资源。点击重置按钮不会改变面板当前的展开或折叠状态。
---
## 七、兼容性与时间轴修复
### 7.1 帧率基准
编辑器固定帧率统一为 **30 FPS**,与 Spine 默认时间基准对齐:
```text
1 秒 = 30 帧
固定模拟步长 = 1 / 30 秒
```
时间轴显示、秒数与帧数换算、拖动吸附、总帧数计算、粒子固定步长模拟和发射器路径跟随均读取同一个帧率配置,不再单独写死 60 FPS。
时间轴视口默认显示 `0~120f`,相当于 4 秒可视范围;实际系统总帧数仍根据发射时长和最长粒子生命动态计算,滚轮可继续缩放显示范围。
### 7.2 粒子属性与时间轴总长度
时间轴总帧数根据各粒子系统最晚消失时间计算:
```text
总时长 = 延迟时间 + 发射持续时间 + 当前模式下的最大粒子生命
```
- 固定生命周期使用 `lifeMin`;
- 随机生命周期使用 `lifeMin`、`lifeMax` 中的较大值;
- 多个粒子系统取最晚结束时间。
修复前固定生命周期仍按 `lifeMax` 计算,时间轴可能提前循环,使生命周期、速度、缩放等参数看起来没有生效。
### 7.3 时间轴粒子条语义
时间轴粒子条使用一个延迟段和两层叠加色条:
1. 左侧空白:发射延迟 `delay`;
2. 红色底条:整个粒子系统仍有粒子存在的总时长,不可直接缩放;
3. 绿色覆盖层:持续发射时长 `duration`,仅持续模式显示,可拖动。
持续发射时长默认 1 秒,即 30f;拖动绿色层到 60f 表示持续发射 2 秒。持续模式下,红色底条长度为“绿色发射时长 + 最长粒子生命”,绿色覆盖红色底条的前半段,停止发射后露出的红色尾段表示剩余粒子继续存活的时间。因此红色底条一定比绿色层长。
爆发模式没有持续发射窗口,因此不显示绿色覆盖层,红色底条长度就是最长粒子生命。
红色生命段根据粒子属性模式自动计算:固定模式使用固定生命值,随机模式使用上下限中的较大值。用户修改生命参数后,红色段和总帧数同步更新。
拖动整个叠加条仍用于修改发射延迟。持续发射的最短时长为 1 帧。
### 7.4 拖动重算修复
修复前,鼠标每次移动都会直接写入配置,触发固定帧缓存清空和粒子模拟重置,造成粒子跳动、数量异常或短暂停止。
现在的处理流程为:
1. 按下并拖动时只更新本地条带预览;
2. 粒子运行时保持当前模拟,不反复清空;
3. 松开鼠标后一次性提交最终值;
4. 固定帧缓存只重建一次;
5. 取消拖动不修改配置;
6. 拖长操作不再被修改前的总帧数错误限制。
切换持续/爆发模式不再承担“恢复异常状态”的作用;模式切换仅按当前配置正常重新模拟。
---
## 八、数据与迁移
粒子系统配置新增发射器跟随字段,旧配置通过 `ensureEmitterConfig()` 自动补齐默认值:
- `emitterFollow`;
- `emitterFollowMode`;
- `emitterFollowPathId`;
- `emitterAngleMode`;
- `emitterFollowCurve`;
- `emitterFollowDuration`;
- `emitterFollowSpace`;
- `emitterFollowDirection`;
- `emitterFollowOffset`。
碰撞体和路径存储在 Pinia 场景状态中,和粒子系统共同参与配置变化监听。对象、路径锚点或碰撞参数改变后会清空旧固定帧并从第 0 帧重新生成。
---
## 九、验证结果
本阶段完成以下验证:
- 粒子系统、碰撞体和路径的创建、选择、删除与英文默认命名;
- 圆形、椭圆、矩形和规则多边形碰撞检测;
- 物理、暂停和销毁三种碰撞响应;
- 路径预设、锚点新增删除、多选移动和手柄调整;
- 发射器跟随默认“无”及有效路径选择;
- 30 FPS 时间基准,确认 1 秒对应 30 帧;
- 正向/反向、固定角度/移动方向;
- 局部空间和世界空间跟随;
- 路径跟随与粒子初始生命周期、速度、缩放、旋转的组合;
- 固定生命周期与时间轴总帧数联动;
- 时间轴延迟拖动和持续时间缩放只在松手后重建缓存;
- 各模块重置按钮的默认值恢复与面板展开状态保持;
- 页面运行无可见错误;
- `npm run build` 生产构建通过。
---
## 十、阶段七建议
1. 实现碰撞体自由顶点编辑,使“自定义”类型拥有独立轮廓;
2. 把碰撞标签接入粒子系统过滤规则;
3. 实现碰撞体跟随路径移动;
4. 支持路径循环、往返和超出移动时长后的行为设置;
5. 增加碰撞事件输出,为音效、子粒子或动画触发提供接口;
6. 开始接入 Spine 骨架场景对象和骨架资源管理;
7. 为场景对象、路径和碰撞体补充保存、导入与导出格式。
+520
View File
@@ -0,0 +1,520 @@
# 网页粒子系统 · 阶段十
> 阶段定位:**粒子图片序列帧与循环持续三段动画体系定版**
> 完成日期:2026-08-31
> 工程目录:`/Users/tianmokeji/Desktop/SpineParticle`
> 技术栈:Vue 3 + TypeScript + Pinia + PixiJS 7 + Spine Runtime 4.2 + Vite
---
## 一、阶段结论
阶段十在阶段九配置持久化、撤回和预设体系的基础上,扩展了粒子图片资源的序列帧能力,并完成了持续发射模式下“启动—循环—结束”三段动画的编辑和预览模型。
图片资源现在可以在固定图片和序列帧之间切换。每个序列帧资源仍属于同一个粒子和同一根粒子骨骼,只在播放过程中替换纹理,不会因为一组序列图而生成多根粒子骨骼。
持续发射模式新增“常规持续”和“循环持续”。循环持续通过稳定周期预演、确定性随机序列、完整帧缓存和假死粒子状态解决首尾衔接问题,并支持分别生成启动段、纯循环段和结束段。
本阶段主要完成:
1. 图片资源增加固定图片与序列帧两种模式;
2. 支持多选图片并按自然文件名顺序组成序列;
3. 支持单次尾帧消失、单次尾帧固定、正向循环和反向循环;
4. 支持独立序列帧播放帧率;
5. 序列帧状态纳入粒子录制、回放、保存、加载和撤回;
6. 持续发射增加常规持续与循环持续两种表现;
7. 循环时长使用独立整数帧参数,不覆盖常规持续秒数;
8. 实现启动段、纯循环段、结束段三种互斥表现;
9. 建立统一的循环衔接帧;
10. 使用周期预演避免末帧硬复制造成的运动突变;
11. 使用透明度为 0 的假死粒子保持纯循环段骨骼集合稳定;
12. 支持循环模式下的延迟时间与首帧保持;
13. 循环模式自动禁用并重置 Spine 骨骼跟随和路径跟随;
14. 修复循环重新计算期间缓存帧与实时状态交替造成的闪烁;
15. 时间轴支持选择包含结束端点的最后一帧;
16. 多粒子系统独立计算,整体时间轴继续取粒子和 Spine 中最长者;
17. 完成 TypeScript 与 Vite 生产构建验证。
---
## 二、图片序列帧资源
### 2.1 图片模式
每张粒子图片资源卡片增加“图片模式”:
| 模式 | 说明 |
|---|---|
| 固定 | 粒子生命周期内始终使用同一张图片 |
| 序列帧 | 按粒子出生后的时间依次切换多张图片 |
模式属于单个图片资源卡片。一个粒子系统可以同时存在多个图片资源,每个资源可以分别选择固定图片或序列帧,并继续使用原有权重随机选择逻辑。
资源卡片原有参数保持不变,包括:
- 权重与权重锁定;
- 缩放;
- 锚点;
- 颜色模式与颜色渐变;
- 混合模式;
- 独立透明度与透明度曲线。
### 2.2 序列图片选择
选择序列帧时允许一次多选图片。编辑器会按文件名进行自然排序,而不是简单字符串排序。
例如:
```text
diwen_1.png
diwen_2.png
diwen_10.png
```
会按照 `1 → 2 → 10` 排列。
界面明确显示实际播放顺序:
```text
已选择 13 帧,按 diwen_000.png-diwen_012.png 顺序播放
```
阶段测试资源位于:
```text
public/序列帧测试/sg/
```
### 2.3 播放模式
序列帧支持四种播放模式:
| 模式 | 行为 |
|---|---|
| 单次-尾帧消失 | 正向播放一次,超过最后一张后隐藏粒子图片 |
| 单次-尾帧固定 | 正向播放一次,之后保持最后一张图片 |
| 正向循环 | 第一张播放到最后一张,再回到第一张 |
| 反向循环 | 最后一张播放到第一张,再回到最后一张 |
“尾帧消失”只隐藏粒子图片。粒子逻辑、粒子骨骼和拖尾仍可继续存在,直到粒子生命周期结束。
### 2.4 播放帧率
每个序列帧资源拥有独立播放帧率:
```text
sequenceFps
```
有效范围为 1~120 FPS,默认 30 FPS。序列帧索引根据粒子实际出生时间计算,不依赖浏览器刷新次数。
### 2.5 单粒子单骨骼原则
序列帧不会为每张图片创建独立骨骼。
运行规则为:
```text
一个粒子实例 = 一个粒子骨骼 = 一个 Sprite
多张序列图片 = 同一个 Sprite 在不同时间替换 Texture
```
因此 13 张序列图片仍只使用一根粒子骨骼,避免序列帧数量直接放大骨骼数量。
### 2.6 录制和持久化
粒子逐帧状态增加:
```text
imageFrameIndex
imageVisible
```
录制和回放会恢复:
- 当前序列帧索引;
- 当前纹理;
- 当前图片显隐;
- 资源锚点;
- 资源混合模式。
配置保存会保存序列帧文件名、预览数据和播放参数;加载时重新创建纹理。撤回快照也包含序列帧资源配置,因此选择图片、切换模式和调整播放方式均可撤回。
---
## 三、持续发射表现模式
### 3.1 两种表现
持续发射模式下增加:
| 表现 | 说明 |
|---|---|
| 常规持续 | 保持原持续发射逻辑,结束发射后等待存活粒子死亡 |
| 循环持续 | 生成可以拆分为启动、循环和结束的稳定周期动画 |
发射速率等已有参数继续共用。常规持续时长和循环持续时长分别保存,不会互相覆盖。
### 3.2 独立时长数据
常规持续继续使用:
```text
duration: number // 秒
```
循环持续新增:
```text
loopDurationFrames: number // 整数帧
```
默认循环时长为 30f,可编辑范围为 1~1800f。播放仍固定按照:
```text
30f/s
```
旧配置没有 `loopDurationFrames` 时,会把原持续秒数乘以 30 并向上取整作为迁移初值。此后两种时长完全独立。
例如:
```text
常规持续 = 1.00s
循环持续 = 54f
```
在两种表现之间切换不会改变对方的数值。
---
## 四、循环持续三段动画
### 4.1 三种状态
循环持续面板包含两个附加开关:
- 生成开始循环动画;
- 生成结束循环动画。
两个开关互斥,不能同时开启。
| 开始开关 | 结束开关 | 当前生成内容 |
|---|---|---|
| 关 | 关 | 纯循环段 |
| 开 | 关 | 启动—循环段 |
| 关 | 开 | 循环—结束段 |
### 4.2 循环衔接帧
稳定循环状态的周期边界称为“循环衔接帧”。它包含:
- 粒子骨骼集合;
- 粒子位置、旋转和缩放;
- 颜色与透明度;
- 图片资源与序列帧索引;
- 图片显隐;
- 拖尾骨骼、宽度、颜色和透明度;
- 修改器计算后的最终结果。
启动段末端、纯循环段周期边界和结束段起点使用同一套确定性计算规则。
### 4.3 启动段
启动段规则:
1. 第一帧完全为空;
2. 粒子骨骼数量为 0;
3. 延迟期间保持空帧;
4. 延迟结束后开始正常生成粒子;
5. 粒子使用正常生命周期,可以在启动过程中死亡;
6. 系统自然预热到稳定循环状态;
7. 最后一帧到达循环衔接状态。
当最大粒子寿命跨越多个循环周期时,启动段会自动使用足够的预热周期,保证末端确实进入稳定循环分布。
### 4.4 纯循环段
纯循环段规则:
1. 延迟期间保持循环衔接帧;
2. 延迟结束后播放一个完整循环周期;
3. 周期长度由 `loopDurationFrames` 决定;
4. 发射事件、随机参数和资源选择在每个周期使用相同确定性序列;
5. 周期末端由真实模拟自然到达下一周期起点;
6. 不再把末帧强制替换为首帧。
这一实现保证的不只是边界两个快照一致,还保证倒数帧到结束端点、结束端点到下一周期的运动连续,不会在最后两帧突然跳变或卡顿。
### 4.5 结束段
结束段规则:
1. 第一帧为循环衔接状态;
2. 延迟期间保持该衔接状态;
3. 延迟结束后立即停止生成新粒子;
4. 已存在粒子继续按正常生命周期运行;
5. 拖尾随粒子继续更新;
6. 最后一帧所有粒子和拖尾消失。
结束段不使用发射时长作为消亡长度。长度根据衔接帧中粒子的最大剩余生命和拖尾状态自动计算。
---
## 五、稳定循环计算
### 5.1 完整周期预演
早期实现只把纯循环末帧复制为首帧。该方式虽然能让两个快照数值相同,却会使倒数帧直接跳到衔接状态,造成明显卡顿。
当前流程改为:
1. 根据循环时长计算每周期帧数;
2. 根据最大粒子寿命计算需要预演的周期数;
3. 从确定性随机种子开始模拟;
4. 每个新周期重置发射计时和随机序列;
5. 保留跨周期仍存活粒子的物理状态;
6. 预演到粒子分布稳定;
7. 记录下一完整周期及结束端点;
8. 时间轴循环时直接回放缓存结果。
### 5.2 周期随机序列
每个周期重新开始:
- 发射累计器;
- 发射计时;
- 粒子生成序号;
- 全局随机源;
- 与发射周期相关的吸附计时。
仍存活粒子不会在周期边界被重置,其运动会继续自然推进。
噪声相位、拖尾资源采样和吸附曲线方向改为使用周期内粒子生成序号,不再依赖粒子池槽位。即使粒子池复用了不同编号,也不会改变下一周期相同发射事件的视觉属性。
### 5.3 假死粒子
纯循环段需要保持粒子骨骼集合稳定。某粒子在某些帧已按正常生命周期死亡时,录制缓存不会直接删除对应骨骼,而是写入假死状态:
```text
active = true
alpha = 0
imageVisible = false
trailState = undefined
```
因此:
- 视觉上粒子已经死亡;
- 图片和拖尾均不可见;
- 逻辑上粒子骨骼仍存在;
- 周期内不会因为骨骼突然增删破坏衔接;
- 后续正常出现时仍可继续使用同一骨骼轨道。
假死只用于纯循环段。启动段和结束段仍使用正常粒子死亡逻辑。
### 5.4 缓存帧闪烁修复
循环段会在录制首帧一次性生成完整缓存。此前在全局时间轴第一次录制结束前,固定帧之间仍会调用 `update(0)` 显示粒子内部实时状态,导致画面在以下两套状态之间交替:
- 已计算好的循环缓存帧;
- 周期预演结束后的内部状态。
表现为重新计算后粒子高频闪烁,直到时间轴完整运行一遍才恢复。
当前逻辑在循环缓存存在后立即全程读取缓存,不再夹杂实时状态。
---
## 六、延迟时间
循环持续继续使用原“延迟时间”参数,三种状态均支持。
| 状态 | 延迟期间保持内容 |
|---|---|
| 启动段 | 空的第一帧 |
| 纯循环段 | 循环衔接帧 |
| 结束段 | 循环衔接帧 |
延迟结束后才开始推进对应动画。
时间轴中:
- 粒子条左侧位置继续表示延迟;
- 可以拖动粒子条调整延迟;
- 整体播放长度包含延迟帧;
- 延迟期间画布不是重复重新计算,而是保持第一帧缓存。
---
## 七、跟随功能限制
循环持续不与 Spine 骨骼跟随或路径跟随联动。原因是外部目标动画或路径进度可能不具备相同周期,无法保证粒子周期边界稳定。
进入或退出循环持续时均弹出提示:
```text
粒子跟随设置会失效并恢复默认值
```
只有用户确认后才切换。
确认后重置:
- 发射器跟随总开关;
- 跟随模式;
- 路径 ID;
- Spine ID 与骨骼名;
- 跟随空间和方向;
- 路径进度曲线;
- 位移、旋转和缩放偏移。
循环持续期间跟随控件不可重新开启。切回常规持续后控件恢复可用,但不会自动恢复进入循环前的旧跟随设置。
---
## 八、时间轴规则
### 8.1 结束端点
时间轴内部保存的是快照数量,界面显示的是最后一个可选帧编号。
例如动画长度为 54f:
```text
可选帧:0f~54f
快照数量:55
```
因此:
- 可以拖动到第 54 帧;
- “移动到最后一帧”会到达第 54 帧;
- 后一帧按钮会正确限制在第 54 帧;
- 非循环播放会停在第 54 帧。
### 8.2 轨道显示
| 状态 | 时间轴表现 |
|---|---|
| 常规持续 | 绿色发射段 + 红色粒子存活尾段 |
| 启动段 | 延迟偏移 + 启动预热发射段 |
| 纯循环段 | 延迟偏移 + 一个完整循环周期 |
| 结束段 | 延迟偏移 + 只有粒子存在/消亡段 |
结束段不显示可调整的绿色发射时长,因为该段不会产生新粒子。
### 8.3 多系统与 Spine
每个粒子系统独立计算自己的:
- 循环时长;
- 预热周期;
- 循环衔接状态;
- 启动或结束长度;
- 延迟帧。
整体时间轴仍取以下对象的最长结束时间:
- 全部粒子系统;
- 当前加载并选择动画的 Spine 对象;
- 已存在的其他时间轴场景动画数据。
较短粒子系统到达末端后保持末帧,直到整体时间轴结束。
---
## 九、配置、预设与撤回联动
阶段十新增字段均属于粒子系统配置:
```text
streamBehavior
loopDurationFrames
generateLoopStartAnimation
generateLoopEndAnimation
imageMode
sequencePlayback
sequenceFps
sequenceFrames
```
因此它们自动纳入:
- JSON 配置保存;
- JSON 配置加载;
- `public/Preset/` 预设加载;
- 对象复制;
- 编辑器撤回快照;
- 模块重置。
加载旧配置时会补齐默认值,并处理:
- 旧 `once` 序列模式迁移为 `once-hold`;
- 旧 `loop` 序列模式迁移为 `loop-forward`;
- 缺少循环时长时从旧持续秒数换算初值;
- 启动和结束开关同时为真时自动保留启动段、关闭结束段;
- 缺少序列帧字段时恢复固定图片模式。
粒子逐帧缓存不写入配置。配置加载、撤回或参数修改后会根据当前确定性配置重新计算。
---
## 十、主要代码落点
| 文件 | 阶段十职责 |
|---|---|
| `src/core/particleEmitter.ts` | 序列帧纹理切换、循环配置字段、强制/停止发射控制、周期随机源重启 |
| `src/views/ImageResourceCard.vue` | 图片模式、播放模式、播放帧率、序列文件和顺序提示 |
| `src/views/ParticlePanel.vue` | 序列帧选择、循环持续面板、独立帧时长、互斥开关、跟随重置确认 |
| `src/views/Stage.vue` | 三段动画烘焙、周期预演、衔接状态、假死粒子、缓存回放与闪烁修复 |
| `src/views/Timeline.vue` | 循环轨道宽度、延迟偏移、结束段显示和末端帧选择 |
| `src/store/particleStore.ts` | 三种循环状态总帧数、多系统和 Spine 最长时间计算 |
| `src/editor/editorConfig.ts` | 序列图片数据保存、纹理重建和旧配置恢复 |
---
## 十一、验证结果
本阶段完成以下静态与构建验证:
```text
npm run build
```
结果:
- `vue-tsc -b` 通过;
- Vite 生产构建通过;
- 548 个模块完成转换;
- 无 TypeScript 错误;
- 无构建错误;
- 仅保留既有的大包体积提示。
交互修复过程中已覆盖:
- 循环时长与常规持续时长独立;
- 纯循环末端不再硬复制首帧;
- 延迟期间保持第一帧;
- 时间轴可以到达结束端点;
- 重新计算期间不再交替显示内部实时状态;
- 配置字段具备旧数据迁移默认值。
---
## 十二、阶段十最终状态
阶段十完成后,编辑器已经具备:
- 单图和序列帧混合资源;
- 多种序列播放方式;
- 确定性粒子序列帧录制;
- 常规持续与独立循环持续;
- 启动、循环、结束三段动画生成;
- 稳定周期预演;
- 假死粒子骨骼保持;
- 延迟首帧保持;
- 多系统时间轴对齐;
- 配置、预设、复制和撤回联动。
至此,阶段十“序列帧资源与循环持续动画体系”完成落盘。
+771
View File
@@ -0,0 +1,771 @@
# 网页粒子系统 · 阶段十一
> 阶段定位:**Spine JSON 导出体系、拖尾蒙皮定版与曲线编辑能力完善**
> 完成日期:2026-08-31
> 工程目录:`/Users/tianmokeji/Desktop/SpineParticle`
> 技术栈:Vue 3 + TypeScript + Pinia + PixiJS 7 + Spine Runtime 4.2 + Vite
---
## 一、阶段结论
阶段十一在阶段十的序列帧和循环持续动画体系上,完成了第一版可重新导入 Spine 编辑器继续编辑的 Spine 4.2 JSON 导出功能,并围绕实际导入结果持续修正了骨骼层级、关键帧数量、加权 Mesh 拓扑、拖尾生命周期和首帧语义。
本阶段没有把粒子效果导出为不可编辑的逐帧图片,而是把粒子运行结果烘焙为 Spine 可编辑结构:粒子使用独立骨骼和插槽,拖尾使用同级骨骼组与加权 Mesh,运动、旋转、缩放和 RGBA 进入 Spine 时间轴。碰撞、路径和 Spine 预览对象不直接导出,与粒子有关的最终表现均烘焙到粒子动画数据中。
本阶段主要完成:
1. 实现 Spine 4.2 JSON 导出设置与文件下载;
2. 建立独立导出模块、关键帧精简模块、Mesh 构建模块和结构校验模块;
3. 粒子系统导出为系统骨骼、粒子骨骼、插槽和附件;
4. 拖尾导出为固定拓扑加权 Mesh 与同级拖尾骨骼组;
5. 图片只写资源路径,不把图片文件写入 JSON;
6. 隐藏粒子系统不导出,图片缺失时仍保留附件路径;
7. 支持固定图和序列帧附件时间轴;
8. 支持线性与平滑贝塞尔关键帧精简;
9. 按数据类型设置导出误差容限;
10. 修正 Spine 版本字段为 `4.2`;
11. 修正拖尾 Mesh 的 hull、edges、三角形和权重结构;
12. 粒子骨骼增加粒子系统父级,拖尾骨骼改为同级结构;
13. 拖尾整组骨骼同生同死,假死改为插槽透明度控制;
14. 拖尾未展开阶段同时插值位置和旋转;
15. 曲线编辑器实现真正的贝塞尔控制手柄;
16. “跟随导出”模式正确读取全局插值设置,不再反向覆盖设置;
17. 普通发射模式第 0 帧固定为空,循环持续保留独立衔接规则;
18. 修复 `update(0)` 导致爆发数量重复的问题;
19. 画布骨骼调试增加固定屏幕尺寸的方向轴;
20. 拖尾资源增加拖尾比例以及固定/随机长度、宽度;
21. 增加新手使用说明弹窗;
22. 完成 TypeScript、Vite、浏览器交互和 Spine Runtime 解析验证。
详细且长期有效的导出约束另见:
```text
导出设计标准规范.md
```
阶段十一文档记录实际开发结果;导出设计标准规范记录后续导出功能必须继续遵守的标准。
---
## 二、Spine JSON 导出范围
### 2.1 导出对象
当前只导出可见粒子系统:
```text
visible !== false
```
不会直接导出:
- 碰撞对象;
- 场景路径;
- 加载进编辑器用于预览的 Spine 对象;
- 背景图;
- 画布网格、发射点和调试骨骼;
- 编辑器 UI 状态。
如果粒子运动受到碰撞、路径或其他模块影响,导出的是已经计算完成的粒子最终位置、旋转、缩放和颜色,不保留对外部模块的运行时引用。
### 2.2 图片资源
JSON 不内嵌 PNG,也不额外复制图片。附件只写入正确的图片资源路径。导入 Spine 后,由用户重新选择图片文件夹完成扫描。
图片缺失不会阻止导出。导出器会:
1. 保留原图片文件名和路径;
2. 使用可用尺寸或安全占位尺寸建立附件;
3. 在导出结果中返回资源提示;
4. 允许用户在 Spine 中按文件名补齐图片。
默认图片路径:
```text
./images/
```
### 2.3 文件名和版本
默认导出文件名:
```text
SpineParticle.json
```
骨架版本字段固定为:
```json
{
"skeleton": {
"spine": "4.2"
}
}
```
第一版设置面板只提供 Spine 4.2,不伪装支持其他版本。
---
## 三、导出设置
### 3.1 当前参数
导出设置模块包含:
| 参数 | 当前规则 |
|---|---|
| 图片路径 | 写入 `skeleton.images` |
| Spine 版本 | 固定 Spine 4.2 |
| 关键帧插值曲线 | 线性或平滑贝塞尔 |
| 导出 FPS | 默认 30 |
| 不写 fps | 只省略元数据,不改变动画时间 |
| 整数帧对齐 | 调整采样帧率时保持实际秒数不变 |
| 骨骼对象池 | 默认开启,稳定复用粒子骨骼轨道 |
| 排除无内容动画 | 按开关决定是否保留空动画 |
### 3.2 FPS 规则
修改导出 FPS 时保持动画实际秒数不变。
例如原动画为 30 FPS、30 帧,即 1 秒;改为 60 FPS 后会重采样到约 60 帧,仍保持 1 秒,而不是把 30 帧直接解释为 0.5 秒。
“不写 fps”只影响:
```text
skeleton.fps
```
不会改变关键帧的 `time` 秒数。
### 3.3 空动画
空动画是否保留完全遵循“排除无内容动画”设置。
关闭开关时,即使动画当前没有粒子时间轴,也保留空动画名称,方便后续在 Spine 工程内继续编辑或与其他动画合并。
---
## 四、导出骨骼层级
### 4.1 粒子系统父级
每个粒子系统建立一根系统骨骼,直接挂在 `root` 下:
```text
root
└─ ParticleSystem1
```
粒子骨骼和拖尾骨骼全部直接挂在粒子系统骨骼下:
```text
root
└─ ParticleSystem1
├─ _particle_000
├─ _particle_000_trail_1_0
├─ _particle_000_trail_1_1
└─ _particle_000_trail_1_2
```
不再使用以下错误结构:
```text
root
├─ ParticleSystem1_particle_000
├─ ParticleSystem1_particle_001
└─ ParticleSystem1_particle_002
```
### 4.2 拖尾骨骼不组成骨骼链
早期导出把拖尾骨骼首尾相接:
```text
trail_0
└─ trail_1
└─ trail_2
```
这种结构会让子骨骼继承父骨骼的位置、旋转和缩放,导致世界变换逐级累积。网页画布与 Spine 编辑器中的拖尾表现因此不一致。
当前规则是所有拖尾骨骼同级:
```text
ParticleSystem1
├─ trail_0
├─ trail_1
└─ trail_2
```
每根拖尾骨骼独立写入:
- 世界位置;
- 世界旋转;
- 宽度和形状需要的缩放。
导出校验器会阻止拖尾骨骼再次组成父子链。
### 4.3 设置姿态
所有系统骨骼和粒子骨骼的设置姿态保持安全默认值:
```text
位置:0, 0
旋转:0
缩放:1, 1
```
拖尾同级骨骼也从原点设置姿态开始,实际位置和旋转写入动画时间轴,避免设置姿态破坏 Mesh 蒙皮。
附件默认颜色使用透明状态,导入 Spine 后不会在设置模式中干扰已有图像显示。
---
## 五、粒子附件和动画
### 5.1 粒子图片
每根粒子骨骼根据图片混合模式创建相应插槽。支持:
- normal;
- additive;
- multiply;
- screen。
固定图片使用区域附件。序列帧图片在同一插槽中通过 attachment 时间轴切换附件,不会为每张序列图创建新骨骼。
### 5.2 粒子动画时间轴
粒子骨骼导出:
- translate;
- rotate;
- scale。
粒子插槽导出:
- attachment;
- rgba。
粒子出生、死亡、资源切换和序列帧切换会被识别为强制边界,关键帧精简不能跨过这些离散事件破坏状态。
### 5.3 骨骼对象池
开启骨骼对象池后,同一粒子池槽位在多个出生周期中复用同一根导出骨骼。粒子死亡阶段通过附件显隐和 RGBA 时间轴表达,而不是不断创建新的骨骼名称。
该规则显著降低高发射率粒子系统的骨骼数量,并让重新导入 Spine 后的层级更适合继续编辑。
---
## 六、关键帧精简
### 6.1 数据容差
阶段十一最终采用以下导出容差:
| 数据 | 容差 |
|---|---:|
| 粒子位置 | 0.1 像素 |
| 拖尾骨骼位置 | 0.1 像素 |
| 旋转 | 0.01° |
| 缩放 | 0.01 |
| 透明度 | 0.01 |
| RGB | 单通道 1/255 |
这些数值用于判断中间关键帧是否可以由相邻关键帧安全重建。
### 6.2 线性模式
线性模式使用误差受控的数值关键帧精简。如果一段位置、旋转、缩放或颜色变化可以由线性插值在容差内还原,则只保留必要端点。
出生、死亡、附件切换和突变帧不会被普通线性精简跨越。
### 6.3 平滑贝塞尔
平滑贝塞尔模式对逐帧烘焙结果进行分段三次曲线拟合,并把控制值写入 Spine 曲线数据。
该模式主要用于:
- 曲线运动;
- 非线性透明度;
- 平滑颜色变化;
- 受修改器影响的连续轨迹。
拟合不会过度激进,优先保留动画精度和 Spine 编辑器中的可编辑性。
### 6.4 旋转展开
旋转精简前会先展开跨越 `-180°/180°` 的角度序列,避免真实连续旋转被误判为反方向大跳变。
---
## 七、曲线编辑器
### 7.1 三种模式
带“曲线模式”的参数支持:
| 模式 | 行为 |
|---|---|
| 跟随导出 | 读取导出设置的关键帧插值模式 |
| 线性 | 当前参数强制使用线性 |
| 贝塞尔 | 当前参数强制使用贝塞尔 |
“跟随导出”是单向读取关系:
```text
导出设置 → 当前曲线参数
```
它不会反向修改导出设置。
### 7.2 贝塞尔控制手柄
曲线编辑器的贝塞尔模式现在会显示:
- 节点输入手柄;
- 节点输出手柄;
- 节点到手柄的辅助线;
- 三次贝塞尔实际曲线路径。
手柄位置作为曲线点的相对数据保存:
```text
inX / inY
outX / outY
```
这些字段自动进入配置保存、加载、预设和撤回快照。
未手动调整的贝塞尔手柄默认位于相邻节点三分之一位置,因此初始外观仍与线性一致,但用户可以拖动手柄改变曲率。
### 7.3 当前联动范围
当前已接入曲线模式解析的主要参数:
- 粒子系统透明度曲线;
- 图片资源独立透明度曲线;
- 碰撞对象路径跟随曲线。
粒子实时预览与导出使用同一套“跟随导出”解析结果。
---
## 八、拖尾加权 Mesh
### 8.1 固定拓扑
每个拖尾附件只建立一次固定拓扑 Mesh。生命周期中不会改变:
- 顶点数量;
- UV 数量;
- 三角形索引;
- hull 顶点数量;
- edges;
- 顶点骨骼权重结构。
动画只改变拖尾骨骼和插槽数据,绝不在不同帧生成不同数量的网格顶点。
### 8.2 hull 与顶点顺序
Spine JSON 的 `hull` 表示顶点数组开头连续存放的外轮廓顶点数量。Mesh 构建器会先按顺时针顺序写入四周轮廓,再追加内部网格点。
该规则修复了早期导入 Spine 后出现的异常外轮廓、顶点连线和导入失败问题。
### 8.3 权重
每个网格列最多受相邻两根拖尾骨骼影响。权重之和严格为 1。
拖尾骨骼改成同级后,顶点局部位置不再依赖链式设置姿态偏移。每列中心主要由骨骼动画位置与相邻骨骼权重插值得到,网格宽度由骨骼 Y 缩放和局部横向顶点共同表达。
### 8.4 导入前校验
下载前会检查:
- UV 数量是否合法;
- 顶点编码是否合法;
- 三角形是否越界;
- hull 是否越界;
- edges 是否为有效偶数索引;
- 骨骼索引是否存在;
- 每个顶点权重之和是否为 1;
- 加权 Mesh 是否使用了空附件;
- 拖尾骨骼是否错误组成骨骼链。
随后使用项目内 Spine 4.2 Runtime 的 `SkeletonJson` 再解析一次,只有通过后才触发下载。
---
## 九、拖尾骨骼生命周期
### 9.1 整组出生和死亡
一个拖尾 Mesh 绑定的全部骨骼属于同一个固定骨骼组。只要该粒子生成拖尾,这组骨骼必须:
- 同一帧出生;
- 同一帧进入活动状态;
- 同步移动;
- 同一帧死亡或假死。
禁止根据轨迹长度逐节激活骨骼。旧字段 `activeBoneCount` 只保留配置兼容意义,当前有效状态始终等于拖尾骨骼总数。
### 9.2 未完全展开时的位置
拖尾刚生成时,历史轨迹可能短于配置长度。当前规则不是把后方骨骼全部堆叠在最旧点,而是:
1. 从粒子出生点开始记录首个轨迹段;
2. 计算当前已经积累的有效轨迹长度;
3. 在有效轨迹内把全部拖尾骨骼等距分布;
4. 随有效轨迹增长,整组骨骼逐渐展开到完整配置长度。
因此即使拖尾尚未达到完整长度,Mesh 也始终由完整骨骼组驱动。
### 9.3 未完全展开时的旋转
位置插值期间,旋转也必须平滑。
当前做法:
1. 计算头部切线方向;
2. 计算当前尾端切线方向;
3. 将角度差转换为最短旋转方向;
4. 按骨骼序号在头尾角度之间插值;
5. 达到完整轨迹长度后恢复按真实局部切线计算。
这样可以避免同级骨骼方向突然折跳造成网格不平滑。
缩放不参与这段“轨迹不足”的补间,继续由拖尾宽度和形状参数控制。
### 9.4 假死方式
早期方案把尚未出现或死亡的拖尾骨骼缩放到 0。整组骨骼生命周期统一后,不再需要逐骨骼缩放假死。
当前规则:
```text
拖尾存在:插槽 alpha = 实际透明度
拖尾不存在:插槽 alpha = 0
```
骨骼缩放始终保留,原因是缩放还负责:
- 拖尾宽度;
- 粒子宽度跟随;
- 拖尾形状曲线;
- 后续在 Spine 中继续编辑。
出生前使用首次有效骨骼姿态但插槽透明,死亡后保持最后有效骨骼姿态但插槽透明。Mesh 和骨骼对象不会在时间轴中删除和重建。
---
## 十、拖尾比例与随机尺寸
### 10.1 拖尾比例
每张拖尾资源卡片新增:
```text
拖尾比例 (%)
```
有效范围为 0~100:
| 数值 | 行为 |
|---:|---|
| 0 | 选中该资源的粒子均不生成拖尾 |
| 50 | 约一半粒子生成拖尾 |
| 100 | 所有选中该资源的粒子生成拖尾 |
判断发生在粒子出生时,使用粒子系统随机种子和周期内出生序号,因此相同配置与种子可以稳定复现。
当当前发射段计算出的粒子数量为 1、拖尾比例大于 0 时,第一颗粒子强制生成拖尾,避免开启拖尾却因概率采样完全看不到效果。比例明确设置为 0 时不执行保底。
### 10.2 长度和宽度模式
拖尾长度与宽度分别支持:
| 模式 | 参数 |
|---|---|
| 固定 | 单一比例值 |
| 随机范围 | 最小比例和最大比例 |
默认值:
```text
固定长度:100%
固定宽度:100%
随机最小:100%
随机最大:100%
```
尺寸解释:
| 数值 | 行为 |
|---:|---|
| 0 | 不生成拖尾 |
| 100 | 使用标准长度或宽度 |
| 200 | 标准尺寸的 2 倍 |
为支持超过 100 的比例放大,界面当前允许输入 0~500。
随机长度和宽度分别使用独立确定性采样,不会因为其他随机参数增加或删除而改变结果。
### 10.3 导出适配
不同粒子可以拥有不同拖尾长度和宽度:
- 长度通过每根拖尾骨骼的实际动画位置表达;
- 宽度通过骨骼 Y 缩放表达;
- 形状曲线继续叠加到各骨骼宽度缩放;
- 没有生成拖尾的粒子不创建对应拖尾动画内容;
- 某个资源在整条轨道中从未出现拖尾时,不创建无用 Mesh 和拖尾骨骼。
---
## 十一、第 0 帧与发射规则
### 11.1 普通发射模式
以下模式第 0 帧固定为空:
- 爆发;
- 常规持续。
所有粒子发射行为从第 1 帧开始。第 0 帧画布中:
```text
粒子数量 = 0
粒子骨骼数量 = 0
拖尾骨骼数量 = 0
```
### 11.2 循环持续例外
循环持续不受普通第 0 帧空白规则约束,而是继续使用阶段十已经确定的三段逻辑:
| 循环状态 | 第 0 帧 |
|---|---|
| 生成开始循环动画 | 空帧 |
| 纯循环 | 循环衔接帧,可包含骨骼 |
| 生成结束循环动画 | 循环衔接帧,可包含骨骼 |
缓存生成层和画布显示层都按粒子系统分别判断,不会因为同一场景中存在普通系统而错误清空循环系统的衔接帧。
### 11.3 爆发重复修复
时间轴固定帧之间会调用:
```text
update(0)
```
用于保持当前画面而不推进时间。旧爆发判断使用:
```text
burstTimer <= dt
```
当 `dt = 0` 时,`0 <= 0` 被误认为第一次爆发,导致设置单次数量为 1 时实际出现 2 个粒子。
当前增加明确限制:
```text
dt > 0
```
零时间刷新不再触发发射。浏览器回归验证结果:
```text
单次数量 = 1
第 0 帧粒子 = 0
第 1 帧粒子 = 1
```
---
## 十二、画布骨骼调试
### 12.1 骨骼方向轴
开启系统设置中的“骨骼连线”后,每个粒子骨骼和拖尾骨骼点都会显示局部方向轴:
| 颜色 | 含义 |
|---|---|
| 青色 | 骨骼正方向 |
| 黄色 | 与正方向垂直的辅助轴 |
粒子方向轴会处理网页画布 Y 轴向下与数学旋转方向的差异;拖尾方向轴直接使用拖尾轨迹切线旋转。
画布中的拖尾连接线也已改为同级语义,不再把相邻拖尾骨骼画成父子链。
### 12.2 固定屏幕尺寸
骨骼点和方向轴的视觉尺寸不随画布缩放改变。绘制前先把目标屏幕像素除以当前画布缩放,确保放大或缩小画布后调试标记仍保持相同大小。
系统设置新增:
```text
骨骼点绘制大小:默认 1
坐标轴绘制大小:默认 2
```
有效范围为 0.1~10。参数会进入:
- 配置保存;
- 配置加载;
- 预设;
- 撤回快照。
旧配置缺少字段时自动恢复默认值。
---
## 十三、使用说明
画布右上角增加“使用说明”按钮。点击后显示可关闭的新手说明弹窗,内容包括:
- 创建和选择场景对象;
- 粒子常用面板;
- 时间轴播放和逐帧查看;
- 图片、序列帧和拖尾资源;
- 配置保存、加载和预设;
- Spine JSON 导出;
- 常用快捷键。
弹窗不会改变编辑器配置,也不会阻塞画布以外的配置数据。
---
## 十四、配置、撤回与兼容
阶段十一新增的重要配置字段包括:
```text
曲线点:inX / inY / outX / outY
系统设置:bonePointDrawSize / boneAxisDrawSize
拖尾资源:trailRatio
拖尾资源:lengthMode / lengthMin / lengthMax
拖尾资源:widthSizeMode / widthMin / widthMax
```
这些字段会自动进入编辑器 JSON 配置、预设、对象复制和撤回快照。
拖尾资源版本升级到:
```text
trailResourceVersion = 6
```
旧资源迁移规则:
- 缺少拖尾比例时使用 100;
- 缺少长度模式时使用固定;
- 长度随机范围初始化为旧固定长度;
- 缺少宽度模式时使用固定;
- 宽度随机范围初始化为旧固定宽度;
- 旧配置已有的长度和宽度数值保持不变,不强制覆盖为 100;
- 新建资源和重置资源使用 100 的新默认值。
---
## 十五、主要代码落点
| 文件 | 阶段十一职责 |
|---|---|
| `src/export/spineJsonExporter.ts` | Spine JSON 文档、骨骼层级、动画时间轴、拖尾整组显隐、文件下载 |
| `src/export/spineKeyframeReducer.ts` | 线性与贝塞尔关键帧误差精简、控制值拟合、角度展开 |
| `src/export/spineMeshBuilder.ts` | 同级拖尾骨骼加权 Mesh、UV、三角形、hull、edges 和顶点权重 |
| `src/export/spineJsonValidator.ts` | Mesh 结构、拖尾层级与 Spine 4.2 Runtime 解析校验 |
| `src/export/spineExportSettings.ts` | 导出参数默认值与旧设置补齐 |
| `src/core/particleEmitter.ts` | 贝塞尔曲线采样、爆发修复、拖尾比例、随机尺寸、整组骨骼插值和录制状态 |
| `src/views/CurveEditor.vue` | SVG 贝塞尔路径、输入/输出控制手柄和节点编辑 |
| `src/views/ParticlePanel.vue` | 导出设置、拖尾资源比例、固定/随机尺寸、曲线模式与导出按钮 |
| `src/views/Stage.vue` | 第 0 帧规则、循环例外、骨骼方向轴、固定调试尺寸、缓存录制和回放 |
| `src/store/particleStore.ts` | 新系统设置字段和时间轴状态 |
| `src/editor/editorConfig.ts` | 新字段保存、加载、旧配置补齐与纹理恢复 |
| `导出设计标准规范.md` | Spine 导出长期标准与不可回退约束 |
---
## 十六、验证结果
### 16.1 构建
阶段结束执行:
```text
npm run build
```
结果:
- `vue-tsc -b` 通过;
- Vite 生产构建通过;
- 553 个模块完成转换;
- 无 TypeScript 错误;
- 无构建错误;
- 仅保留既有的大包体积提示。
### 16.2 Spine JSON
使用 TrailTest 数据构造拖尾导出样例并通过项目内 Spine 4.2 Runtime 解析:
```text
拖尾骨骼数量:3
拖尾骨骼父级:全部为 ParticleSystem1
动画数量:1
Mesh 顶点和 UV:固定且匹配
```
整组假死验证:
```text
骨骼缩放归零:false
插槽透明度:00 → bf → 00
```
即骨骼缩放完整保留,拖尾通过统一插槽透明度出现和消失。
### 16.3 浏览器交互
已验证:
- 贝塞尔模式显示控制手柄;
- 跟随导出 + 线性时不显示贝塞尔手柄;
- 跟随导出 + 平滑贝塞尔时显示手柄;
- 第 0 帧普通粒子数量为 0;
- 爆发数量 1 时第 1 帧粒子数量为 1;
- 骨骼方向轴正常显示;
- 骨骼点绘制大小默认 1;
- 坐标轴绘制大小默认 2;
- 拖尾比例默认 100;
- 长度和宽度可切换固定与随机范围;
- 随机范围初值正确继承当前固定值。
---
## 十七、阶段十一最终状态
阶段十一完成后,编辑器已经具备:
- 可重新导入 Spine 4.2 编辑器的粒子 JSON;
- 可编辑粒子骨骼、插槽和附件;
- 固定拓扑加权拖尾 Mesh;
- 同级拖尾骨骼结构;
- 整组拖尾骨骼生命周期;
- 插槽透明度假死;
- 未展开拖尾的位置与旋转平滑插值;
- 误差受控的关键帧精简;
- 线性和贝塞尔导出模式;
- 可操作的贝塞尔曲线手柄;
- 普通发射第 0 帧空白规则;
- 循环持续独立第 0 帧衔接规则;
- 正确的单次爆发数量;
- 固定屏幕尺寸的骨骼点与方向轴;
- 拖尾比例与确定性随机长度、宽度;
- 配置、预设、复制和撤回完整联动;
- 下载前 Spine 4.2 结构校验。
至此,阶段十一“Spine JSON 导出体系与拖尾蒙皮定版”完成落盘。
+342
View File
@@ -0,0 +1,342 @@
# 网页粒子系统 · 阶段四
> 阶段定位:**修改器模块第一阶段定版**
> 完成日期:2026-08-28
> 工程目录:`/Users/tianmokeji/Desktop/SpineParticle`
> 技术栈:Vue 3 + TypeScript + Pinia + PixiJS 7 + Vite
---
## 一、阶段结论
阶段四围绕粒子系统的外部运动影响完成开发,已经实现并接入实时预览的修改器包括:
1. 移动噪声;
2. 重力;
3. 风力;
4. 全局力场;
5. 吸附。
本阶段同时统一了场景初始状态和随机种子语义:打开项目时默认创建一个粒子系统,仅展开“场景对象”模块,其他模块保持折叠;所有修改器默认关闭。随机种子移动到场景对象模块,作为粒子系统全局随机入口,相同种子能够复现随机初始属性、资源选择和修改器采样结果。
阶段四结束后,项目已经具备“基础发射参数 + 外观资源 + 生命周期属性 + 多种运动修改器 + 画布辅助交互”的完整实时编辑链路。
---
## 二、初始状态与全局随机种子
### 2.1 默认场景状态
- 场景中默认创建一个名为 `ParticleSystem1` 的粒子系统;
- “场景对象”模块默认展开;
- 发射、形状、粒子属性、外观资源、修改器和导出等模块默认折叠;
- 移动噪声、重力、风力、力场和吸附默认关闭;
- 原有粒子属性、外观资源和时间轴功能保持不变。
### 2.2 随机种子
随机种子从“粒子属性”移动到“场景对象”,其影响范围扩展为整个粒子系统:
- 初始生命周期、速度、缩放和旋转的随机采样;
- 图片资源的随机选择;
- 重力和风力的粒子级随机强度;
- 移动噪声的粒子相位;
- 曲线吸附的弯曲方向。
包括 `0` 在内的任意整数种子都使用确定性随机序列。重新模拟相同配置时,可以获得一致的粒子结果。
---
## 三、移动噪声
移动噪声用于在不破坏基础物理轨迹的前提下,为粒子增加连续的视觉偏移。
| 参数 | 默认值 | 作用 |
|---|---:|---|
| 强度 | 30 | 控制噪声影响比例,范围 0~100% |
| 波动范围 | 100 | 控制粒子偏离原轨迹的最大范围 |
| 时间流速 | 1 | 控制噪声随时间变化的速度 |
实现规则:
- 噪声只作用于最终渲染位置,粒子的基础 `x/y` 和速度仍由物理系统维护;
- 每个粒子使用由全局种子和粒子池编号推导的独立相位;
- 出生时减去初始噪声采样,保证粒子仍从原发射点出现;
- 使用连续正弦变化避免逐帧随机产生的跳动;
- 反复关闭、开启且参数和种子相同时,轨迹保持可复现。
---
## 四、重力
重力支持固定强度、粒子级随机强度、方向和生命周期曲线。
### 4.1 默认值与方向
| 参数 | 默认值 |
|---|---:|
| 模式 | 固定 |
| 重力强度 | 980 |
| 方向 | -90° |
| 生命周期曲线 | 全程 1 |
方向使用数学角度:
- `0°`:向右;
- `-90°`:向屏幕下方;
- 正强度沿设定方向作用;
- 负强度使作用方向反转。
### 4.2 固定与随机
- 固定模式下,全部粒子使用当前固定强度;
- 随机模式下,每个粒子出生时在 `min~max` 中独立采样;
- 随机结果受全局随机种子控制;
- 已出生粒子的随机强度保持不变。
### 4.3 生命周期内重力
生命周期曲线的横轴为归一化生命时间 `0~1`,纵轴为重力倍率 `0~1`。最终重力为:
```text
最终重力 = 粒子重力强度 × 生命周期曲线倍率
```
曲线支持关键点编辑和恢复默认水平曲线。
---
## 五、风力
风力复用重力的模式结构和生命周期曲线,但使用独立配置。
| 参数 | 默认值 |
|---|---:|
| 模式 | 固定 |
| 风力强度 | 100 |
| 方向 | 0°,水平向右 |
| 生命周期曲线 | 全程 1 |
风力同样支持:
- 固定强度;
- 每个粒子独立的随机强度范围;
- `0°` 向右、`-90°` 向下的数学角度规则;
- 负强度反转方向;
- 生命周期 `0~1` 倍率曲线;
- 全局随机种子复现。
---
## 六、全局力场
力场以场景全局坐标定义,不跟随粒子系统自身的位移、旋转和缩放。
### 6.1 参数
| 参数 | 默认值 | 说明 |
|---|---:|---|
| 中心 X/Y | 0 / 0 | 场景全局数学坐标 |
| 力场强度 | 10 | 正数吸引,负数排斥 |
| 力场半径 | 100 | 力场作用范围 |
| 衰减模式 | 线性衰减 | 边缘强度为 0 |
### 6.2 衰减模式
1. **恒定**:半径范围内力度不变;
2. **线性衰减**:中心最强,接近边缘时线性下降为 0;
3. **距离反比**:距离中心越近力度越强,并设置中心上限以避免数值爆炸。
### 6.3 坐标与物理计算
每帧先将粒子从发射器局部坐标转换为场景全局数学坐标,计算力场加速度,再将加速度转换回粒子系统局部坐标。因此:
- 力场中心始终位于固定的场景位置;
- 粒子系统经过位移、旋转或非等比缩放后,力场方向仍正确;
- 正强度始终把粒子拉向中心;
- 负强度始终把粒子推离中心。
### 6.4 画布交互
- 开启后在画布显示力场作用圆和中心标记;
- 正吸力使用蓝色,负斥力使用红色;
- 按住圆内任意位置即可拖动力场中心;
- 拖动结果实时同步到面板中心 X/Y;
- 圆形辅助线使用全局坐标,不应用粒子系统根变换。
---
## 七、吸附
吸附用于在延迟结束后,把粒子从当时所在位置移动到指定全局点;粒子进入吸附完成半径后立即死亡。
### 7.1 默认参数
| 参数 | 默认值 | 说明 |
|---|---:|---|
| 吸附点 X | 200 | 场景全局数学坐标 |
| 吸附点 Y | 0 | 场景全局数学坐标 |
| 吸附半径 | 20 | 进入范围即视为完成并死亡 |
| 吸附时长 | 0.5 秒 | 从当前点移动到吸附点的时间 |
| 延迟时间 | 0.1 秒 | 延迟结束后开始受吸附影响 |
| 延迟方式 | 单粒子生命 | 每个粒子按出生时间独立计时 |
| 吸附方式 | 曲线 | 使用确定性的二次曲线路径 |
吸附模块默认关闭。
### 7.2 延迟方式
| 模式 | 行为 |
|---|---|
| 统一时间 | 从吸附开关开启时统一计时;延迟结束后,当前及之后的粒子立即具备吸附条件 |
| 单粒子生命 | 每个粒子从出生开始独立计时,达到延迟时间后才开始吸附 |
### 7.3 吸附路径
- **直线**:从吸附开始时的位置匀速插值到吸附点;
- **曲线**:使用二次贝塞尔曲线移动,曲线弯曲方向由全局种子和粒子编号确定;
- 吸附开始时记录粒子的实际当前位置,不会跳回出生点;
- 拖动画布上的吸附点时,正在吸附的粒子会继续朝新的目标位置移动。
### 7.4 死亡规则
粒子在以下任一条件先满足时死亡:
1. 进入吸附半径;
2. 吸附路径进度达到终点;
3. 粒子自身生命周期耗尽。
生命周期判断优先于吸附计算,因此吸附不会延长粒子原本的生命。吸附半径可设为 `0`,此时目标表示一个点,粒子到达中心后死亡。
### 7.5 画布交互
- 开启后在画布显示红色吸附圆和中心十字;
- 半径为 `0` 时显示红色中心点;
- 可按住吸附圆拖动全局位置;
- 半径为 `0` 时仍保留约 12 像素的鼠标命中区域,便于拖动;
- 拖动结果实时同步到面板吸附点 X/Y;
- 吸附圆与力场圆重叠时,红色吸附圆优先接收拖动。
---
## 八、修改器执行顺序
当前单帧主要执行顺序为:
1. 扣除粒子自身生命周期,生命耗尽则立即销毁;
2. 计算重力;
3. 计算风力;
4. 计算全局力场;
5. 应用阻尼;
6. 积分基础位置;
7. 判断吸附延迟并覆盖吸附路径位置;
8. 计算移动噪声的渲染偏移;
9. 更新缩放、旋转、透明度、颜色和混合模式;
10. 生成实时状态或录制帧。
吸附开始后位置由吸附路径控制,并清除原速度,避免外力积累造成终点抖动。移动噪声仍属于渲染偏移,不改变吸附和物理系统维护的基础坐标。
---
## 九、核心实现文件
| 文件 | 本阶段职责 |
|---|---|
| `src/core/particleEmitter.ts` | 修改器配置、默认值、配置补齐、随机采样、重力/风力/力场/吸附/噪声计算 |
| `src/views/ParticlePanel.vue` | 修改器开关、参数控件、模式选择与生命周期曲线 |
| `src/views/Stage.vue` | 全局力场和吸附范围绘制、画布拖动、坐标换算及实时预览 |
| `src/store/particleStore.ts` | 默认粒子系统和编辑器状态的承载 |
为兼容热更新对象和旧配置,`ensureEmitterConfig()` 会自动补齐新增字段。旧版重力、二维风力和力场参数也保留迁移处理。
---
## 十、验证结果
本阶段已完成以下检查:
- 相同种子下移动噪声重复开启可得到一致结果;
- 重力方向 `0°` 向右、`-90°` 向下;
- 重力和风力固定/随机模式能正确切换;
- 生命周期倍率曲线已参与重力和风力计算;
- 力场三种衰减模式可切换且配置实时生效;
- 力场圆画布拖动与面板坐标同步;
- 吸附默认参数、延迟模式、路径模式、死亡优先级均已接入运行逻辑;
- `npm run build` 通过;
- `git diff --check` 通过。
Vite 仍会提示主包体积超过 500 kB,该提示不影响当前功能运行。
---
## 十一、当前边界与已知风险
1. **拖尾尚未实现**:当前只有配置开关,没有轨迹采样和渲染逻辑。
2. **碰撞尚未实现**:界面仍标记为预留,未定义碰撞体和反弹规则。
3. **路径跟随尚未实现**:界面仍标记为预留,尚未建立路径编辑和粒子约束。
4. **修改器组合缺少系统测试**:重力、风力、力场、吸附和噪声同时开启时,需要补充参数极值和稳定性测试。
5. **吸附曲线暂为自动弧线**:当前不能由用户编辑控制点,也没有自定义吸附曲线编辑器。
6. **辅助圆没有独立显示开关**:开启力场或吸附后,选中系统时始终显示对应辅助范围。
7. **工程保存/加载尚未完成**:新增修改器字段目前依赖内存配置和默认值,尚未形成正式版本化工程格式。
8. **缺少自动化单元测试**:当前主要依赖类型检查、生产构建和人工交互验证。
---
## 十二、下一步开发建议
### 优先级 P0:修改器模块收尾
建议按以下顺序继续,避免同时扩散多个高成本功能:
1. 拖尾:确定每粒子采样点上限、宽度/颜色/透明度生命周期和对象池;
2. 碰撞:先支持全局直线、矩形和圆形碰撞体,再扩展反弹、摩擦和死亡;
3. 路径跟随:复用吸附的全局坐标与曲线求值,增加可编辑路径控制点;
4. 增加修改器总开关、辅助线显示开关和单项重置。
### 优先级 P1:工程保存与加载
- 定义 `ProjectSchema` 和 `schemaVersion`;
- 将 Pixi 运行时对象与可序列化参数分离;
- 保存全部修改器开关、模式、曲线和全局坐标;
- 为阶段三、阶段四配置建立明确迁移函数;
- 支持工程 JSON 或包含资源的 ZIP 工程包。
### 优先级 P2:修改器组合测试
- 为全局/局部坐标转换增加单元测试;
- 验证旋转、负缩放、非等比缩放下的力场和吸附;
- 测试极短生命周期、零延迟、零半径和极短吸附时长;
- 测试正负极值、帧率波动和大粒子数量;
- 固化相同种子的轨迹快照,防止随机语义回归。
### 优先级 P3:交互与可视化
- 力场和吸附中心增加悬停、选中和拖动反馈;
- 为圆形辅助线增加半径拖拽手柄;
- 在画布标注半径、强度方向和中心坐标;
- 增加修改器辅助线显隐入口;
- 处理力场、吸附点、根变换 Gizmo 重叠时的明确选中规则。
### 优先级 P4:性能与导出准备
- 避免每帧重复查找资源配置;
- 为多个全局力场或碰撞体预留空间索引;
- 明确修改器结果在固定帧录制中的采样精度;
- 验证吸附死亡对粒子骨骼池和导出帧的影响;
- 对 400 粒子上限进行组合修改器性能压测。
---
## 十三、阶段四验收标准
阶段四可视为完成,当以下条件持续满足:
- 默认场景和模块折叠状态符合约定;
- 所有已实现修改器默认关闭;
- 移动噪声受全局随机种子控制且可复现;
- 重力和风力的固定、随机、方向与生命周期曲线真实生效;
- 力场使用全局坐标并支持吸力、斥力、三种衰减和画布拖动;
- 吸附支持全局点、完成半径、时长、两种延迟方式、两种路径和画布拖动;
- 粒子进入吸附范围或自身生命周期结束时能够正确销毁;
- 阶段三已经完成的粒子属性与外观资源功能没有发生回归;
- TypeScript 类型检查和生产构建保持通过。
+83
View File
@@ -0,0 +1,83 @@
# Spine 粒子编辑器 · 骨骼对象池架构(重构设计 v1)
> 基于我们多轮确认的设计意图整理。**目标**:一个"用参数创建粒子系统"的编辑器,
> 粒子本质是**挂在动态骨骼下的图片**,最终可导出 Spine 骨骼 json。
>
> **与旧实现的根本区别**:不再"加载外部 Spine 骨骼、让粒子跟随某根骨骼",而是反过来——
> **编辑器自建骨架(仅 root 根骨骼),粒子生成骨骼并挂在 root 下**。
---
## 一、核心概念(最终确认)
1. **自建最小骨架**:编辑器打开时,**自建一棵只含一根 `root` 根骨骼的骨架**。
- 不加载任何外部 `.json/.atlas/.png`(禁止 sample.json 作模板、禁止"加载示例骨骼"入口)。
2. **骨骼对象池**:按**粒子发射数量**,在 `root` 下**动态生成对应数量的骨骼**。
- **一个粒子 = 一根骨骼**。
3. **粒子图片挂到骨骼**:每个粒子的图片(贴图光斑)作为 **child 挂载到对应该粒子的骨骼下面**。
- 骨骼决定粒子的 **位置 / 旋转 / 缩放**;粒子图片跟随骨骼运动(这正是 Spine 动画的结构)。
4. **参数驱动一切**:粒子系统的创建与所有参数(发射/数量/形状/方向/速度/生命/缩放/透明度/旋转/颜色/混合/重力等)完全由**编辑器参数面板**决定。
5. **最终产出**:导出的 json 描述这些**在 root 下动态生成、随动画变化的骨骼**(即 Spine 骨骼动画 json)。
---
## 二、数据流(预览阶段)
```
参数面板(store) → 每帧生成/更新 骨骼池
│
粒子数量 N ⇒ 在 root 下创建 N 根骨骼(Bone i)
│
每根骨骼设置 pos/rot/scale = 对应粒子的状态
│
该粒子图片(Sprite) 作为 child 挂到该骨骼 → 骨骼带着图片动
```
- 单根 `root` 骨骼:静止于原点(0,0),承载所有生成的子骨骼。
- 生成的骨骼名:如 `p_0, p_1, ...`(与粒子一一对应),挂在 root 下。
---
## 三、渲染实现要点
- 画布用 Pixi 的 `Spine`(官方 spine-pixi)挂载骨架;但**不渲染 Spines 附件的贴图**(附件我们不用)。
- 为**每根粒子骨骼**添加一个 `Sprite`(粒子贴图)作为其 child(Pixi `Bone` 可通过 `Spine` 组件的 slot/骨骼挂载子对象)。
- 替代方案:不真正用 Spine 渲染骨骼,而是**自己维护一根"逻辑骨骼"+一个 Sprite**——每帧从参数计算粒子状态,直接移动 Sprite,同时记录这根"逻辑骨骼"便于导出。**这是更可控的实现**(避免 Spine 运行时挂载复杂)。
> 我建议用**方案 B(逻辑骨骼 + Sprite)**:预览时用 Sprite 显示粒子,同时维护"每颗粒子对应一根逻辑骨骼"的数据结构(名字/父级=root/位置/旋转/缩放随时间序列),既满足预览又为导出 json 提供干净的数据。避免陷入 Spine 附件的复杂挂载。若你要严格用 Spine 的 Bone 对象,我再切方案 A。
---
## 四、参数面板(保留现有字段,去掉"加载骨骼/跟随骨骼"相关)
保留并完善:
- 系统:名称
- 发射:持续/爆发、**粒子数量/速率**、发射形状(点/圆/矩形/锥体)
- 方向:角度、扩散
- 粒子属性:速度/生命/缩放/透明度/旋转(各 min→max)
- 外观:颜色(起始/结束/寿命渐变)、混合模式、粒子贴图(内置选择/上传)
- 物理:重力
移除/调整:
- ~~跟随骨骼开关~~、~~骨骼下拉~~(不再"跟随某骨骼",而是 root 下生成)
- ~~加载示例骨骼~~ 按钮(不要再有)
---
## 五、导出(后续阶段,先做预览)
- 把每根粒子逻辑骨骼的**位置/旋转/缩放随时间的序列** dump 成 Spine 骨骼时间轴 json。
- 骨架 = 1 个 root + N 根生成的子骨骼。
---
## 六、待你确认的实现方式(仅一点)
预览阶段粒子的"骨骼"用哪种实现:
| 方案 | 说明 | 取舍 |
|---|---|---|
| **B(推荐)** | 自维护"逻辑骨骼"数据 + Sprite 显示粒子;导出时从逻辑骨骼 dump json | 实现简单可控、与导出天然对齐 |
| **A** | 用 spine-pixi 的 `Bone` 对象,粒子 Sprite 真实挂到 Bone 下 | 更贴近 Spine,但挂载/渲染复杂 |
> 我倾向 **B**。请确认选 A 还是 B(或让我默认按 B 开始)。