duckengine
Version:
A 2D Game Engine for the web.
488 lines (430 loc) • 12 kB
text/typescript
import { Duck } from '../../../index';
import randomFloat from '../../math/randomFloat';
import randomInt from '../../math/randomInt';
import Game from '../../game';
import Particle from './particle';
import Scene from '../../scene';
/**
* @class ParticleEmitter
* @classdesc Creates a DuckEngine ParticleEmitter
* @description The ParticleEmitter Class. Emits, creates, and destroys particles
* @since 1.0.0-beta
*/
export default class ParticleEmitter {
/**
* @memberof ParticleEmitter
* @description A unique identifier for the ParticleEmitter
* @type string
* @since 1.0.0-beta
*/
public readonly id: string;
protected particle: Particle;
/**
* @memberof ParticleEmitter
* @description A range of numbers where particle x pos will be
* @type Duck.Types.ParticleEmitter.Range
* @since 1.0.0-beta
*/
public rangeX: Duck.Types.ParticleEmitter.Range;
/**
* @memberof ParticleEmitter
* @description A range of numbers where particle y pos will be
* @type Duck.Types.ParticleEmitter.Range
* @since 1.0.0-beta
*/
public rangeY: Duck.Types.ParticleEmitter.Range;
/**
* @memberof ParticleEmitter
* @description The starting amount of particle
* @type number
* @since 1.0.0-beta
*/
public readonly amount: number;
/**
* @memberof ParticleEmitter
* @description The list of current particles
* @type Particles[]
* @since 1.0.0-beta
*/
public list: Particle[];
/**
* @memberof ParticleEmitter
* @description Game instance
* @type Game
* @since 1.0.0-beta
*/
public game: Game;
/**
* @memberof ParticleEmitter
* @description Scene instance
* @type Scene
* @since 1.0.0-beta
*/
public scene: Scene;
/**
* @memberof ParticleEmitter
* @description Determines if the ParticleEmitter is emitting or not
* @type boolean
* @since 1.0.0-beta
*/
public emitting: boolean;
/**
* @memberof ParticleEmitter
* @description Determines if the ParticleEmitter is enabled or not
* @type boolean
* @since 2.0.0
*/
public enabled: boolean;
protected floatRangeX: Duck.Types.ParticleEmitter.Range;
protected floatRangeY: Duck.Types.ParticleEmitter.Range;
/**
* @constructor ParticleEmitter
* @description Creates ParticleEmitter instance
* @param {Particle} particle Base particle that is cloned and modified from
* @param {Duck.Types.ParticleEmitter.Range} rangeX Where the new emitted particles x position is. A range of 2 values
* @param {Duck.Types.ParticleEmitter.Range} rangeY Where the new emitted particles y position is. A range of 2 values
* @param {number} amount Amount of starting particles
* @param {boolean} [autoCreate=true] Determines if particles are created on init, populates the list, optional -> defaults: true
* @param {Game} game Game instance
* @param {Scene} scene Scene instance
* @since 1.0.0-beta
*/
constructor(
particle: Particle,
rangeX: Duck.Types.ParticleEmitter.Range,
rangeY: Duck.Types.ParticleEmitter.Range,
amount: number,
game: Game,
scene: Scene,
autoCreate = true
) {
this.id = particle.id;
this.particle = particle;
this.rangeX = rangeX;
this.rangeY = rangeY;
this.amount = amount;
this.list = [];
this.game = game;
this.scene = scene;
this.emitting = false;
this.enabled = false;
this.floatRangeX = [0, 0];
this.floatRangeY = [0, 0];
// create particles
if (autoCreate) {
this.create();
}
}
/**
* @memberof ParticleEmitter
* @description Creates an amount of particles
* @since 2.1.0
*/
public create() {
for (let i = 0; i < this.amount; i++) {
this.createOne();
}
}
/**
* @memberof ParticleEmitter
* @description Creates one particle
* @since 2.1.0
*/
public createOne() {
const obj = new Particle(
this.particle.shape,
this.particle.w,
this.particle.h,
this.particle.r,
this.particle.originalFillColorOrIMGPath,
this.game,
this.scene
);
obj.visible = true;
obj.enabled = true;
obj.position.x = randomInt(this.rangeX[0], this.rangeX[1]);
obj.position.y = randomInt(this.rangeY[0], this.rangeY[1]);
obj.floatVelocity.x = randomFloat(
this.floatRangeX[0],
this.floatRangeX[1],
1
);
obj.floatVelocity.y = randomFloat(
this.floatRangeY[0],
this.floatRangeY[1],
1
);
this.list.push(obj);
// add to display list
this.scene.displayList.add(obj);
// no need to pool the renderer pipeline as displayList.add pools the renderer pipeline
// add to physics list
this.scene.physicsList.add(obj);
}
/**
* @memberof ParticleEmitter
* @description Starts emitting particles
* @since 1.0.0-beta
*/
public emit() {
this.emitting = true;
this.enabled = true;
}
/**
* @memberof ParticleEmitter
* @description Stops emitting particles
* @since 1.0.0-beta
*/
public stopEmit() {
this.emitting = false;
this.enabled = false;
}
/**
* @memberof ParticleEmitter
* @description Starts emitting particles for a certain duration
* @param {number} ms Duration in milliseconds
* @since 1.0.0-beta
*/
public emitFor(ms: number) {
this.emitting = true;
setTimeout(() => {
this.emitting = false;
}, ms);
}
/**
* @memberof ParticleEmitter
* @description Sets the new or old particles' position range
* @param {Duck.Types.ParticleEmitter.Range} rangeX Where the new emitted particles x position is. A range of 2 values
* @param {Duck.Types.ParticleEmitter.Range} rangeY Where the new emitted particles y position is. A range of 2 values
* @since 1.0.0-beta
*/
public setRange(
rangeX: Duck.Types.ParticleEmitter.Range,
rangeY: Duck.Types.ParticleEmitter.Range
) {
this.rangeX = rangeX;
this.rangeY = rangeY;
this.list.forEach((obj) => {
obj.position.x = randomInt(this.rangeX[0], this.rangeX[1]);
obj.position.y = randomInt(this.rangeY[0], this.rangeY[1]);
obj.floatVelocity.x = randomFloat(
this.floatRangeX[0],
this.floatRangeX[1],
1
);
obj.floatVelocity.y = randomFloat(
this.floatRangeY[0],
this.floatRangeY[1],
1
);
});
}
/**
* @memberof ParticleEmitter
* @description Spawns new particles after the initial particles
* @param {number} intervalMS How often a new particle gets spawned
* @param {number} [limitTo] A limit to how many particles can be rendered at once, optional
* @since 1.0.0-beta
*/
public keepEmitting(intervalMS: number, limitTo?: number) {
setInterval(() => {
if (limitTo) {
if (this.list.length < limitTo) {
this.createOne();
}
} else {
this.createOne();
}
}, intervalMS);
}
/**
* @memberof ParticleEmitter
* @description Pops particles from the list if too many particles exist
* @since 1.0.0
*/
public offloadMaxAmount(limit: number) {
if (this.list.length >= limit) {
this.list.pop();
}
// remove from display list
(
this.scene.displayList.list.filter(
(renderableObject) => renderableObject instanceof Particle
) as Particle[]
).pop();
// remove from physics list
(
this.scene.displayList.list.filter(
(obj) => obj instanceof Particle
) as Particle[]
).pop();
}
/**
* @memberof ParticleEmitter
* @description Offloads particles based on position
* @param {number} bounds The Bounds of the particles, a BoundsLike object that causes a particle to be offloaded
* @since 2.0.0
*/
public offloadBounds(bounds: Duck.Types.Math.BoundsLike) {
this.list.forEach((particle, index) => {
if (particle.position.x < bounds.x) {
this.list.splice(index, 1);
}
if (particle.position.y < bounds.y) {
this.list.splice(index, 1);
}
if (particle.position.x > bounds.w) {
this.list.splice(index, 1);
}
if (particle.position.y > bounds.w) {
this.list.splice(index, 1);
}
});
// remove from displayList
this.scene.displayList.each((renderableObject, index) => {
if (renderableObject instanceof Particle) {
if (renderableObject.position.x < bounds.x) {
this.list.splice(index, 1);
}
if (renderableObject.position.y < bounds.y) {
this.list.splice(index, 1);
}
if (renderableObject.position.x > bounds.w) {
this.list.splice(index, 1);
}
if (renderableObject.position.y > bounds.w) {
this.list.splice(index, 1);
}
}
});
// remove from physicsList
this.scene.physicsList.each((obj, index) => {
if (obj instanceof Particle) {
if (obj instanceof Particle) {
if (obj.position.x < bounds.x) {
this.list.splice(index, 1);
}
if (obj.position.y < bounds.y) {
this.list.splice(index, 1);
}
if (obj.position.x > bounds.w) {
this.list.splice(index, 1);
}
if (obj.position.y > bounds.w) {
this.list.splice(index, 1);
}
}
}
});
}
/**
* @memberof ParticleEmitter
* @description Offloads particles based on how many seconds they existed for
* @param {number} ageInSeconds Max amount of seconds a particle existed for
* @since 1.0.0
*/
public offloadMaxAge(ageInSeconds: number) {
this.list.forEach((particle, index) => {
if (particle.age >= ageInSeconds) {
this.list.splice(index, 1);
}
});
// remove from displayList
this.scene.displayList.each((renderableObject, index) => {
if (renderableObject instanceof Particle) {
if (renderableObject.age >= ageInSeconds) {
this.scene.displayList.splice(index, 1);
}
}
});
// remove from physicsList
this.scene.physicsList.each((obj, index) => {
if (obj instanceof Particle) {
if (obj.age >= ageInSeconds) {
this.scene.physicsList.splice(index, 1);
}
}
});
}
/**
* @memberof ParticleEmitter
* @description Sets the velocity of all particles
* @param {'x'|'y'} axis 'x' or 'y' axis to change the velocity of all particles
* @param {number} v Value to set the velocity of all particles
* @since 1.0.0-beta
*/
public setVelocity(axis: 'x' | 'y', v: number) {
this.list.forEach((particle) => {
particle.setVelocity(axis, v);
});
}
/**
* @memberof ParticleEmitter
* @description Wobbles the particle on a random direction and axis
* @param {number} v Value to set the wobble velocity to
* @since 1.0.0-beta
*/
public wobble(v: number) {
const r = randomInt(1, 4);
if (r === 1) {
this.list.forEach((particle) => {
particle.setVelocity('x', v);
});
}
if (r === 2) {
this.list.forEach((particle) => {
particle.setVelocity('y', v);
});
}
if (r === 3) {
this.list.forEach((particle) => {
particle.setVelocity('x', -v);
});
}
if (r === 4) {
this.list.forEach((particle) => {
particle.setVelocity('y', -v);
});
}
}
/**
* @memberof ParticleEmitter
* @description Sets the floatVelocity of all particles in between a range
* @param {Duck.Types.ParticleEmitter.Range} rangeVX A range of 2 values for the floatVelocity.x, randomly chosen between that range
* @param {Duck.Types.ParticleEmitter.Range} rangeVY A range of 2 values for the floatVelocity.y, randomly chosen between that range
* @since 1.0.0-beta
*/
public float(
rangeVX: Duck.Types.ParticleEmitter.Range,
rangeVY: Duck.Types.ParticleEmitter.Range
) {
this.floatRangeX = rangeVX;
this.floatRangeY = rangeVY;
this.list.forEach((particle) => {
particle.floatVelocity.x = randomInt(rangeVX[0], rangeVX[1]);
particle.floatVelocity.y = randomInt(rangeVY[0], rangeVY[1]);
});
}
/**
* @memberof ParticleEmitter
* @description Sets the fillColor of all particles
* @param {string} fillColor Color
* @since 1.0.0-beta
*/
public setFillColor(fillColor: string) {
this.list.forEach((particle) => {
particle.setFillColor(fillColor);
});
}
/**
* @memberof ParticleEmitter
* @description Sets the imagePath of all particles
* @param {string} imagePath Image path
* @since 1.0.0
*/
public setImagePath(imagePath: string) {
this.list.forEach((particle) => {
particle.setImagePath(imagePath);
});
}
}