feat(particle): 添加完整粒子系统和粒子编辑器 (#284)

* feat(editor-core): 添加用户系统自动注册功能

- IUserCodeService 新增 registerSystems/unregisterSystems/getRegisteredSystems 方法
- UserCodeService 实现系统检测、实例化和场景注册逻辑
- ServiceRegistry 在预览开始时注册用户系统,停止时移除
- 热更新时自动重新加载用户系统
- 更新 System 脚本模板添加 @ECSSystem 装饰器

* feat(editor-core): 添加编辑器脚本支持(Inspector/Gizmo)

- registerEditorExtensions 实际注册用户 Inspector 和 Gizmo
- 添加 unregisterEditorExtensions 方法
- ServiceRegistry 在项目加载时编译并加载编辑器脚本
- 项目关闭时自动清理编辑器扩展
- 添加 Inspector 和 Gizmo 脚本创建模板

* feat(particle): 添加粒子系统和粒子编辑器

新增两个包:
- @esengine/particle: 粒子系统核心库
- @esengine/particle-editor: 粒子编辑器 UI

粒子系统功能:
- ECS 组件架构,支持播放/暂停/重置控制
- 7种发射形状:点、圆、环、矩形、边缘、线、锥形
- 5个动画模块:颜色渐变、缩放曲线、速度控制、旋转、噪声
- 纹理动画模块支持精灵表动画
- 3种混合模式:Normal、Additive、Multiply
- 11个内置预设:火焰、烟雾、爆炸、雨、雪等
- 对象池优化,支持粒子复用

编辑器功能:
- 实时 Canvas 预览,支持全屏和鼠标跟随
- 点击触发爆发效果(用于测试爆炸类特效)
- 渐变编辑器:可视化颜色关键帧编辑
- 曲线编辑器:支持缩放曲线和缓动函数
- 预设浏览器:快速应用内置预设
- 模块开关:独立启用/禁用各个模块
- Vector2 样式输入(重力 X/Y)

* feat(particle): 完善粒子系统核心功能

1. Burst 定时爆发系统
   - BurstConfig 接口支持时间、数量、循环次数、间隔
   - 运行时自动处理定时爆发
   - 支持无限循环爆发

2. 速度曲线模块 (VelocityOverLifetimeModule)
   - 6种曲线类型:Constant、Linear、EaseIn、EaseOut、EaseInOut、Custom
   - 自定义关键帧曲线支持
   - 附加速度 X/Y
   - 轨道速度和径向速度

3. 碰撞边界模块 (CollisionModule)
   - 矩形和圆形边界类型
   - 3种碰撞行为:Kill、Bounce、Wrap
   - 反弹系数和最小速度阈值
   - 反弹时生命损失

* feat(particle): 添加力场模块、碰撞模块和世界/本地空间支持

- 新增 ForceFieldModule 支持风力、吸引点、漩涡、湍流四种力场类型
- 新增 SimulationSpace 枚举支持世界空间和本地空间切换
- ParticleSystemComponent 集成力场模块和空间模式
- 粒子编辑器添加 Collision 和 ForceField 模块的 UI 编辑支持
- 新增 Vortex、Leaves、Bouncing 三个预设展示新功能
- 编辑器预览实现完整的碰撞和力场效果

* fix(particle): 移除未使用的 transform 循环变量
This commit is contained in:
YHH
2025-12-05 23:03:31 +08:00
committed by GitHub
parent 690d7859c8
commit 32d35ef2ee
43 changed files with 9704 additions and 0 deletions

View File

@@ -0,0 +1,211 @@
/**
* 粒子效果资源加载器
* Particle effect asset loader
*/
import type {
IAssetLoader,
IAssetContent,
IAssetParseContext,
AssetContentType
} from '@esengine/asset-system';
import { EmissionShape, type ColorValue } from '../ParticleEmitter';
import { ParticleBlendMode } from '../ParticleSystemComponent';
/**
* 粒子资产类型常量
* Particle asset type constant
*/
export const ParticleAssetType = 'particle';
/**
* 粒子模块配置
* Particle module configuration
*/
export interface IParticleModuleConfig {
/** 模块类型 | Module type */
type: string;
/** 是否启用 | Enabled */
enabled: boolean;
/** 模块参数 | Module parameters */
params: Record<string, unknown>;
}
/**
* 粒子效果资源数据接口
* Particle effect asset data interface
*/
export interface IParticleAsset {
/** 资源版本 | Asset version */
version: number;
/** 效果名称 | Effect name */
name: string;
/** 效果描述 | Effect description */
description?: string;
// 基础属性 | Basic properties
/** 最大粒子数 | Maximum particles */
maxParticles: number;
/** 是否循环 | Looping */
looping: boolean;
/** 持续时间 | Duration in seconds */
duration: number;
/** 播放速度 | Playback speed */
playbackSpeed: number;
/** 启动时自动播放 | Auto play on start */
playOnAwake: boolean;
// 发射属性 | Emission properties
/** 发射速率 | Emission rate */
emissionRate: number;
/** 发射形状 | Emission shape */
emissionShape: EmissionShape;
/** 形状半径 | Shape radius */
shapeRadius: number;
/** 形状宽度 | Shape width */
shapeWidth: number;
/** 形状高度 | Shape height */
shapeHeight: number;
/** 圆锥角度 | Cone angle */
shapeAngle: number;
// 粒子属性 | Particle properties
/** 生命时间最小值 | Lifetime min */
lifetimeMin: number;
/** 生命时间最大值 | Lifetime max */
lifetimeMax: number;
/** 速度最小值 | Speed min */
speedMin: number;
/** 速度最大值 | Speed max */
speedMax: number;
/** 发射方向(度数)| Direction in degrees */
direction: number;
/** 方向扩散(度数)| Direction spread in degrees */
directionSpread: number;
/** 缩放最小值 | Scale min */
scaleMin: number;
/** 缩放最大值 | Scale max */
scaleMax: number;
/** 重力 X | Gravity X */
gravityX: number;
/** 重力 Y | Gravity Y */
gravityY: number;
// 颜色属性 | Color properties
/** 起始颜色 | Start color */
startColor: ColorValue;
/** 起始透明度 | Start alpha */
startAlpha: number;
/** 结束透明度 | End alpha */
endAlpha: number;
/** 结束缩放 | End scale */
endScale: number;
// 渲染属性 | Rendering properties
/** 粒子大小 | Particle size */
particleSize: number;
/** 混合模式 | Blend mode */
blendMode: ParticleBlendMode;
/** 排序顺序 | Sorting order */
sortingOrder: number;
/** 纹理路径 | Texture path */
texture?: string;
// 模块配置 | Module configurations
/** 模块列表 | Module list */
modules?: IParticleModuleConfig[];
// 纹理动画(可选)| Texture animation (optional)
/** 纹理图集列数 | Texture sheet columns */
textureTilesX?: number;
/** 纹理图集行数 | Texture sheet rows */
textureTilesY?: number;
/** 动画帧率 | Animation frame rate */
textureAnimationFPS?: number;
}
/**
* 创建默认粒子资源数据
* Create default particle asset data
*/
export function createDefaultParticleAsset(name: string = 'New Particle'): IParticleAsset {
return {
version: 1,
name,
description: '',
maxParticles: 100,
looping: true,
duration: 5,
playbackSpeed: 1,
playOnAwake: true,
emissionRate: 10,
emissionShape: EmissionShape.Point,
shapeRadius: 0,
shapeWidth: 0,
shapeHeight: 0,
shapeAngle: 30,
lifetimeMin: 1,
lifetimeMax: 2,
speedMin: 50,
speedMax: 100,
direction: 90,
directionSpread: 30,
scaleMin: 1,
scaleMax: 1,
gravityX: 0,
gravityY: 0,
startColor: { r: 1, g: 1, b: 1, a: 1 },
startAlpha: 1,
endAlpha: 0,
endScale: 1,
particleSize: 8,
blendMode: ParticleBlendMode.Normal,
sortingOrder: 0,
modules: [],
};
}
/**
* 粒子效果加载器实现
* Particle effect loader implementation
*/
export class ParticleLoader implements IAssetLoader<IParticleAsset> {
readonly supportedType = ParticleAssetType;
readonly supportedExtensions = ['.particle', '.particle.json'];
readonly contentType: AssetContentType = 'text';
/**
* 从文本内容解析粒子资源
* Parse particle asset from text content
*/
async parse(content: IAssetContent, _context: IAssetParseContext): Promise<IParticleAsset> {
if (!content.text) {
throw new Error('Particle content is empty');
}
const jsonData = JSON.parse(content.text) as IParticleAsset;
// 验证必要字段 | Validate required fields
if (jsonData.maxParticles === undefined) {
throw new Error('Invalid particle format: missing maxParticles');
}
// 填充默认值 | Fill default values
const defaults = createDefaultParticleAsset();
return { ...defaults, ...jsonData };
}
/**
* 释放已加载的资源
* Dispose loaded asset
*/
dispose(asset: IParticleAsset): void {
(asset as any).modules = null;
}
}

View File

@@ -0,0 +1,7 @@
export {
ParticleLoader,
ParticleAssetType,
createDefaultParticleAsset,
type IParticleAsset,
type IParticleModuleConfig
} from './ParticleLoader';