跳转到内容

代码篇 — 用代码控制 VFX

本篇讲如何在运行时用代码驱动 VFX:播放控制、动态改属性、发事件、接收回调。所有 API 以引擎 VisualEffect.ts 实际签名为准。

代码控制的对象是场景节点上的 VisualEffect 组件.vfx 在 IDE 里编好、挂到节点后,运行时通过该组件操作。


VisualEffect 是引擎组件。在挂了该组件的节点脚本里获取:

// 标准方式
const vfx = this.owner.getComponent(Laya.VisualEffect);
// 工程示例里用的稳妥写法(VisualEffect 是引擎符号,按组件名兜底查找)
private findVisualEffect(): any {
const comps = (this.owner as any)._components;
if (!comps) return null;
for (const c of comps) {
if (c && c.constructor?.name === "VisualEffect") return c;
}
return null;
}

参考:LayaVFXSample/src/CustomSpawnerDemo.tsOutputEventDemo.ts 等 Demo 脚本均用此模式。


API签名作用
play()play(): void发送 OnPlay 事件启动生成(并触发 PreWarm)
stop()stop(): void发送 OnStop 停止生成(已存在粒子继续模拟到死亡)
pauseget/set pause: boolean暂停/恢复模拟(暂停时 dt=0,保留已积累的时间)
playRateplayRate: number(默认 1.0)播放速率倍数(0.5=半速,2=两倍速)
advanceOneFrame()advanceOneFrame(): void单步前进一帧(调试用)
const vfx = this.owner.getComponent(Laya.VisualEffect);
vfx.play(); // 启动
vfx.playRate = 0.5; // 半速
vfx.pause = true; // 暂停
vfx.pause = false; // 恢复
vfx.stop(); // 停止

字段签名说明
assetget/set asset: VFXAsset资源;赋值时自动初始化整套系统
randomSeedrandomSeed: number(默认 0)随机种子(resetSeedOnPlay=false 时生效,可复现)
resetSeedOnPlayresetSeedOnPlay: boolean(默认 true)每次 play 是否重置种子
initialEventget/set initialEvent: string初始事件名(默认取 asset 的 initialEventName
mainCameraget/set mainCamera: Camera主相机(billboard 朝向计算用,默认自动取场景主相机)

对应 Blackboard 里 Exposed=true 的属性。按类型选对应方法

API签名
FloatsetPropertyFloat(name: string, value: number): void
Vec2setPropertyVec2(name: string, x: number, y: number): void
Vec3setPropertyVec3(name: string, x: number, y: number, z: number): void
Vec4 / ColorsetPropertyVec4(name: string, x: number, y: number, z: number, w: number): void
vfx.setPropertyFloat("EmissionRate", 25);
vfx.setPropertyVec3("WindDir", 1, 0, 0);
vfx.setPropertyVec4("TintColor", 1, 0, 0, 1); // 颜色用 Vec4 (rgba)

name 必须与 Blackboard 里的属性名一致。Color 没有专门的 set 方法,用 setPropertyVec4 传 rgba。 这与 IDE 组件的 Properties Override 面板图级与组件)是同一套机制 —— 面板里勾选覆盖的值,运行时也是通过这些方法应用。

setBuffer(name: string, buffer: Laya.DeviceBuffer): void

把外部 DeviceBuffer 绑到图里的 sampleGraphicsBuffer Operator,用于自定义数据驱动粒子(参考 src/GraphicsBufferDemo.ts)。


除了 OnPlay/OnStop,可触发自定义事件(图里加 Event 节点自定义名字):

API签名说明
按 IDsendEvent(id: number, attribute?: VFXEventAttribute): voidid 由 Shader3D.propertyNameToID(name) 得到
按名sendEventByName(name: string, attribute?: VFXEventAttribute): void直接用事件名
创建载荷createEventAttribute(): VFXEventAttribute创建事件附带属性对象
// 简单触发
vfx.sendEventByName("Explode");
// 带载荷触发(把数据传给 Spawn/Initialize 的 Source 属性)
const attr = vfx.createEventAttribute();
attr.setVector3("position", hit.x, hit.y, hit.z);
attr.setFloat("size", 2.0);
vfx.sendEventByName("Explode", attr);

VFXEventAttribute 设值方法setFloat / setVector2 / setVector3 / setVector4 / setBool / setInt / setUint


6. 接收 Output Event 回调(GPU → CPU)

Section titled “6. 接收 Output Event 回调(GPU → CPU)”

图里有 Output Event Context 时,粒子触发的事件会异步回调到 CPU(通常 1-2 帧延迟,因 GPU readback)。直接赋值 outputEventReceived 回调:

vfx.outputEventReceived = (args) => {
// args 字段:
// eventName: string — Output Event 的事件名
// particleId: number — 源粒子 id
// position: number[] — [x, y, z]
// velocity: number[] — [vx, vy, vz]
// age: number, lifetime: number
// color: number[] — [r, g, b, a]
// size: number
if (args.eventName === "OnHit") {
this.spawnDecal(args.position[0], args.position[1], args.position[2]);
}
};
// 卸载
vfx.outputEventReceived = null;

典型用途:粒子落地播音效、命中生成贴花、烟花炸点生成二级特效(CPU 侧逻辑)。参考 src/OutputEventDemo.ts


7. 自定义 Spawn 回调(脚本驱动生成数量)

Section titled “7. 自定义 Spawn 回调(脚本驱动生成数量)”

图里用 Custom Spawner Block(配 Callback Name)时,用代码决定每帧生成多少:

setCustomSpawnCallback(name: string, callback: IVFXCustomSpawnCallback): void
clearCustomSpawnCallback(name: string): void
// 回调签名: (state, dt) => 本帧要生成的粒子数
vfx.setCustomSpawnCallback("onClick", (state, dt) => {
const base = 40 * dt; // 基础每秒 40
const burst = this._pending; // 鼠标点击排队的爆发量
this._pending = 0;
return base + burst;
});
// 节点销毁时清理
vfx.clearCustomSpawnCallback("onClick");

name 对应 Block 的 Callback Name。参考 src/CustomSpawnerDemo.ts(点击屏幕喷一团粒子)。


8. 绑定 SkinnedMesh 源(粒子贴骨骼动画角色)

Section titled “8. 绑定 SkinnedMesh 源(粒子贴骨骼动画角色)”

图里用 sampleSkinnedMeshXxx Operator 时,用代码把场景里的 SkinnedMeshRenderer 注册进去:

setSkinnedMeshSource(name: string, renderer: SkinnedMeshRenderer): void
clearSkinnedMeshSource(name: string): void
const smr = monkey.getComponent(Laya.SkinnedMeshRenderer);
vfx.setSkinnedMeshSource("monkey", smr); // "monkey" 与 Operator 的 Source Name 一致

用途:粒子贴在角色表面随骨骼动画运动(溶解/受击火星)。参考 src/SampleSkinnedMeshDemo.ts


const { regClass } = Laya;
@regClass()
export class MyVfxController extends Laya.Script {
declare owner: Laya.Sprite3D;
private _vfx: Laya.VisualEffect;
onStart(): void {
this._vfx = this.owner.getComponent(Laya.VisualEffect);
this._vfx.play();
this._vfx.setPropertyFloat("EmissionRate", 50);
// 接收粒子命中事件
this._vfx.outputEventReceived = (args) => {
console.log("hit at", args.position);
};
}
// 点击时触发一次爆发事件
onClick(): void {
this._vfx.sendEventByName("Burst");
}
onDestroy(): void {
if (this._vfx) this._vfx.outputEventReceived = null;
}
}

分类API
播放play() / stop() / pause / playRate / advanceOneFrame()
字段asset / randomSeed / resetSeedOnPlay / initialEvent / mainCamera
属性setPropertyFloat/Vec2/Vec3/Vec4(name, ...) / setBuffer(name, buffer)
事件sendEvent(id, attr?) / sendEventByName(name, attr?) / createEventAttribute()
回调outputEventReceived = (args) => {...}
自定义生成setCustomSpawnCallback(name, cb) / clearCustomSpawnCallback(name)
蒙皮源setSkinnedMeshSource(name, renderer) / clearSkinnedMeshSource(name)

➡️ 属性详解·Operator(第二批) ➡️ 示例验证索引(第二批)