载入中...
搜索中...
未找到
粒子模块

粒子模块

脚本入口: eve.Particles()

用代码或 JSON 创建发射器,配置运动、颜色、寿命并进行更新和渲染。

基本用法

local particles = eve.Particles();
local fire = particles.newEmitterFromFile("particles/fire.json");
fire.setPosition(320, 420);
fire.start();
particles.update(dt);
particles.render(gfx);

配置热重载观察

setAutoReload(true)(默认开启)后,模块会在 pollConfigs() 或模块更新阶段 观察绑定配置文件的修改时间。getConfigReloadObservation() 返回最近一次明确观察 到的状态字符串:"unbound"(没有绑定配置文件)、 "mtime_polling_unchanged"(文件未改变)、"mtime_polling_reloaded"(已重新加载)、 "mtime_unavailable"(无法取得文件修改时间)或 "mtime_polling_reload_failed"(检测到变化但重新加载失败)。该接口只报告观察结果, 不会代替 reloadConfig();读取失败时发射器仍保留原配置,调用方可据此记录诊断或重试。

版本化多发射器特效资产

复杂特效应保存为 eve.particle-effect 资产,而不是让玩法脚本逐个拼装发射器。当前接受 schema v1 与 **v2**(更高版本失败关闭)。支持版本上的未知字段会被忽略;重名层、无效配置或坏路由/时间线引用会拒绝实例化。

  • **v1**:命名层、嵌入式 emitter 或外部 config、局部偏移/旋转、禁用层、参数默认值与逐层覆盖。
  • **v2**:在 v1 之上增加 eventRoutes(编译为子发射器)与 timeline(duration / looping / cues)。v1 资产也可选携带这两块字段。

参考特效包见 examples/particle-effects/(fire / smoke / impact / trail / weather)。

{
"type": "eve.particle-effect",
"version": 2,
"parameters": { "intensity": 1.0 },
"eventRoutes": [
{ "from": "core", "on": "death", "to": "smoke", "inheritVelocity": 0.2 }
],
"timeline": {
"duration": 1.2,
"looping": false,
"cues": [
{ "time": 0.0, "action": "emit", "emitter": "core", "count": 24 },
{ "time": 0.2, "action": "setParameter", "parameter": "intensity", "value": 1.5 }
]
},
"emitters": [
{
"name": "core",
"offset": [0, -6],
"emitter": {
"buffer": 128,
"preset": "fire",
"parameterBindings": [
{ "parameter": "intensity", "target": "size", "scale": 1.0 }
]
}
},
{ "name": "smoke", "config": "emitters/smoke.json" }
]
}

时间线 action:start / stop / pause / reset / emit / setParameter。Particles.update / advance 会推进已 start 且带时间线的特效时钟。

local impact = particles.newEffectFromFile("effects/impact.effect.json");
if (impact == null) {
print(particles.getLastEffectError() + "\n");
} else {
impact.setPosition(480, 320);
impact.setRotation(0.5);
impact.setFloatParameter("intensity", 1.4);
impact.start();
local sparks = impact.getEmitterByName("sparks"); // 仍可精调单层
}

newEffectFromText() 供编辑器预览未落盘的 JSON。组对象提供 setPosition、setRotation、setScale、setLayer、setVisible、start、pause、stop、reset、命名层 emit、updateTimeline / getTimelineSeconds / isTimelinePlaying,以及事务式 reloadFromFile(解析失败时保留旧实例与世界变换)。组销毁时会一起回收所拥有的发射器。短命玩法特效可用 ParticleEmitterPool.acquire/recycle;idleCount() 返回池中空闲发射器数量,clear() 释放全部空闲实例。

SDF 碰撞与运动向量策略

IParticleSdfField(common/ParticleSdf.h)是借用的 2D 有符号距离接口;setSdfField + setCollision("bounce"|"kill"|"stop", …) 在 CPU 路径采样。GPU 常驻回退原因为 sdf。

setMotionVectorPolicy("none"|"velocity"|"spawn_delta") 记录资产策略,getMotionVectorPolicy() 读回规范化后的策略名;在图形后端真正写入速度缓冲前,isMotionVectorActive() 恒为 false。

绑定到动态骨骼

粒子仍是 2D。支持多种运行时骨骼源;每帧 particles.update 会自动 syncAttach。

源 API 说明
3D AnimPose attachToBone / attachToBoneByName 骨骼世界坐标经 plane(xy/xz/yz)与 scale 投影
2D Spine attachToSpineBone / attachToSpineBoneByName 像素空间;scale 仍生效,plane 忽略
IK Skeleton2D attachToSkeleton2D FABRIK 链骨位置(像素/世界单位 × scale)
IK Skeleton3D attachToSkeleton3D 3D 骨位置经 plane+scale 投影
local anim = eve.Animation();
// … player/sm 驱动 pose,并 pose.computeWorld(sk) …
local spark = particles.newEmitter(128);
spark.applyPreset("spark");
spark.setAttachScale(100); // 米 → 像素
spark.setAttachPlane("xy");
spark.setAttachOffset(0, 0.05, 0);
spark.setFollowBoneRotation(true);
spark.attachToBoneByName(pose, sk, "LeftHand");
spark.start();

Spine(2D)示例:

local spine = anim.newSpineSkeleton(data);
spine.updateWorldTransform();
local dust = particles.newEmitter(64);
dust.attachToSpineBoneByName(spine, "hand");
dust.start();

IK 2D/3D 示例:

local ik = eve.IK();
local sk2 = ik.newSkeleton2D();
// … createBone / solve …
local tipFx = particles.newEmitter(32);
tipFx.attachToSkeleton2D(sk2, tipBoneId);
tipFx.setFollowBoneRotation(true);

detach() 解除绑定;getAttachKind() 返回 "anim" / "spine" / "ik2d" / "ik3d" / "none";也可手动 syncAttach()。

蒙皮表面发射(人物皮肤粒子)

从 CPU 蒙皮后的顶点采样发射位置,适合身体表面的火花、灰尘、能量层:

local skin = anim.newSkinFromModel(model, meshIndex, sk);
pose.computeWorld(sk);
local aura = particles.newEmitter(512);
aura.applyPreset("smoke");
aura.setSkinScale(100);
aura.setSkinPlane("xy");
aura.setSkinSource(skin, pose);
// 可选:只从某根骨附近的顶点发射
aura.setSkinBoneFilterByName(sk, "Spine", 0.2);
aura.start();
// 或瞬时爆发:
aura.emitFromSkin(80);

脚本侧也可主动刷新蒙皮缓存:skin.updateSkinnedPositions(pose),再用 getSkinnedPositionX/Y/Z(i) 读取。

外观增强:混合模式、序列帧与生命周期曲线

发射器支持加法混合、精灵表序列帧动画、多段颜色渐变与大小/旋转曲线,均可通过 JSON 或脚本配置。

// 加法混合 + 序列帧(4x2 精灵表,12 帧/秒,随机起始帧 0~25%)
local burst = particles.newEmitter(256);
burst.setBlendMode("additive");
burst.setFlipbook(4, 2, 12, 0.25);
burst.setTexture(tex); // 精灵表贴图
burst.start();
// 多段颜色渐变 + 大小曲线 + 初始随机旋转
burst.clearColorGradient();
burst.addColorStop(0.0, 1.0, 0.9, 0.4, 1.0); // 白热
burst.addColorStop(0.5, 1.0, 0.3, 0.0, 0.8); // 橙红
burst.addColorStop(1.0, 0.1, 0.0, 0.0, 0.0); // 熄灭
burst.clearSizeCurve();
burst.addSizeCurvePoint(0.0, 0.3);
burst.addSizeCurvePoint(0.7, 1.0);
burst.addSizeCurvePoint(1.0, 0.1);
burst.setStartRotation(-30, 30); // 度

JSON 等价配置:

{
"blendMode": "additive",
"flipbook": { "hframes": 4, "vframes": 2, "frameRate": 12, "frameRandomStart": 0.25 },
"startRotation": [-30, 30],
"colorOverLifetime": [
{ "t": 0.0, "r": 1.0, "g": 0.9, "b": 0.4, "a": 1.0 },
{ "t": 0.5, "r": 1.0, "g": 0.3, "b": 0.0, "a": 0.8 },
{ "t": 1.0, "r": 0.1, "g": 0.0, "b": 0.0, "a": 0.0 }
],
"sizeOverLifetime": [[0.0, 0.3], [0.7, 1.0], [1.0, 0.1]],
"rotationOverLifetime": [[0.0, 0.0], [1.0, 120.0]]
}

说明:

  • blendMode:"alpha"(默认)/ "additive" / "opaque"。加法混合适合火焰、火花、魔法等自发光效果。
  • flipbook:hframes/vframes 为精灵表行列数;frameRate 为每秒帧数(0 = 静止第一帧);frameRandomStart(0~1)随机化起始帧。
  • colorOverLifetime / sizeOverLifetime / rotationOverLifetime 为按归一化寿命采样的多段渐变/曲线;不配置时回退到 colorStart/colorEnd 与 sizes 的两端线性插值。rotationOverLifetime 单位为度,叠加在初始旋转与自旋之上。

发射控制与受力增强

发射器支持定时爆发、预热、重力/阻尼/限速、速度曲线、继承速度与本地/世界仿真空间:

// 定时爆发 + 预热(一次性爆炸,开场即满)
local boom = particles.newEmitter(256);
boom.addBurst(0.0, 60); // 第 0 秒爆发 60 个
boom.addBurst(0.15, 30); // 第 0.15 秒再爆 30 个
boom.setEmitterLifetime(0.3); // 单次效果
boom.setPrewarm(0.5); // start() 时预模拟 0.5 秒
boom.start();
// 重力 + 阻尼 + 限速 + 继承发射器移动速度(角色跑动扬尘)
local dust = particles.newEmitter(128);
dust.setGravity(0, 40);
dust.setDamping(0.8);
dust.setLimitVelocity(120);
dust.setInheritVelocity(0.6);
dust.setSimulationSpace("local"); // 粒子跟随发射器(本地空间)
dust.start();

JSON 等价配置:

{
"bursts": [ { "time": 0.0, "count": 60 }, { "time": 0.15, "count": 30 } ],
"prewarm": 0.5,
"gravity": [0, 40],
"damping": 0.8,
"limitVelocity": 120,
"velocityOverLifetime": [[0.0, 1.0], [1.0, 0.2]],
"inheritVelocity": 0.6,
"simulationSpace": "local",
"noise": { "strength": 8, "frequency": 0.05, "speed": 1.5 },
"maxDeltaTime": 0.1
}

说明:

  • bursts:按发射器运行时间触发的爆发(数组元素为 {time, count} 或 [time, count]),每个爆发只触发一次。
  • prewarm:start() 时按 1/60 步长预模拟,让循环效果在首帧就是满的。
  • gravity 每步作为加速度叠加;damping 为每秒速度衰减比例(0~1);limitVelocity 为速度上限(0 = 不限);velocityOverLifetime 为速度倍率曲线。
  • inheritVelocity(0~1)把发射器当前移动速度按比例附加到新粒子;simulationSpace: "local" 时粒子随发射器平移(默认 "world")。
  • noise 为 CPU 值噪声湍流(强度/频率/时间流速),适合烟雾、火焰的自然扰动。
  • maxDeltaTime 限制单帧模拟步长,防止卡顿后的粒子爆炸。

可复现播放、固定步进与按距离发射

需要录制回放、网络同步、慢动作或稳定拖尾时,可显式控制粒子时间线:

local trail = particles.newEmitter(512);
trail.setRandomSeed(20260826); // 每次 start() 重放相同随机序列
trail.setAutoRandomSeed(false);
trail.setEmitterLifetime(1.5);
trail.setLooping(true); // 有限时间线循环,burst 也会重新触发
trail.setPlaybackSpeed(0.5); // 半速播放;0 = 冻结
trail.setFixedTimeStep(1.0 / 120.0, 8); // 固定步长 + 每帧最多追 8 步
trail.setEmissionRateOverDistance(0.25); // 每移动 4 个世界单位发射 1 个
trail.start();

JSON 等价配置:

{
"randomSeed": 20260826,
"autoRandomSeed": false,
"emitterLife": 1.5,
"looping": true,
"playbackSpeed": 0.5,
"fixedTimeStep": 0.008333333,
"maxSubSteps": 8,
"emissionRateOverDistance": 0.25
}
  • 固定种子会让寿命、速度、方向、大小、旋转、发射形状和序列帧起点等随机采样可复现;autoRandomSeed: true(默认)则每次 start() 生成新序列。
  • 固定步进由 maxSubSteps 限制追帧工作量,超过上限的时间债务会被丢弃,避免暂停恢复后形成长时间尖峰。
  • 按距离发射会沿上一位置到当前位置的线段均匀插值,世界空间拖尾不会因帧率变化形成粒子团。第一次更新只建立运动基线,不会补发创建前的路径。

质量等级、预算、剔除与性能统计

大规模战斗或天气效果应通过运行时预算稳定降级,而不是等粒子缓冲溢出:

particles.setQualityLevel(2); // 0=最低,3=最高
particles.setBudget(50000, 256); // 全局存活粒子软上限、每帧模拟发射器上限;0=不限
local heroFx = particles.newEmitter(2048);
heroFx.setPriority(100); // 高优先级先获得模拟/生成预算
heroFx.setMinimumQuality(0); // 所有质量等级都保留
heroFx.setCullingMode("always"); // 即使离屏也继续模拟
heroFx.setMaxSpawnPerFrame(256);
local ambience = particles.newEmitter(8192);
ambience.setPriority(-10);
ambience.setMinimumQuality(2);
ambience.setCullingMode("pause"); // 离屏或超出距离时冻结
ambience.setCullDistance(1800);
ambience.setMaxSpawnPerFrame(96);

JSON 发射器策略:

{
"priority": -10,
"minimumQuality": 2,
"cullingMode": "pause",
"cullDistance": 1800,
"maxSpawnPerFrame": 96
}
  • 发射器按 priority 从高到低分配每帧模拟器数量和剩余粒子空间;总粒子数是软上限,调低预算不会立即删除已经存活的粒子,但会停止超额生成,让它们自然死亡。
  • cullingMode:"automatic"(默认,仅跳过离屏且为空的发射器)、"pause"(离屏时冻结全部模拟)、"always"(关键玩法效果始终模拟)。渲染阶段仍会跳过确定不可见的粒子。
  • minimumQuality 高于当前全局质量等级时,发射器冻结且不渲染;恢复质量后继续运行,不破坏已有粒子状态。
  • 每次 update/render 后可读取 getLastSimulatedEmitters()、getLastCulledEmitters()、getLastBudgetSkippedEmitters()、getLastParticleCount()、getLastSpawnedParticles()、getLastDroppedSpawns()、getLastRenderedParticles()、getLastSimulationMs() 与 getLastRenderMs(),用于 HUD、自动伸缩和性能回归。

碰撞与子发射器

// 世界边界反弹(雨滴落地溅射)
local rain = particles.newEmitter(512);
rain.setCollision("bounce", 2, 0.4, 0.1); // 半径 2、弹性 0.4、每次碰撞损失 10% 寿命
rain.setCollisionBounds(true, 0, 0, 960, 640);
rain.setWorldCollision(true); // 可选:查询引擎级碰撞解析器(tilemap/Box2D)
// 子发射器:出生时喷火花,死亡时冒烟
local sparks = particles.newEmitter(128);
local smoke = particles.newEmitter(128);
sparks.addSubEmitter(smoke, "death", 0.3); // 继承 30% 速度

JSON:collision: {mode, radius, restitution, lifetimeLoss}、collisionBounds: {enabled, minX, minY, maxX, maxY}、worldCollision: true。子发射器目前通过脚本 addSubEmitter(target, trigger, inherit) 关联(trigger 为 "birth" / "death" / "collision")。

精灵朝向统一由 setRenderMode 控制:"billboard" 使用粒子自身旋转,"axis" 配合 setRenderAxis(degrees) 固定到屏幕空间轴,"stretched"(也接受 "velocity")按速度方向拉伸。JSON 对应 renderMode、renderAxis 和 stretch。

透明粒子可用 setSortMode("none" | "oldest" | "youngest" | "distance") 选择稳定的逐发射器提交顺序,getSortMode() 返回规范化后的策略。distance 在有相机时按远到近排列。CPU 路径按索引重排后提交;GPU 常驻路径在压缩后的 SSBO 上做索引 bitonic 排序,绘制时通过 sorted-index 间接访问,不移动粒子缓冲本身。distance 在无相机时退化为无序绘制。

连续拖尾使用 setRibbon(width, minSegmentLength),它按稳定的粒子出生顺序连接相邻控制点,跳过过短段,并沿段方向生成带宽度的纹理四边形;JSON 为 ribbon: {width, minSegmentLength}。CPU 路径按存活数组邻接连接;GPU 常驻路径用 spawn birthSerial 做索引排序后再生成段,适合配合 setEmissionRateOverDistance 制作弹道、刀光和移动轨迹。

setSoftParticles(true, depth, fadeDistance) 让常驻粒子采样当前 G-buffer 的线性场景深度,在与几何相交或被遮挡时平滑衰减 alpha;JSON 为 softParticles: {enabled, depth, fadeDistance},深度值均为 [0,1] 线性深度。isSoftParticlesActive() 只有在 GPU 常驻渲染器已激活且当前帧确实产生场景深度时才返回 true;纯 2D 帧不会伪造深度,而是保持普通粒子外观。

粒子材质默认是 unlit,不受场景灯光影响。setMaterialMode("lit") 会让粒子接收现有 2D 环境光和点光/方向光;配合 setNormalTexture(normalTex) 时使用切线空间法线贴图,否则使用无贴图的逐粒子中心光照。JSON 可写为:

{
"material": {
"mode": "lit",
"normalTexture": "particles/smoke-normal.png"
}
}

Lit/法线贴图目前明确使用 CPU 粒子模拟加 GPU 2D lit 绘制;即使请求了 gpuSimulation,也会安全回退,不会静默忽略灯光。切回 unlit 后可重新满足 GPU 常驻条件。

setMaterialMode("distortion") 把粒子纹理解释为屏幕空间位移场:R/G 的 0.5 表示零偏移,0/1 表示负/正方向,A 控制羽化覆盖;setDistortionStrength(pixels)(getDistortionStrength() 可读回)设置最大折射像素数。它采样同一帧已解析的 3D 场景颜色,因此纯 2D 帧或离屏 Canvas 不会伪造背景。JSON 示例:material: {mode: "distortion", distortionStrength: 12}。Distortion 当前使用 CPU 粒子模拟和专用 GPU 合成管线。

运行时诊断可读取 getSimulationBackend()("cpu" / "gpu")和 getGpuFallbackReason()。后者在 GPU 已激活时为空;可返回 disabled、backend_unavailable、pending_activation,或具体功能原因:canvas、custom_shader、collision、sdf、force_fields、sub_emitters、particle_lights、curves、lit_material、distortion_material。isGpuFeatureSetSupported() 只检查当前功能组合,不把机器是否支持 Vulkan resident 后端混在一起。

玩法和效果资产可通过命名浮点参数实时驱动 emitter,无需重建或覆盖基础配置:

fire.setFloatParameter("intensity", 1.5);
fire.bindFloatParameter("intensity", "emission", 1.0, 0.0);
fire.bindFloatParameter("intensity", "speed", 0.5, 0.25);
fire.bindFloatParameter("intensity", "size", 0.35, 0.65);

支持的 target 是 emission、speed、size、playback,解析公式为 max(0, value * scale + offset);参数缺失或未绑定时倍率为 1。JSON 使用 parameters 对象和 parameterBindings 数组。这些值在 CPU 与 GPU resident 的生成输入上保持一致,适合武器充能、天气强度、角色状态与关卡脚本驱动。

健壮性:overflowMode("drop" 默认 / "pause" 暂停发射直到有空位 / "warn" 日志提示);setMaxDeltaTime 限制单帧步长;带相机的发射器在屏幕外且无存活粒子时会跳过模拟。

力场、自定义 Shader 与粒子灯光

// 径向力场:黑洞(吸引)/ 斥力场;strength > 0 吸引,< 0 排斥
local vortex = particles.newEmitter(512);
vortex.addForceField(400, 300, 180, 90, 1.5); // 中心 (400,300),半径 180,强度 90,衰减指数 1.5
// 自定义粒子 Shader(仅作用于带贴图的粒子;需要先创建 Shader)
local glow = particles.newEmitter(256);
glow.setShader(myShader);
// 粒子灯光:跟随存活粒子的 2D 点光源(每个发射器最多 8 盏,引擎单画布每帧最多 8 盏)
local torch = particles.newEmitter(128);
torch.setLights(true, 120, 1.2, 1.0, 0.6, 0.2, 4);

JSON:forceFields: [{x, y, radius, strength, falloff}];lights: {enabled, max, radius, intensity, color: [r,g,b]}。粒子灯光由 ParticleLightSystem(随 particles.update 自动调用)维护一盏 Light2D 池,位置与最前面的存活粒子同步。

GPU 加速模拟

setGpuSimulation(true) 或 JSON "gpuSimulation": true 会请求常驻 GPU 后端。首次可提交帧把当前新生粒子上传到后端拥有的多帧 SSBO;之后更新、死亡剔除、存活压缩和 VkDrawIndirectCommand 都在同一帧命令缓冲内完成。渲染直接读取压缩后的 SSBO,不逐帧回读粒子状态,也不为每个发射器单独提交或等待队列。CPU 只维护确定性的发射调度、预算和寿命数量估计。

local embers = particles.newEmitter(20000); // 上万粒子场景
embers.setGpuSimulation(true);
embers.setGravity(0, 30);
embers.start();

调用 isGpuSimulationActive() 可区分“资产请求 GPU”与“本帧已经迁移到 GPU”。particles.getLastGpuResidentEmitters() 和 particles.getLastGpuResidentParticles() 可用于性能 HUD 和自动质量伸缩。后者是 CPU 侧精确寿命估计;后端的存活、生成、死亡、丢弃和间接实例计数采用帧槽延迟读数,不会阻塞当前帧。

当前常驻 GPU 后端覆盖基础点/线/矩形/椭圆发射、重力、线性/径向/切向加速度、阻尼、限速、噪声、本地/世界空间、旋转、尺寸和起止颜色、flipbook、拉伸、ribbon(birthSerial 排序段)、oldest/youngest/distance 透明排序与常用混合模式。需要玩法回调或逐粒子 CPU 状态的功能会自动保留在确定性 CPU 后端,包括碰撞、力场、子发射器、粒子灯光、自定义 shader/canvas,以及自定义速度/尺寸/旋转曲线和多段颜色渐变。没有可用图形后端时也安全回退 CPU。

着色器源位于 graphics/shaders/particle_resident.*(含 particle_resident_sort.comp);修改后运行 python scripts/compile_particle_gpu_shaders.py 更新随引擎编译的 SPIR-V 与 include 文件。

对象关系与调用时机

Particles 管理 Emitter 及配置、模拟、渲染系统;Emitter 持有容量、发射配置与运行状态。模块 update 统一推进所有 emitter(含骨骼绑定同步与蒙皮表面采样),render 按 layer 提交。

applyUnderwaterParticles(ambience,transition,active,transitionFx,entered,exited) 将 Pcg 水下状态接到两个借用 Emitter。ambience 在水下可见并运行、离水停止并隐藏;启用过渡效果且本帧 entered/exited 时,transition 会停止、复位播放状态并重新启动。entered 与 exited 同时为真会在任何修改前被拒绝。

帧序建议:动画 computeWorld → particles.update(dt) → particles.render(gfx)。

目标导向指南

从 JSON 创建火焰

配置 buffer、发射率、寿命、速度、颜色和 autoReload,调用 newEmitterFromFile();设置位置后 start()。模块统一 update(dt) 和 render(gfx),无需逐粒子操作。

制作一次性爆炸

创建容量足够的 emitter,设置有限 emitter life 和较高瞬时发射率,停止循环;播放结束后检查 active 状态并回收。预览时用 preset 起步,再逐项覆盖参数。

角色手上的拖尾 / 皮肤光晕

动画更新并 computeWorld 后,用 attachToBoneByName 绑定肢体,或用 setSkinSource 从蒙皮表面发射;setAttachScale / setSkinScale 把模型单位映射到像素空间。

常见问题

  • buffer 太小导致高发射率粒子被覆盖。
  • 只 render 不 update,粒子静止。
  • 无限 emitter 离开场景后未 stop/回收。
  • 骨骼绑定后位置不对:检查是否先更新姿态(pose.computeWorld(sk) / spine.updateWorldTransform() / IK forwardKinematics/solve),以及 plane/scale 是否匹配相机投影。
  • 蒙皮表面无粒子:确认 setSkinSource 与 hasBones 网格,且过滤器未把候选顶点剔光。

API 快查

下列方法名来自当前 Squirrel 绑定;同一模块创建的辅助对象(例如 World、Body、Source)的方法也列在这里。

  • addBurst()、addColorStop()、addForceField()、addRotationCurvePoint()、addSizeCurvePoint()、addSubEmitter()、addVelocityCurvePoint()、applyConfig()、applyPreset()、attachToBone()、attachToBoneByName()、attachToSkeleton2D()、attachToSkeleton3D()、attachToSpineBone()、attachToSpineBoneByName()、bindFloatParameter()、clearBursts()、clearColorGradient()、clearFloatParameterBindings()、clearFloatParameters()、clearForceFields()、clearRotationCurve()、clearSizeCurve()、clearSkinSource()、clearSubEmitters()、clearVelocityCurve()、detach()、emit()、emitFromSkin()、getAttachBone()、getAttachKind()、getAutoRandomSeed()、getAutoReload()、getBlendMode()、getBufferSize()、getConfigPath()、getCount()、getDirection()、getEmissionRateOverDistance()、getFixedTimeStep()、getGpuSimulation()、getLightsEnabled()、getLooping()、getPlaybackSpeed()、getPrewarmSeconds()、getRandomSeed()、getShader()
  • getEmissionAreaType()、getEmissionAreaX()、getEmissionAreaY()、getEmissionRate()、getEmitterCount()、getEmitterLifetime()、getEmitterName()、getEmitter()、getEmitterByName()、getFloatParameter()、getGpuFallbackReason()、getLastEffectError()、getLayer()、getName()、getPriority()、getMinimumQuality()、getCullingMode()、getCullDistance()、getMaxSpawnPerFrame()、getMaterialMode()、getDistortionStrength()、getNormalTexture()、getResolvedParameterScale()、getRotation()、getScale()、getSimulationBackend()、getSourcePath()、getVersion()
  • getMaxParticles()、getMaxSimulatedEmitters()、getQualityLevel()、getLastSimulatedEmitters()、getLastCulledEmitters()、getLastBudgetSkippedEmitters()、getLastParticleCount()、getLastSpawnedParticles()、getLastDroppedSpawns()、getLastRenderedParticles()、getLastSimulationMs()、getLastRenderMs()
  • getParticleHeight()、getParticleLifetimeMax()、getParticleLifetimeMin()、getParticleWidth()、getSizeVariation()、getSpread()、getX()、getY()
  • hasFloatParameter()、hasSkinSource()、isActive()、isAttached()、isGpuFeatureSetSupported()、isPaused()、isStopped()、isVisible()、loadConfig()、moveTo()、newEffectFromFile()、newEffectFromText()、newEmitter()、newEmitterFromFile()
  • pause()、pollConfigs()、reloadConfig()、render()、reset()、setAttachOffset()、setAttachPlane()、setAttachScale()、setAutoReload()、setCamera()、setCanvas()、getConfigReloadObservation()
  • setAutoRandomSeed()、setBlendMode()、setBudget()、setCollision()、setCollisionBounds()、setColorEnd()、setColorStart()、setCullingMode()、setCullDistance()、setDamping()、setDirection()、setDistortionStrength()、setEmissionArea()、setEmissionRate()、setEmissionRateOverDistance()、setEmitterLife()、setEmitterLifetime()、setEmitterTime()、setFixedTimeStep()、setFlipbook()、setFloatParameter()、setGpuSimulation()、setGravity()、setInheritVelocity()、setLights()、setLimitVelocity()、setLooping()、setMaterialMode()、setMaxDeltaTime()、setMaxSpawnPerFrame()、setMinimumQuality()、setNoise()、setNormalTexture()、setOverflowMode()、setPlaybackSpeed()、setPrewarm()、setPriority()、setQualityLevel()、setRandomSeed()、setRenderMode()、setShader()、setSimulationSpace()、setWorldCollision()
  • setFollowBoneRotation()、setLayer()、setLinearAcceleration()、setParticleLife()、setParticleLifetime()、setParticleSize()、setPosition()、setRadialAcceleration()、setRotation()、setScale()、setSizeVariation()
  • setSizes()、setSkinBoneFilter()、setSkinBoneFilterByName()、setSkinPlane()、setSkinScale()、setSkinSource()、setSpeed()、setSpin()、setSpread()、setStartRotation()、setTangentialAcceleration()、setTexture()、setVisible()、start()
  • stop()、syncAttach()、update()

使用要点

  • 模块对象和它创建的资源对象应保存在全局或实体状态中,不要在每帧重复创建。
  • 带 update(dt) 的系统应在 eve_update 调用;绘制方法应在 eve_render 调用。
  • 参数约束、默认值和返回类型以对应模块头文件及 addFunc 绑定为准;本文 API 快查与当前源码同步生成。

源码: src/modules/particles/ 相关测试: test/particles.cpp、test/particles_effect_asset.cpp、test/particles_p2_p3.cpp、test/particles_reference_effects.cpp、test/particles_attach_skin.cpp、test/particles_dynamic_bones.cpp、test/particles_attach_more.cpp、test/particles_attach_extra.cpp。

applyUnderwaterSurfaceVfx(surfaceVfx,active) 对应 Pcg SetHDRPVisualEffectsState 的反向水下开关:水面时 显示并启动调用者拥有的天气/VFX emitter,潜水时停止并隐藏。调用可对一组 surface emitter 逐个执行;函数 同步借用对象且不保存指针,空对象在修改前返回失败。

applyGroundParticleCulling(emitter,playerTag,visitorTag,entered,exited) 对应 Pcg GroundParticlesCulling 的玩家触发式粒子开关。创建区域时,Box 触发器的完整尺寸使用 Pcg 的 Radius 三轴值,Sphere 触发器直接使用 Radius;Shape 必须设为 sensor,并把稳定的玩家标签从 World3D begin/end 事件传给此函数。组件启用时先调用 emitter.stop();匹配玩家标签的 enter 启动 emitter,exit 停止 emitter,无关访客保持原状态。函数只在调用期间借用 emitter,不保存指针;空 emitter、零玩家标签, 或同时设置 enter/exit 会在修改前返回失败。

shiftWorldSpaceParticles(emitter,shiftX,shiftY) 对应 Pcg FloatingPointFixParticleSystem 与 TerrainLoaderManager.SetOrigin 的存量粒子修正。调用者计算本次世界原点 增量,并在下一次粒子 update/render 前调用;函数从所有存活粒子位置减去该增量,同时保留 emitter 位置、 速度、寿命以及播放/暂停状态。返回值是本次覆盖的 CPU 与 GPU-resident 存活粒子总数。GPU 粒子位移会累计到 下一次 resident update,提交成功后清零;若后端在提交前停用,待提交偏移也随其状态一同清除。 只有 simulationSpace="world" 的 emitter 可调用,本地空间 emitter 和非有限增量会在修改前失败。对于把 3D X/Z 投影到粒子 X/Y 平面的项目,参数 shiftY 对应世界 Z 增量;Pcg 的垂直世界 Y 原点保持不变。

Pcg 随机粒子材质

eve.PcgMaterialSelector() 保存调用者提供的借用纹理列表。add(texture) 添加候选,clear() 清空, count() 返回数量;selectAndApply(emitter,seed) 使用显式 seed 确定性选择并应用到粒子发射器。为忠实对应 Pcg RandomMaterialSelector 的 Random.Range(1, materials.Count),索引 0 被保留且永不选择;因此至少 需要两个非空候选。失败返回标准 Result,不会修改 emitter 原有纹理。