Files
esengine/packages/particle/src/presets/index.ts
YHH 32d35ef2ee 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 循环变量
2025-12-05 23:03:31 +08:00

775 lines
15 KiB
TypeScript

/**
* 粒子效果预设
* Particle effect presets
*
* Collection of pre-configured particle system settings.
* 预配置的粒子系统设置集合。
*/
import { EmissionShape, type ColorValue } from '../ParticleEmitter';
import { ParticleBlendMode, SimulationSpace } from '../ParticleSystemComponent';
import { ForceFieldType } from '../modules/ForceFieldModule';
import { BoundaryType, CollisionBehavior } from '../modules/CollisionModule';
/**
* 辅助函数:十六进制转 ColorValue
* Helper: hex to ColorValue
*/
function hexToColor(hex: string, alpha = 1): ColorValue {
const h = hex.replace('#', '');
return {
r: parseInt(h.slice(0, 2), 16) / 255,
g: parseInt(h.slice(2, 4), 16) / 255,
b: parseInt(h.slice(4, 6), 16) / 255,
a: alpha,
};
}
/**
* 预设配置接口
* Preset configuration interface
*/
export interface ParticlePreset {
/** 预设名称 | Preset name */
name: string;
/** 预设描述 | Preset description */
description: string;
/** 预设分类 | Preset category */
category: PresetCategory;
/** 预设图标 | Preset icon */
icon?: string;
// 基础属性 | Basic properties
maxParticles: number;
looping: boolean;
duration: number;
playbackSpeed: number;
// 发射属性 | Emission properties
emissionRate: number;
emissionShape: EmissionShape;
shapeRadius: number;
shapeWidth: number;
shapeHeight: number;
shapeAngle: number;
// 粒子属性 | Particle properties
lifetimeMin: number;
lifetimeMax: number;
speedMin: number;
speedMax: number;
direction: number;
directionSpread: number;
scaleMin: number;
scaleMax: number;
gravityX: number;
gravityY: number;
// 颜色属性 | Color properties
startColor: ColorValue;
endColor?: ColorValue;
startAlpha: number;
endAlpha: number;
endScale: number;
// 渲染属性 | Rendering properties
particleSize: number;
blendMode: ParticleBlendMode;
// 可选模块 | Optional modules
simulationSpace?: SimulationSpace;
forceField?: {
type: ForceFieldType;
strength: number;
directionX?: number;
directionY?: number;
centerX?: number;
centerY?: number;
inwardStrength?: number;
frequency?: number;
};
collision?: {
boundaryType: BoundaryType;
behavior: CollisionBehavior;
radius?: number;
bounceFactor?: number;
};
}
/**
* 预设分类
* Preset category
*/
export enum PresetCategory {
/** 自然效果 | Natural effects */
Nature = 'nature',
/** 魔法效果 | Magic effects */
Magic = 'magic',
/** 爆炸效果 | Explosion effects */
Explosion = 'explosion',
/** 环境效果 | Environment effects */
Environment = 'environment',
/** UI 效果 | UI effects */
UI = 'ui',
/** 基础效果 | Basic effects */
Basic = 'basic',
}
/**
* 火焰预设
* Fire preset
*/
export const FirePreset: ParticlePreset = {
name: 'Fire',
description: 'Realistic fire effect with hot colors',
category: PresetCategory.Nature,
icon: 'Flame',
maxParticles: 200,
looping: true,
duration: 5,
playbackSpeed: 1,
emissionRate: 40,
emissionShape: EmissionShape.Rectangle,
shapeRadius: 20,
shapeWidth: 30,
shapeHeight: 5,
shapeAngle: 30,
lifetimeMin: 0.5,
lifetimeMax: 1.2,
speedMin: 80,
speedMax: 150,
direction: 90,
directionSpread: 25,
scaleMin: 0.8,
scaleMax: 1.2,
gravityX: 0,
gravityY: 50,
startColor: hexToColor('#ff6600'),
endColor: hexToColor('#ff0000'),
startAlpha: 1,
endAlpha: 0,
endScale: 0.3,
particleSize: 16,
blendMode: ParticleBlendMode.Additive,
};
/**
* 烟雾预设
* Smoke preset
*/
export const SmokePreset: ParticlePreset = {
name: 'Smoke',
description: 'Soft rising smoke effect',
category: PresetCategory.Nature,
icon: 'Cloud',
maxParticles: 150,
looping: true,
duration: 5,
playbackSpeed: 1,
emissionRate: 15,
emissionShape: EmissionShape.Circle,
shapeRadius: 15,
shapeWidth: 30,
shapeHeight: 30,
shapeAngle: 30,
lifetimeMin: 2,
lifetimeMax: 4,
speedMin: 20,
speedMax: 50,
direction: 90,
directionSpread: 20,
scaleMin: 0.5,
scaleMax: 1,
gravityX: 10,
gravityY: -5,
startColor: hexToColor('#888888'),
endColor: hexToColor('#cccccc'),
startAlpha: 0.6,
endAlpha: 0,
endScale: 2.5,
particleSize: 32,
blendMode: ParticleBlendMode.Normal,
};
/**
* 火花预设
* Sparkle preset
*/
export const SparklePreset: ParticlePreset = {
name: 'Sparkle',
description: 'Twinkling star-like particles',
category: PresetCategory.Magic,
icon: 'Sparkles',
maxParticles: 100,
looping: true,
duration: 5,
playbackSpeed: 1,
emissionRate: 20,
emissionShape: EmissionShape.Circle,
shapeRadius: 40,
shapeWidth: 40,
shapeHeight: 40,
shapeAngle: 360,
lifetimeMin: 0.3,
lifetimeMax: 0.8,
speedMin: 10,
speedMax: 30,
direction: 90,
directionSpread: 360,
scaleMin: 0.5,
scaleMax: 1.5,
gravityX: 0,
gravityY: -20,
startColor: hexToColor('#ffffff'),
startAlpha: 1,
endAlpha: 0,
endScale: 0,
particleSize: 8,
blendMode: ParticleBlendMode.Additive,
};
/**
* 爆炸预设
* Explosion preset
*/
export const ExplosionPreset: ParticlePreset = {
name: 'Explosion',
description: 'Radial burst explosion effect',
category: PresetCategory.Explosion,
icon: 'Zap',
maxParticles: 300,
looping: false,
duration: 1,
playbackSpeed: 1,
emissionRate: 0, // Burst only
emissionShape: EmissionShape.Point,
shapeRadius: 5,
shapeWidth: 10,
shapeHeight: 10,
shapeAngle: 360,
lifetimeMin: 0.3,
lifetimeMax: 0.8,
speedMin: 200,
speedMax: 400,
direction: 0,
directionSpread: 360,
scaleMin: 0.8,
scaleMax: 1.2,
gravityX: 0,
gravityY: 200,
startColor: hexToColor('#ffaa00'),
endColor: hexToColor('#ff4400'),
startAlpha: 1,
endAlpha: 0,
endScale: 0.2,
particleSize: 12,
blendMode: ParticleBlendMode.Additive,
};
/**
* 雨滴预设
* Rain preset
*/
export const RainPreset: ParticlePreset = {
name: 'Rain',
description: 'Falling rain drops',
category: PresetCategory.Environment,
icon: 'CloudRain',
maxParticles: 500,
looping: true,
duration: 10,
playbackSpeed: 1,
emissionRate: 80,
emissionShape: EmissionShape.Line,
shapeRadius: 200,
shapeWidth: 400,
shapeHeight: 10,
shapeAngle: 0,
lifetimeMin: 0.5,
lifetimeMax: 1,
speedMin: 400,
speedMax: 600,
direction: -80,
directionSpread: 5,
scaleMin: 0.8,
scaleMax: 1,
gravityX: 0,
gravityY: 0,
startColor: hexToColor('#88ccff'),
startAlpha: 0.7,
endAlpha: 0.3,
endScale: 1,
particleSize: 6,
blendMode: ParticleBlendMode.Normal,
};
/**
* 雪花预设
* Snow preset
*/
export const SnowPreset: ParticlePreset = {
name: 'Snow',
description: 'Gently falling snowflakes',
category: PresetCategory.Environment,
icon: 'Snowflake',
maxParticles: 300,
looping: true,
duration: 10,
playbackSpeed: 1,
emissionRate: 30,
emissionShape: EmissionShape.Line,
shapeRadius: 200,
shapeWidth: 400,
shapeHeight: 10,
shapeAngle: 0,
lifetimeMin: 3,
lifetimeMax: 6,
speedMin: 20,
speedMax: 50,
direction: -90,
directionSpread: 30,
scaleMin: 0.3,
scaleMax: 1,
gravityX: 0,
gravityY: 0,
startColor: hexToColor('#ffffff'),
startAlpha: 0.9,
endAlpha: 0.5,
endScale: 1,
particleSize: 8,
blendMode: ParticleBlendMode.Normal,
};
/**
* 魔法光环预设
* Magic aura preset
*/
export const MagicAuraPreset: ParticlePreset = {
name: 'Magic Aura',
description: 'Mystical swirling aura',
category: PresetCategory.Magic,
icon: 'Star',
maxParticles: 100,
looping: true,
duration: 5,
playbackSpeed: 1,
emissionRate: 20,
emissionShape: EmissionShape.Circle,
shapeRadius: 50,
shapeWidth: 50,
shapeHeight: 50,
shapeAngle: 360,
lifetimeMin: 1,
lifetimeMax: 2,
speedMin: 5,
speedMax: 15,
direction: 0,
directionSpread: 360,
scaleMin: 0.5,
scaleMax: 1,
gravityX: 0,
gravityY: -30,
startColor: hexToColor('#aa55ff'),
endColor: hexToColor('#5555ff'),
startAlpha: 0.8,
endAlpha: 0,
endScale: 0.5,
particleSize: 10,
blendMode: ParticleBlendMode.Additive,
};
/**
* 灰尘预设
* Dust preset
*/
export const DustPreset: ParticlePreset = {
name: 'Dust',
description: 'Floating dust particles',
category: PresetCategory.Environment,
icon: 'Wind',
maxParticles: 200,
looping: true,
duration: 10,
playbackSpeed: 1,
emissionRate: 15,
emissionShape: EmissionShape.Rectangle,
shapeRadius: 100,
shapeWidth: 200,
shapeHeight: 150,
shapeAngle: 0,
lifetimeMin: 4,
lifetimeMax: 8,
speedMin: 5,
speedMax: 15,
direction: 45,
directionSpread: 90,
scaleMin: 0.2,
scaleMax: 0.6,
gravityX: 10,
gravityY: -2,
startColor: hexToColor('#ccbb99'),
startAlpha: 0.3,
endAlpha: 0.1,
endScale: 1.2,
particleSize: 4,
blendMode: ParticleBlendMode.Normal,
};
/**
* 泡泡预设
* Bubble preset
*/
export const BubblePreset: ParticlePreset = {
name: 'Bubbles',
description: 'Rising soap bubbles',
category: PresetCategory.Environment,
icon: 'CircleDot',
maxParticles: 50,
looping: true,
duration: 10,
playbackSpeed: 1,
emissionRate: 5,
emissionShape: EmissionShape.Rectangle,
shapeRadius: 40,
shapeWidth: 80,
shapeHeight: 20,
shapeAngle: 0,
lifetimeMin: 2,
lifetimeMax: 4,
speedMin: 30,
speedMax: 60,
direction: 90,
directionSpread: 20,
scaleMin: 0.5,
scaleMax: 1.5,
gravityX: 10,
gravityY: -10,
startColor: hexToColor('#aaddff'),
startAlpha: 0.5,
endAlpha: 0.2,
endScale: 1.3,
particleSize: 16,
blendMode: ParticleBlendMode.Normal,
};
/**
* 星轨预设
* Star trail preset
*/
export const StarTrailPreset: ParticlePreset = {
name: 'Star Trail',
description: 'Glowing star trail effect',
category: PresetCategory.Magic,
icon: 'Sparkle',
maxParticles: 150,
looping: true,
duration: 5,
playbackSpeed: 1,
emissionRate: 50,
emissionShape: EmissionShape.Point,
shapeRadius: 2,
shapeWidth: 4,
shapeHeight: 4,
shapeAngle: 0,
lifetimeMin: 0.2,
lifetimeMax: 0.6,
speedMin: 5,
speedMax: 20,
direction: 180,
directionSpread: 30,
scaleMin: 0.8,
scaleMax: 1.2,
gravityX: 0,
gravityY: 0,
startColor: hexToColor('#ffff88'),
endColor: hexToColor('#ffaa44'),
startAlpha: 1,
endAlpha: 0,
endScale: 0.1,
particleSize: 6,
blendMode: ParticleBlendMode.Additive,
};
/**
* 默认预设(简单)
* Default preset (simple)
*/
export const DefaultPreset: ParticlePreset = {
name: 'Default',
description: 'Basic particle emitter',
category: PresetCategory.Basic,
icon: 'Circle',
maxParticles: 100,
looping: true,
duration: 5,
playbackSpeed: 1,
emissionRate: 10,
emissionShape: EmissionShape.Point,
shapeRadius: 0,
shapeWidth: 0,
shapeHeight: 0,
shapeAngle: 0,
lifetimeMin: 1,
lifetimeMax: 2,
speedMin: 50,
speedMax: 100,
direction: 90,
directionSpread: 30,
scaleMin: 1,
scaleMax: 1,
gravityX: 0,
gravityY: 0,
startColor: hexToColor('#ffffff'),
startAlpha: 1,
endAlpha: 0,
endScale: 1,
particleSize: 8,
blendMode: ParticleBlendMode.Normal,
};
/**
* 漩涡预设
* Vortex preset
*/
export const VortexPreset: ParticlePreset = {
name: 'Vortex',
description: 'Swirling vortex with inward pull',
category: PresetCategory.Magic,
icon: 'Tornado',
maxParticles: 200,
looping: true,
duration: 10,
playbackSpeed: 1,
emissionRate: 30,
emissionShape: EmissionShape.Circle,
shapeRadius: 80,
shapeWidth: 160,
shapeHeight: 160,
shapeAngle: 360,
lifetimeMin: 2,
lifetimeMax: 4,
speedMin: 10,
speedMax: 30,
direction: 0,
directionSpread: 360,
scaleMin: 0.5,
scaleMax: 1,
gravityX: 0,
gravityY: 0,
startColor: hexToColor('#88ccff'),
startAlpha: 0.8,
endAlpha: 0,
endScale: 0.3,
particleSize: 8,
blendMode: ParticleBlendMode.Additive,
forceField: {
type: ForceFieldType.Vortex,
strength: 150,
centerX: 0,
centerY: 0,
inwardStrength: 30,
},
};
/**
* 落叶预设
* Falling leaves preset
*/
export const LeavesPreset: ParticlePreset = {
name: 'Leaves',
description: 'Falling leaves with wind effect',
category: PresetCategory.Environment,
icon: 'Leaf',
maxParticles: 100,
looping: true,
duration: 10,
playbackSpeed: 1,
emissionRate: 8,
emissionShape: EmissionShape.Line,
shapeRadius: 200,
shapeWidth: 400,
shapeHeight: 10,
shapeAngle: 0,
lifetimeMin: 4,
lifetimeMax: 8,
speedMin: 30,
speedMax: 60,
direction: -90,
directionSpread: 20,
scaleMin: 0.6,
scaleMax: 1.2,
gravityX: 0,
gravityY: 20,
startColor: hexToColor('#dd8844'),
startAlpha: 0.9,
endAlpha: 0.6,
endScale: 1,
particleSize: 12,
blendMode: ParticleBlendMode.Normal,
forceField: {
type: ForceFieldType.Turbulence,
strength: 40,
frequency: 0.5,
},
};
/**
* 弹球预设
* Bouncing balls preset
*/
export const BouncingPreset: ParticlePreset = {
name: 'Bouncing',
description: 'Bouncing particles in a box',
category: PresetCategory.Basic,
icon: 'Circle',
maxParticles: 50,
looping: true,
duration: 10,
playbackSpeed: 1,
emissionRate: 5,
emissionShape: EmissionShape.Point,
shapeRadius: 0,
shapeWidth: 0,
shapeHeight: 0,
shapeAngle: 0,
lifetimeMin: 8,
lifetimeMax: 12,
speedMin: 100,
speedMax: 200,
direction: 90,
directionSpread: 60,
scaleMin: 0.8,
scaleMax: 1.2,
gravityX: 0,
gravityY: 200,
startColor: hexToColor('#66aaff'),
startAlpha: 1,
endAlpha: 0.8,
endScale: 1,
particleSize: 16,
blendMode: ParticleBlendMode.Normal,
collision: {
boundaryType: BoundaryType.Rectangle,
behavior: CollisionBehavior.Bounce,
bounceFactor: 0.8,
},
};
/**
* 所有预设
* All presets
*/
export const AllPresets: ParticlePreset[] = [
DefaultPreset,
FirePreset,
SmokePreset,
SparklePreset,
ExplosionPreset,
RainPreset,
SnowPreset,
MagicAuraPreset,
DustPreset,
BubblePreset,
StarTrailPreset,
VortexPreset,
LeavesPreset,
BouncingPreset,
];
/**
* 按分类获取预设
* Get presets by category
*/
export function getPresetsByCategory(category: PresetCategory): ParticlePreset[] {
return AllPresets.filter(p => p.category === category);
}
/**
* 获取预设名称列表
* Get preset name list
*/
export function getPresetNames(): string[] {
return AllPresets.map(p => p.name);
}
/**
* 按名称获取预设
* Get preset by name
*/
export function getPresetByName(name: string): ParticlePreset | undefined {
return AllPresets.find(p => p.name === name);
}