@urso/revolt-fx
Version:
Particle and Effect System for Pixi.js
475 lines • 17.4 kB
JavaScript
/// <reference types="pixi.js" />
import { BaseEffect } from "./BaseEffect";
import { SpawnType } from "./FX";
import deepClone from "./util/DeepClone";
import { FXSignal } from "./util/FXSignal";
import { LinkedList } from "./util/LinkedList";
import { Rnd } from "./util/Rnd";
export class ParticleEmitter extends BaseEffect {
constructor(componentId) {
super(componentId);
this.targetOffset = 0;
this.autoRecycleOnComplete = true;
this._particles = new LinkedList();
this._particleCount = 0;
this._childEmitters = [];
this._hasChildEmitters = false;
this._paused = false;
this.__adoptRotation = false;
this.__on = {
started: new FXSignal(),
completed: new FXSignal(),
exhausted: new FXSignal(),
particleUpdated: new FXSignal(),
particleSpawned: new FXSignal(),
particleBounced: new FXSignal(),
particleDied: new FXSignal()
};
}
// *********************************************************************************************
// * Public *
// *********************************************************************************************
init(container, autoStart = true, scaleMod = 1) {
this.container = container;
this.core.__scaleMod = this._scaleMod = scaleMod;
if (autoStart)
this.start();
return this;
}
start() {
if (this._active)
return this;
const t = Date.now();
const s = this.settings;
const RX = this.__fx;
RX.emitterCount++;
this.infinite = s.infinite;
this._time = Number.MAX_VALUE;
if (s.duration > 0) {
this.endTime = t + s.duration * 1000;
}
else {
this.endTime = s.duration;
}
this._nextSpawnTime = 0;
this._particleCount = 0;
this._active = true;
this.exhausted = this.completed = false;
RX.__addActiveEffect(this);
let l = s.childs.length;
this._hasChildEmitters = l > 0;
if (this._hasChildEmitters) {
while (--l > -1) {
const def = s.childs[l];
const em = !this.settings.__isClone ? RX.getParticleEmitterById(def.id) : RX.createParticleEmitterFrom(def.settings);
const container = RX.__containers[em.settings.containerId] || this.container;
em.init(container, true, (def.scale || 1) * (this._scaleMod || 1));
if (def.adoptRotation) {
em.rotation = this._rotation;
em.__adoptRotation = true;
}
em.__parent = this;
this._childEmitters.push(em);
}
}
this.rotation = this._rotation;
if (this.__on.started.__hasCallback) {
this.__on.started.dispatch(this);
}
return this;
}
stop(waitForParticles = true) {
if (waitForParticles) {
this.exhausted = true;
if (this._hasChildEmitters) {
this.stopChildEmitters(true);
}
}
else {
if (this.__on.completed.__hasCallback) {
this.__on.completed.dispatch(this);
}
if (this.autoRecycleOnComplete) {
this.recycle();
}
else {
this.recycleParticles();
this.completed = true;
this._active = false;
this.__fx.__removeActiveEffect(this);
}
}
}
update(dt) {
if (!this._active)
return this;
const t = Date.now();
const s = this.settings;
if (!this.exhausted) {
if (this.settings.autoRotation !== 0) {
this.rotation += s.autoRotation * (dt / 0.016666);
}
if (this.target) {
this.rotation = this.target.rotation;
if (this.targetOffset == 0) {
this.x = this.target.x;
this.y = this.target.y;
}
else {
this.x = this.target.x + Math.cos(this._rotation) * this.targetOffset;
this.y = this.target.y + Math.sin(this._rotation) * this.targetOffset;
}
}
if (this.endTime == 0 && !this.infinite) {
this.spawn();
this.exhausted = true;
}
else if (this.infinite || t < this.endTime) {
this._time += dt;
if (this._time >= this._nextSpawnTime) {
this._time = 0;
this.spawn();
this._nextSpawnTime = this._time + Rnd.float(s.spawnFrequencyMin, s.spawnFrequencyMax);
}
}
else {
this.exhausted = true;
if (this.__on.exhausted.__hasCallback) {
this.__on.exhausted.dispatch(this);
}
}
}
else {
if (this._particleCount == 0) {
this._active = false;
this.completed = true;
if (this.__on.completed.__hasCallback) {
this.__on.completed.dispatch(this);
}
this.__fx.__removeActiveEffect(this);
if (this.autoRecycleOnComplete)
this.recycle();
}
}
const list = this._particles;
let node = list.first;
let next;
while (node) {
next = node.next;
node.update(dt);
node = next;
}
return this;
}
spawn() {
if (this._paused)
return this;
const s = this.settings;
const fx = this.__fx;
let n = Rnd.integer(s.spawnCountMin, s.spawnCountMax) * fx.particleFac;
this.core.prepare(n);
while (--n > -1) {
if (this._particleCount >= s.maxParticles || fx.particleCount >= fx.maxParticles)
return this;
const ps = s.particleSettings;
const p = fx.__getParticle();
let component;
switch (ps.componentType) {
case 0:
p.componentId = ps.componentId;
component = fx.__getSprite(p.componentId);
break;
case 1:
p.componentId = ps.componentId;
component = fx.__getMovieClip(p.componentId);
if (ps.componentParams) {
component.loop = ps.componentParams.loop == null || !ps.componentParams.loop ? false : true;
component.animationSpeed = Rnd.float(ps.componentParams.animationSpeedMin || 1, ps.componentParams.animationSpeedMax || 1);
}
component.gotoAndPlay(0);
break;
}
component.anchor.set(ps.componentParams.anchorX, ps.componentParams.anchorY);
p.component = component;
this.core.emit(p);
p.init(this, ps, this._scaleMod);
this._particles.add(p);
this._particleCount++;
fx.particleCount++;
}
this.core.step();
this._nextSpawnTime = Rnd.float(s.spawnFrequencyMin, s.spawnFrequencyMax);
return this;
}
recycle() {
if (this.__recycled)
return;
if (this.__parent) {
this.__parent.__removeChildEmitter(this);
this.__parent = undefined;
}
this.recycleParticles();
this.settings = undefined;
this._active = false;
this._paused = false;
this.completed = true;
this._x = this._y = this._rotation = 0;
if (this._hasChildEmitters) {
this.stopChildEmitters(true);
this._childEmitters.length = 0;
this._hasChildEmitters = false;
}
this.__fx.emitterCount--;
this.__fx.__recycleEmitter(this);
this.__recycled = true;
this.__adoptRotation = false;
this.core = null;
this.target = null;
this.name = null;
const on = this.__on;
if (on.completed.__hasCallback)
on.completed.removeAll();
if (on.started.__hasCallback)
on.started.removeAll();
if (on.exhausted.__hasCallback)
on.exhausted.removeAll();
if (on.particleBounced.__hasCallback)
on.particleBounced.removeAll();
if (on.particleDied.__hasCallback)
on.particleDied.removeAll();
if (on.particleSpawned.__hasCallback)
on.particleSpawned.removeAll();
if (on.particleUpdated.__hasCallback)
on.particleUpdated.removeAll();
}
dispose() {
const list = this._particles;
let node = list.first;
let next;
while (node) {
next = node.next;
node.recycle();
node = next;
}
list.clear();
this.__recycled = true;
if (this._hasChildEmitters) {
this.stopChildEmitters(false);
}
this.__fx.particleCount -= this._particleCount;
this._particles = null;
this.componentId = null;
this.settings = null;
this._active = false;
this.completed = true;
this._childEmitters = null;
if (this.core) {
this.core.dispose();
}
this.core = null;
this.target = null;
this.name = null;
const on = this.__on;
on.completed.removeAll();
on.started.removeAll();
on.exhausted.removeAll();
on.particleBounced.removeAll();
on.particleDied.removeAll();
on.particleSpawned.removeAll();
on.particleUpdated.removeAll();
this.__parent = null;
this.__fx.__removeActiveEffect(this);
this.__fx = null;
}
get x() {
return this._x;
}
set x(value) {
this._x = this.core.x = value;
if (!this._xPosIntialized) {
this.core.__x = value;
this._xPosIntialized = true;
}
if (this._hasChildEmitters) {
const childs = this._childEmitters;
let l = childs.length;
while (--l > -1) {
childs[l].x = value;
}
}
}
get y() {
return this._y;
}
set y(value) {
this._y = this.core.y = value;
if (!this._yPosIntialized) {
this.core.__y = value;
this._yPosIntialized = true;
}
if (this._hasChildEmitters) {
const childs = this._childEmitters;
let l = childs.length;
while (--l > -1) {
childs[l].y = value;
}
}
}
set rotation(value) {
this._rotation = this.core.rotation = value;
if (this._hasChildEmitters) {
const childs = this._childEmitters;
let l = childs.length;
while (--l > -1) {
const child = childs[l];
if (child.__adoptRotation) {
child.rotation = child.settings.rotation + value;
}
}
}
}
get rotation() {
return this._rotation;
}
get paused() {
return this._paused;
}
set paused(value) {
this._paused = value;
if (this._hasChildEmitters) {
const childs = this._childEmitters;
let l = childs.length;
while (--l > -1) {
childs[l].paused = value;
}
}
}
get on() {
return this.__on;
}
// *********************************************************************************************
// * Private *
// *********************************************************************************************
recycleParticles() {
let node = this._particles.first;
let next;
while (node) {
next = node.next;
node.recycle();
node = next;
}
this._particles.clear();
this.__fx.particleCount -= this._particleCount;
}
stopChildEmitters(waitForParticles) {
const childs = this._childEmitters;
let l = childs.length;
while (--l > -1) {
childs[l].stop(waitForParticles);
}
}
// *********************************************************************************************
// * Internal *
// *********************************************************************************************
__removeParticle(particle) {
if (particle.useSpawns && this._spawnOnComplete) {
this.__subSpawn(particle, this.settings.particleSettings.spawn.onComplete);
}
this._particles.remove(particle);
this._particleCount--;
this.__fx.particleCount--;
particle.recycle();
}
__removeChildEmitter(emitter) {
const index = this._childEmitters.indexOf(emitter);
if (index > -1) {
this._childEmitters.splice(index, 1);
if (this._childEmitters.length == 0)
this._hasChildEmitters = false;
}
}
__subSpawn(particle, list) {
const fx = this.__fx;
if (list) {
let l = list.length;
while (--l > -1) {
const def = list[l];
let component;
let container;
switch (def.type) {
case 0:
component = !this.settings.__isClone ? fx.getParticleEmitterById(def.id) : fx.createParticleEmitterFrom(def.settings);
container = fx.__containers[component.settings.containerId] || this.container;
component.init(container, true, (def.scale || 1) * this._scaleMod);
if (def.adoptRotation) {
component.rotation = particle.component.rotation + component.settings.rotation;
component.__adoptRotation = true;
}
else {
component.rotation = component.settings.rotation;
}
break;
case 1:
component = !this.settings.__isClone ? fx.getEffectSequenceById(def.id) : fx.createEffectSequenceEmitterFrom(def.settings);
container = fx.__containers[component.settings.containerId] || this.container;
component.init(container, 0, true, (def.scale || 1) * this._scaleMod);
if (def.adoptRotation) {
component.rotation = particle.component.rotation;
}
break;
}
component.x = particle.component.x;
component.y = particle.component.y;
}
}
}
__applySettings(value) {
const fx = this.__fx;
this.__recycled = this._xPosIntialized = this._yPosIntialized = false;
this.settings = value;
this.core = fx.__getEmitterCore(value.core.type, this);
this.core.init(this);
this.rotation = value.rotation;
this.name = value.name;
this._spawnOnComplete = value.particleSettings.spawn.onComplete.length > 0;
this._childEmitters.length = 0;
if (value.__isClone) {
//Clone spawns
const spawns = value.particleSettings.spawn;
for (const key in spawns) {
const list = spawns[key];
for (const spawn of list) {
switch (spawn.type) {
case SpawnType.ParticleEmitter:
spawn.settings = deepClone(fx.__getEmitterSettings(spawn.id));
break;
case SpawnType.EffectSequence:
spawn.settings = deepClone(fx.__getSequenceSettings(spawn.id));
break;
}
spawn.settings.__isClone = true;
}
}
//Clone childs
const childs = value.childs;
for (const spawn of childs) {
switch (spawn.type) {
case SpawnType.ParticleEmitter:
spawn.settings = deepClone(fx.__getEmitterSettings(spawn.id));
break;
case SpawnType.EffectSequence:
spawn.settings = deepClone(fx.__getSequenceSettings(spawn.id));
break;
}
spawn.settings.__isClone = true;
}
}
}
__setCore(type) {
this.core = this.__fx.__getEmitterCore(type, this);
this.core.init(this);
this.core.__scaleMod = this._scaleMod;
this._xPosIntialized = this._yPosIntialized = false;
}
}
//# sourceMappingURL=ParticleEmitter.js.map