effects.js
3.04 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
// 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<import('./assets.js').createAssetLoader>=} deps.assets
* @param {((name:string)=>(EffectDef|null))=} deps.resolve
* 把特效名解析成帧序列定义(由场景 / bundle 注入;缺省返回 null 跳过)
* @returns {{ play:(name:string,opts?:Partial<EffectDef>)=>void, stop:(name:string)=>void, update:(dtMs:number)=>void, stopAll:()=>void }}
*/
export function createEffects({ PIXI, layer, assets, resolve } = {}) {
/** @type {Map<string, EffectEntry>} */
const active = new Map()
/** @param {string} name @param {Partial<EffectDef>} [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 }
}