// effects.js — 屏幕特效播放 / 停止(帧序列轮播)。 // // 特效层浮在世界之上、不进 worldContainer(与 HUD 同理,不受相机 pan/zoom)。 // 加一种特效 = 在调用方提供帧 url 序列即可,无需改接口(render-pixi §6 可扩展性)。 /** * @typedef {Object} EffectDef * @property {string[]} frames * @property {number=} fps * @property {boolean=} loop */ /** * @typedef {Object} EffectEntry * @property {*} spr * @property {string[]} frames * @property {number} fps * @property {boolean} loop * @property {number} clock */ /** * 创建特效播放器。 * @param {Object} [deps] * @param {*=} deps.PIXI * @param {*=} deps.layer 特效层容器(建议挂在 app.stage 顶层,非 worldContainer) * @param {ReturnType=} deps.assets * @param {((name:string)=>(EffectDef|null))=} deps.resolve * 把特效名解析成帧序列定义(由场景 / bundle 注入;缺省返回 null 跳过) * @returns {{ play:(name:string,opts?:Partial)=>void, stop:(name:string)=>void, update:(dtMs:number)=>void, stopAll:()=>void }} */ export function createEffects({ PIXI, layer, assets, resolve } = {}) { /** @type {Map} */ const active = new Map() /** @param {string} name @param {Partial} [opts] */ function play(name, opts = {}) { if (!name) return /** @type {EffectDef|null} */ const def = (resolve ? resolve(name) : null) || (opts.frames ? /** @type {EffectDef} */ (opts) : null) if (!def || !def.frames || !def.frames.length) { console.warn('[render-pixi] effect has no frames, skip', name) return } stop(name) const spr = new PIXI.Sprite() if (layer) layer.addChild(spr) /** @type {EffectEntry} */ const entry = { spr, frames: def.frames, fps: def.fps || opts.fps || 12, loop: opts.loop != null ? opts.loop : def.loop != null ? def.loop : false, clock: 0, } for (const u of def.frames) if (assets) assets.load(u) active.set(name, entry) _renderFrame(entry, 0) } /** @param {EffectEntry} entry @param {number} idx */ function _renderFrame(entry, idx) { const url = entry.frames[idx] const tex = url && assets ? assets.get(url) : null if (tex) entry.spr.texture = tex } /** @param {number} dtMs */ function update(dtMs) { for (const [name, e] of active) { e.clock += dtMs const total = e.frames.length const raw = Math.floor(e.clock / (1000 / e.fps)) let idx = raw if (raw >= total) { if (e.loop) idx = raw % total else { stop(name) continue } } _renderFrame(e, idx) } } /** @param {string} name */ function stop(name) { const e = active.get(name) if (!e) return try { e.spr.destroy() } catch (err) { void err } active.delete(name) } function stopAll() { for (const name of [...active.keys()]) stop(name) } return { play, stop, update, stopAll } }