273 lines
6.6 KiB
JavaScript
273 lines
6.6 KiB
JavaScript
import K from 'kefir';
|
|
|
|
import {compose, merge} from '@avocado/core';
|
|
import {Entity, StateProperty, Trait} from '@avocado/entity';
|
|
import {Vector} from '@avocado/math';
|
|
import {Ticker} from '@avocado/timing';
|
|
|
|
import {Proton} from '../proton';
|
|
|
|
const PI_180 = Math.PI / 180;
|
|
|
|
const decorate = compose(
|
|
);
|
|
|
|
export default class Emitter extends decorate(Trait) {
|
|
|
|
static behaviorTypes() {
|
|
return {
|
|
emitParticleEntity: {
|
|
cycle: true,
|
|
type: 'entity',
|
|
label: 'Create particle.',
|
|
args: [
|
|
['entity', {
|
|
type: 'entity',
|
|
}],
|
|
],
|
|
},
|
|
emitParticleJson: {
|
|
type: 'stream',
|
|
label: 'Create particle.',
|
|
args: [
|
|
['json', {
|
|
type: 'object',
|
|
}],
|
|
],
|
|
},
|
|
emitParticle: {
|
|
type: 'stream',
|
|
label: 'Create particle.',
|
|
args: [
|
|
['key', {
|
|
type: 'string',
|
|
options: (entity) => (
|
|
Object.keys(entity.traitInstance('emitter').particles)
|
|
.reduce((r, key) => ({...r, [key]: key}), {})
|
|
),
|
|
}],
|
|
['json', {
|
|
type: 'object',
|
|
}],
|
|
],
|
|
},
|
|
};
|
|
}
|
|
|
|
static defaultParams() {
|
|
return {
|
|
particles: {},
|
|
};
|
|
}
|
|
|
|
static describeParams() {
|
|
return {
|
|
particles: {
|
|
type: 'object',
|
|
label: 'Particles',
|
|
},
|
|
};
|
|
}
|
|
|
|
static type() {
|
|
return 'emitter';
|
|
}
|
|
|
|
constructor(entity, params, state) {
|
|
super(entity, params, state);
|
|
this.emissions = [];
|
|
this.emitter = new Proton.Emitter();
|
|
this.particles = {};
|
|
this.proton = new Proton();
|
|
this.proton.addEmitter(this.emitter);
|
|
this.onParticleDead = this.onParticleDead.bind(this);
|
|
this.onParticleUpdate = this.onParticleUpdate.bind(this);
|
|
this.emitter.bindEvent = true;
|
|
this.emitter.addEventListener('PARTICLE_DEAD', this.onParticleDead);
|
|
this.emitter.addEventListener('PARTICLE_UPDATE', this.onParticleUpdate);
|
|
}
|
|
|
|
gatherParticles() {
|
|
const particles = {};
|
|
const hookParticles = this.entity.invokeHookFlat('particles');
|
|
for (let i = 0; i < hookParticles.length; i++) {
|
|
const traitParticles = hookParticles[i];
|
|
for (const key in traitParticles) {
|
|
particles[key] = traitParticles[key];
|
|
}
|
|
}
|
|
for (const key in this.params.particles) {
|
|
if (particles[key]) {
|
|
particles[key] = merge({}, particles[key], this.params.particles[key]);
|
|
}
|
|
else {
|
|
particles[key] = this.params.particles[key];
|
|
}
|
|
}
|
|
this.particles = particles;
|
|
}
|
|
|
|
onParticleDead(particle) {
|
|
if (particle.body.isTransientParticle) {
|
|
particle.body.destroy();
|
|
}
|
|
}
|
|
|
|
onParticleUpdate(particle) {
|
|
particle.body.setPosition([particle.p.x, particle.p.y]);
|
|
particle.body.opacity = particle.alpha;
|
|
particle.body.visibleScale = [particle.scale, particle.scale];
|
|
particle.body.rotation = particle.rotation * PI_180;
|
|
}
|
|
|
|
hooks() {
|
|
const hooks = {};
|
|
hooks.afterDestructionTickers = () => {
|
|
return (elapsed) => {
|
|
this.tick(elapsed);
|
|
if (0 === this.emitter.particles.length) {
|
|
this.emitter.destroy();
|
|
return true;
|
|
}
|
|
};
|
|
};
|
|
return hooks;
|
|
}
|
|
|
|
emitParticleJson(json, emitter) {
|
|
Entity.loadOrInstance(json).then((particle) => {
|
|
this.entity.emitParticleEntity(particle);
|
|
emitter.emit(particle);
|
|
});
|
|
}
|
|
|
|
listeners() {
|
|
return {
|
|
|
|
traitAdded: () => {
|
|
this.gatherParticles();
|
|
},
|
|
|
|
};
|
|
}
|
|
|
|
methods() {
|
|
return {
|
|
|
|
emitParticleEntity: (entity) => {
|
|
const particle = entity.particle();
|
|
const position = particle.position ?
|
|
particle.position
|
|
:
|
|
this.entity.position
|
|
;
|
|
const initializers = [
|
|
new Proton.Body(entity),
|
|
new Proton.Position(new Proton.PointZone(
|
|
position[0],
|
|
position[1]
|
|
)),
|
|
new Proton.Mass(particle.mass),
|
|
new Proton.Life(particle.ttl),
|
|
new Proton.Velocity(
|
|
particle.velocity.magnitude,
|
|
particle.velocity.angle,
|
|
'polar'
|
|
),
|
|
];
|
|
const behaviors = [
|
|
new Proton.Alpha(
|
|
particle.alpha.start,
|
|
particle.alpha.end
|
|
),
|
|
new Proton.Force(
|
|
particle.force[0],
|
|
particle.force[1]
|
|
),
|
|
new Proton.Rotate(
|
|
particle.rotation.start,
|
|
particle.rotation.add,
|
|
'add'
|
|
),
|
|
new Proton.Scale(
|
|
particle.scale.start,
|
|
particle.scale.end
|
|
),
|
|
];
|
|
const protonParticle = this.emitter.createParticle(
|
|
initializers,
|
|
behaviors
|
|
);
|
|
entity.on('destroy', () => {
|
|
this.emitter.removeParticle(protonParticle);
|
|
});
|
|
if (particle.listed) {
|
|
this.entity.list.addEntity(entity);
|
|
}
|
|
// Prime.
|
|
this.onParticleUpdate(protonParticle);
|
|
return entity;
|
|
},
|
|
|
|
emitParticleJson: (json) => {
|
|
let {
|
|
count = 1,
|
|
rate = 0,
|
|
} = json;
|
|
const stream = K.stream((emitter) => {
|
|
if (0 === rate) {
|
|
for (let i = 0; i < count; ++i) {
|
|
this.emitParticleJson(json, emitter);
|
|
}
|
|
}
|
|
else {
|
|
const ticker = new Ticker(rate);
|
|
this.emitParticleJson(json, emitter);
|
|
count -= 1;
|
|
if (count > 0) {
|
|
const removeEmission = () => {
|
|
const index = this.emissions.indexOf(ticker);
|
|
if (-1 !== index) {
|
|
this.emissions.splice(index, 1);
|
|
}
|
|
emitter.end();
|
|
};
|
|
this.entity.on('destroy', removeEmission);
|
|
ticker.on('tick', () => {
|
|
this.emitParticleJson(json, emitter);
|
|
if (0 >= --count) {
|
|
removeEmission();
|
|
}
|
|
});
|
|
this.emissions.push(ticker);
|
|
return;
|
|
}
|
|
}
|
|
emitter.end();
|
|
});
|
|
// Always make sure something listens...
|
|
stream.onEnd(() => {});
|
|
return stream;
|
|
},
|
|
|
|
emitParticle: (key, json = {}) => {
|
|
const particleJson = this.particles[key]
|
|
if (!particleJson) {
|
|
return;
|
|
}
|
|
const mergedJson = merge({}, particleJson, json);
|
|
return this.entity.emitParticleJson(mergedJson);
|
|
},
|
|
|
|
};
|
|
}
|
|
|
|
tick(elapsed) {
|
|
this.emitter.update(elapsed);
|
|
for (let i = 0; i < this.emissions.length; i++) {
|
|
this.emissions[i].tick(elapsed);
|
|
}
|
|
}
|
|
|
|
}
|