/** * @author Richard Davey * @copyright 2013-2023 Photon Storm Ltd. * @license {@link https://opensource.org/licenses/MIT|MIT License} */ var Class = require('../../utils/Class'); var Components = require('../components'); var ComponentsToJSON = require('../components/ToJSON'); var CopyFrom = require('../../geom/rectangle/CopyFrom'); var DeathZone = require('./zones/DeathZone'); var EdgeZone = require('./zones/EdgeZone'); var EmitterColorOp = require('./EmitterColorOp'); var EmitterOp = require('./EmitterOp'); var Events = require('./events'); var GameObject = require('../GameObject'); var GetFastValue = require('../../utils/object/GetFastValue'); var GetRandom = require('../../utils/array/GetRandom'); var GravityWell = require('./GravityWell'); var HasAny = require('../../utils/object/HasAny'); var HasValue = require('../../utils/object/HasValue'); var Inflate = require('../../geom/rectangle/Inflate'); var List = require('../../structs/List'); var MergeRect = require('../../geom/rectangle/MergeRect'); var Particle = require('./Particle'); var RandomZone = require('./zones/RandomZone'); var Rectangle = require('../../geom/rectangle/Rectangle'); var RectangleToRectangle = require('../../geom/intersects/RectangleToRectangle'); var Remove = require('../../utils/array/Remove'); var Render = require('./ParticleEmitterRender'); var StableSort = require('../../utils/array/StableSort'); var TransformMatrix = require('../components/TransformMatrix'); var Vector2 = require('../../math/Vector2'); var Wrap = require('../../math/Wrap'); var ParticleBounds = require('./ParticleBounds'); /** * Names of simple configuration properties. * * @ignore */ var configFastMap = [ 'active', 'advance', 'blendMode', 'colorEase', 'deathCallback', 'deathCallbackScope', 'duration', 'emitCallback', 'emitCallbackScope', 'follow', 'frequency', 'gravityX', 'gravityY', 'maxAliveParticles', 'maxParticles', 'name', 'emitting', 'particleBringToTop', 'particleClass', 'radial', 'sortCallback', 'sortOrderAsc', 'sortProperty', 'stopAfter', 'tintFill', 'timeScale', 'trackVisible', 'visible' ]; /** * Names of complex configuration properties. * * @ignore */ var configOpMap = [ 'accelerationX', 'accelerationY', 'alpha', 'angle', 'bounce', 'color', 'delay', 'hold', 'lifespan', 'maxVelocityX', 'maxVelocityY', 'moveToX', 'moveToY', 'quantity', 'rotate', 'scaleX', 'scaleY', 'speedX', 'speedY', 'tint', 'x', 'y' ]; /** * @classdesc * A Particle Emitter is a special kind of Game Object that controls a pool of {@link Phaser.GameObjects.Particles.Particle Particles}. * * Particle Emitters are created via a configuration object. The properties of this object * can be specified in a variety of formats, given you plenty of scope over the values they * return, leading to complex visual effects. Here are the different forms of configuration * value you can give: * * ## An explicit static value: * * ```js * x: 400 * ``` * * The x value will always be 400 when the particle is spawned. * * ## A random value: * * ```js * x: [ 100, 200, 300, 400 ] * ``` * * The x value will be one of the 4 elements in the given array, picked at random on emission. * * ## A custom callback: * * ```js * x: (particle, key, t, value) => { * return value + 50; * } * ``` * * The x value is the result of calling this function. This is only used when the * particle is emitted, so it provides it's initial starting value. It is not used * when the particle is updated (see the onUpdate callback for that) * * ## A start / end object: * * This allows you to control the change in value between the given start and * end parameters over the course of the particles lifetime: * * ```js * scale: { start: 0, end: 1 } * ``` * * The particle scale will start at 0 when emitted and ease to a scale of 1 * over the course of its lifetime. You can also specify the ease function * used for this change (the default is Linear): * * ```js * scale: { start: 0, end: 1, ease: 'bounce.out' } * ``` * * ## A start / end random object: * * The start and end object can have an optional `random` parameter. * This forces it to pick a random value between the two values and use * this as the starting value, then easing to the 'end' parameter over * its lifetime. * * ```js * scale: { start: 4, end: 0.5, random: true } * ``` * * The particle will start with a random scale between 0.5 and 4 and then * scale to the end value over its lifetime. You can combine the above * with the `ease` parameter as well to control the value easing. * * ## An interpolation object: * * You can provide an array of values which will be used for interpolation * during the particles lifetime. You can also define the interpolation * function to be used. There are three provided: `linear` (the default), * `bezier` and `catmull`, or you can provide your own function. * * ```js * x: { values: [ 50, 500, 200, 800 ], interpolation: 'catmull' } * ``` * * The particle scale will interpolate from 50 when emitted to 800 via the other * points over the course of its lifetime. You can also specify an ease function * used to control the rate of change through the values (the default is Linear): * * ```js * x: { values: [ 50, 500, 200, 800 ], interpolation: 'catmull', ease: 'bounce.out } * ``` * * ## A stepped emitter object: * * The `steps` parameter allows you to control the placement of sequential * particles across the start-end range: * * ```js * x: { steps: 32, start: 0, end: 576 } * ``` * * Here we have a range of 576 (start to end). This is divided into 32 steps. * * The first particle will emit at the x position of 0. The next will emit * at the next 'step' along, which would be 18. The following particle will emit * at the next step, which is 36, and so on. Because the range of 576 has been * divided by 32, creating 18 pixels steps. When a particle reaches the 'end' * value the next one will start from the beginning again. * * ## A stepped emitter object with yoyo: * * You can add the optional `yoyo` property to a stepped object: * * ```js * x: { steps: 32, start: 0, end: 576, yoyo: true } * ``` * * As with the stepped emitter, particles are emitted in sequence, from 'start' * to 'end' in step sized jumps. Normally, when a stepped emitter reaches the * end it snaps around to the start value again. However, if you provide the 'yoyo' * parameter then when it reaches the end it will reverse direction and start * emitting back down to 'start' again. Depending on the effect you require this * can often look better. * * ## A min / max object: * * This allows you to pick a random float value between the min and max properties: * * ```js * x: { min: 100, max: 700 } * ``` * * The x value will be a random float between min and max. * * You can force it select an integer by setting the 'int' flag: * * ```js * x: { min: 100, max: 700, int: true } * ``` * * Or, you could use the 'random' array approach (see below) * * ## A random object: * * This allows you to pick a random integer value between the first and second array elements: * * ```js * x: { random: [ 100, 700 ] } * ``` * * The x value will be a random integer between 100 and 700 as it takes the first * element in the 'random' array as the 'min' value and the 2nd element as the 'max' value. * * ## Custom onEmit and onUpdate callbacks: * * If the above won't give you the effect you're after, you can provide your own * callbacks that will be used when the particle is both emitted and updated: * * ```js * x: { * onEmit: (particle, key, t, value) => { * return value; * }, * onUpdate: (particle, key, t, value) => { * return value; * } * } * ``` * * You can provide either one or both functions. The `onEmit` is called at the * start of the particles life and defines the value of the property on birth. * * The `onUpdate` function is called every time the Particle Emitter updates * until the particle dies. Both must return a value. * * The properties are: * * particle - A reference to the Particle instance. * key - The string based key of the property, i.e. 'x' or 'lifespan'. * t - The current normalized lifetime of the particle, between 0 (birth) and 1 (death). * value - The current property value. At a minimum you should return this. * * By using the above configuration options you have an unlimited about of * control over how your particles behave. * * ## v3.55 Differences * * Prior to v3.60 Phaser used a `ParticleEmitterManager`. This was removed in v3.60 * and now calling `this.add.particles` returns a `ParticleEmitter` instance instead. * * In order to streamline memory and the display list we have removed the * `ParticleEmitterManager` entirely. When you call `this.add.particles` you're now * creating a `ParticleEmitter` instance, which is being added directly to the * display list and can be manipulated just like any other Game Object, i.e. * scaled, rotated, positioned, added to a Container, etc. It now extends the * `GameObject` base class, meaning it's also an event emitter, which allowed us * to create some handy new events for particles. * * So, to create an emitter, you now give it an xy coordinate, a texture and an * emitter configuration object (you can also set this later, but most commonly * you'd do it on creation). I.e.: * * ```js * const emitter = this.add.particles(100, 300, 'flares', { * frame: 'red', * angle: { min: -30, max: 30 }, * speed: 150 * }); * ``` * * This will create a 'red flare' emitter at 100 x 300. * * Please update your code to ensure it adheres to the new function signatures. * * @class ParticleEmitter * @extends Phaser.GameObjects.GameObject * @memberof Phaser.GameObjects.Particles * @constructor * @since 3.60.0 * * @extends Phaser.GameObjects.Components.AlphaSingle * @extends Phaser.GameObjects.Components.BlendMode * @extends Phaser.GameObjects.Components.Depth * @extends Phaser.GameObjects.Components.Mask * @extends Phaser.GameObjects.Components.Pipeline * @extends Phaser.GameObjects.Components.PostPipeline * @extends Phaser.GameObjects.Components.ScrollFactor * @extends Phaser.GameObjects.Components.Texture * @extends Phaser.GameObjects.Components.Transform * @extends Phaser.GameObjects.Components.Visible * * @param {Phaser.Scene} scene - The Scene to which this Game Object belongs. A Game Object can only belong to one Scene at a time. * @param {number} [x] - The horizontal position of this Game Object in the world. * @param {number} [y] - The vertical position of this Game Object in the world. * @param {(string|Phaser.Textures.Texture)} [texture] - The key, or instance of the Texture this Game Object will use to render with, as stored in the Texture Manager. * @param {Phaser.Types.GameObjects.Particles.ParticleEmitterConfig} [config] - Settings for this emitter. */ var ParticleEmitter = new Class({ Extends: GameObject, Mixins: [ Components.AlphaSingle, Components.BlendMode, Components.Depth, Components.Mask, Components.Pipeline, Components.PostPipeline, Components.ScrollFactor, Components.Texture, Components.Transform, Components.Visible, Render ], initialize: function ParticleEmitter (scene, x, y, texture, config) { GameObject.call(this, scene, 'ParticleEmitter'); /** * The Particle Class which will be emitted by this Emitter. * * @name Phaser.GameObjects.Particles.ParticleEmitter#particleClass * @type {function} * @default Phaser.GameObjects.Particles.Particle * @since 3.0.0 * @see Phaser.Types.GameObjects.Particles.ParticleClassConstructor */ this.particleClass = Particle; /** * An internal object holding all of the EmitterOp instances. * * These are populated as part of the Emitter configuration parsing. * * You typically do not access them directly, but instead use the * provided getters and setters on this class, such as `ParticleEmitter.speedX` etc. * * @name Phaser.GameObjects.Particles.ParticleEmitter#ops * @type {Phaser.Types.GameObjects.Particles.ParticleEmitterOps} * @since 3.60.0 */ this.ops = { accelerationX: new EmitterOp('accelerationX', 0), accelerationY: new EmitterOp('accelerationY', 0), alpha: new EmitterOp('alpha', 1), angle: new EmitterOp('angle', { min: 0, max: 360 }, true), bounce: new EmitterOp('bounce', 0), color: new EmitterColorOp('color'), delay: new EmitterOp('delay', 0, true), hold: new EmitterOp('hold', 0, true), lifespan: new EmitterOp('lifespan', 1000, true), maxVelocityX: new EmitterOp('maxVelocityX', 10000), maxVelocityY: new EmitterOp('maxVelocityY', 10000), moveToX: new EmitterOp('moveToX', 0), moveToY: new EmitterOp('moveToY', 0), quantity: new EmitterOp('quantity', 1, true), rotate: new EmitterOp('rotate', 0), scaleX: new EmitterOp('scaleX', 1), scaleY: new EmitterOp('scaleY', 1), speedX: new EmitterOp('speedX', 0, true), speedY: new EmitterOp('speedY', 0, true), tint: new EmitterOp('tint', 0xffffff), x: new EmitterOp('x', 0), y: new EmitterOp('y', 0) }; /** * A radial emitter will emit particles in all directions between angle min and max, * using {@link Phaser.GameObjects.Particles.ParticleEmitter#speed} as the value. If set to false then this acts as a point Emitter. * A point emitter will emit particles only in the direction derived from the speedX and speedY values. * * @name Phaser.GameObjects.Particles.ParticleEmitter#radial * @type {boolean} * @default true * @since 3.0.0 * @see Phaser.GameObjects.Particles.ParticleEmitter#setRadial */ this.radial = true; /** * Horizontal acceleration applied to emitted particles, in pixels per second squared. * * @name Phaser.GameObjects.Particles.ParticleEmitter#gravityX * @type {number} * @default 0 * @since 3.0.0 * @see Phaser.GameObjects.Particles.ParticleEmitter#setGravity */ this.gravityX = 0; /** * Vertical acceleration applied to emitted particles, in pixels per second squared. * * @name Phaser.GameObjects.Particles.ParticleEmitter#gravityY * @type {number} * @default 0 * @since 3.0.0 * @see Phaser.GameObjects.Particles.ParticleEmitter#setGravity */ this.gravityY = 0; /** * Whether accelerationX and accelerationY are non-zero. Set automatically during configuration. * * @name Phaser.GameObjects.Particles.ParticleEmitter#acceleration * @type {boolean} * @default false * @since 3.0.0 */ this.acceleration = false; /** * Whether moveToX and moveToY are set. Set automatically during configuration. * * When true the particles move toward the moveToX and moveToY coordinates and arrive at the end of their life. * Emitter angle, speedX, and speedY are ignored. * * @name Phaser.GameObjects.Particles.ParticleEmitter#moveTo * @type {boolean} * @default false * @since 3.0.0 */ this.moveTo = false; /** * A function to call when a particle is emitted. * * @name Phaser.GameObjects.Particles.ParticleEmitter#emitCallback * @type {?Phaser.Types.GameObjects.Particles.ParticleEmitterCallback} * @default null * @since 3.0.0 */ this.emitCallback = null; /** * The calling context for {@link Phaser.GameObjects.Particles.ParticleEmitter#emitCallback}. * * @name Phaser.GameObjects.Particles.ParticleEmitter#emitCallbackScope * @type {?*} * @default null * @since 3.0.0 */ this.emitCallbackScope = null; /** * A function to call when a particle dies. * * @name Phaser.GameObjects.Particles.ParticleEmitter#deathCallback * @type {?Phaser.Types.GameObjects.Particles.ParticleDeathCallback} * @default null * @since 3.0.0 */ this.deathCallback = null; /** * The calling context for {@link Phaser.GameObjects.Particles.ParticleEmitter#deathCallback}. * * @name Phaser.GameObjects.Particles.ParticleEmitter#deathCallbackScope * @type {?*} * @default null * @since 3.0.0 */ this.deathCallbackScope = null; /** * Set to hard limit the amount of particle objects this emitter is allowed to create * in total. This is the number of `Particle` instances it can create, not the number * of 'alive' particles. * * 0 means unlimited. * * @name Phaser.GameObjects.Particles.ParticleEmitter#maxParticles * @type {number} * @default 0 * @since 3.0.0 */ this.maxParticles = 0; /** * The maximum number of alive and rendering particles this emitter will update. * When this limit is reached, a particle needs to die before another can be emitted. * * 0 means no limits. * * @name Phaser.GameObjects.Particles.ParticleEmitter#maxAliveParticles * @type {number} * @default 0 * @since 3.60.0 */ this.maxAliveParticles = 0; /** * If set, either via the Emitter config, or by directly setting this property, * the Particle Emitter will stop emitting particles once this total has been * reached. It will then enter a 'stopped' state, firing the `STOP` * event. Note that entering a stopped state doesn't mean all the particles * have finished, just that it's not emitting any further ones. * * To know when the final particle expires, listen for the COMPLETE event. * * Use this if you wish to launch an exact number of particles and then stop * your emitter afterwards. * * The counter is reset each time the `ParticleEmitter.start` method is called. * * 0 means the emitter will not stop based on total emitted particles. * * @name Phaser.GameObjects.Particles.ParticleEmitter#stopAfter * @type {number} * @default 0 * @since 3.60.0 */ this.stopAfter = 0; /** * The number of milliseconds this emitter will emit particles for when in flow mode, * before it stops emission. A value of 0 (the default) means there is no duration. * * When the duration expires the `STOP` event is emitted. Note that entering a * stopped state doesn't mean all the particles have finished, just that it's * not emitting any further ones. * * To know when the final particle expires, listen for the COMPLETE event. * * The counter is reset each time the `ParticleEmitter.start` method is called. * * 0 means the emitter will not stop based on duration. * * @name Phaser.GameObjects.Particles.ParticleEmitter#duration * @type {number} * @default 0 * @since 3.60.0 */ this.duration = 0; /** * For a flow emitter, the time interval (>= 0) between particle flow cycles in ms. * A value of 0 means there is one particle flow cycle for each logic update (the maximum flow frequency). This is the default setting. * For an exploding emitter, this value will be -1. * Calling {@link Phaser.GameObjects.Particles.ParticleEmitter#flow} also puts the emitter in flow mode (frequency >= 0). * Calling {@link Phaser.GameObjects.Particles.ParticleEmitter#explode} also puts the emitter in explode mode (frequency = -1). * * @name Phaser.GameObjects.Particles.ParticleEmitter#frequency * @type {number} * @default 0 * @since 3.0.0 * @see Phaser.GameObjects.Particles.ParticleEmitter#setFrequency */ this.frequency = 0; /** * Controls if the emitter is currently emitting a particle flow (when frequency >= 0). * * Already alive particles will continue to update until they expire. * * Controlled by {@link Phaser.GameObjects.Particles.ParticleEmitter#start} and {@link Phaser.GameObjects.Particles.ParticleEmitter#stop}. * * @name Phaser.GameObjects.Particles.ParticleEmitter#emitting * @type {boolean} * @default true * @since 3.0.0 */ this.emitting = true; /** * Newly emitted particles are added to the top of the particle list, i.e. rendered above those already alive. * * Set to false to send them to the back. * * Also see the `sortOrder` property for more complex particle sorting. * * @name Phaser.GameObjects.Particles.ParticleEmitter#particleBringToTop * @type {boolean} * @default true * @since 3.0.0 */ this.particleBringToTop = true; /** * The time rate applied to active particles, affecting lifespan, movement, and tweens. Values larger than 1 are faster than normal. * * @name Phaser.GameObjects.Particles.ParticleEmitter#timeScale * @type {number} * @default 1 * @since 3.0.0 */ this.timeScale = 1; /** * An array containing Particle Emission Zones. These can be either EdgeZones or RandomZones. * * Particles are emitted from a randomly selected zone from this array. * * Prior to Phaser v3.60 an Emitter could only have one single Emission Zone. * In 3.60 they can now have an array of Emission Zones. * * @name Phaser.GameObjects.Particles.ParticleEmitter#emitZones * @type {Phaser.GameObjects.Particles.Zones.EdgeZone[]|Phaser.GameObjects.Particles.Zones.RandomZone[]} * @since 3.60.0 * @see Phaser.GameObjects.Particles.ParticleEmitter#setEmitZone */ this.emitZones = []; /** * An array containing Particle Death Zone objects. A particle is immediately killed as soon as its x/y coordinates * intersect with any of the configured Death Zones. * * Prior to Phaser v3.60 an Emitter could only have one single Death Zone. * In 3.60 they can now have an array of Death Zones. * * @name Phaser.GameObjects.Particles.ParticleEmitter#deathZones * @type {Phaser.GameObjects.Particles.Zones.DeathZone[]} * @since 3.60.0 * @see Phaser.GameObjects.Particles.ParticleEmitter#setDeathZone */ this.deathZones = []; /** * An optional Rectangle object that is used during rendering to cull Particles from * display. For example, if your particles are limited to only move within a 300x300 * sized area from their origin, then you can set this Rectangle to those dimensions. * * The renderer will check to see if the `viewBounds` Rectangle intersects with the * Camera bounds during the render step and if not it will skip rendering the Emitter * entirely. * * This allows you to create many emitters in a Scene without the cost of * rendering if the contents aren't visible. * * Note that the Emitter will not perform any checks to see if the Particles themselves * are outside of these bounds, or not. It will simply check the bounds against the * camera. Use the `getBounds` method with the `advance` parameter to help define * the location and placement of the view bounds. * * @name Phaser.GameObjects.Particles.ParticleEmitter#viewBounds * @type {?Phaser.Geom.Rectangle} * @default null * @since 3.60.0 * @see Phaser.GameObjects.Particles.ParticleEmitter#setViewBounds */ this.viewBounds = null; /** * A Game Object whose position is used as the particle origin. * * @name Phaser.GameObjects.Particles.ParticleEmitter#follow * @type {?Phaser.GameObjects.GameObject} * @default null * @since 3.0.0 * @see Phaser.GameObjects.Particles.ParticleEmitter#startFollow * @see Phaser.GameObjects.Particles.ParticleEmitter#stopFollow */ this.follow = null; /** * The offset of the particle origin from the {@link Phaser.GameObjects.Particles.ParticleEmitter#follow} target. * * @name Phaser.GameObjects.Particles.ParticleEmitter#followOffset * @type {Phaser.Math.Vector2} * @since 3.0.0 * @see Phaser.GameObjects.Particles.ParticleEmitter#startFollow */ this.followOffset = new Vector2(); /** * Whether the emitter's {@link Phaser.GameObjects.Particles.ParticleEmitter#visible} state will track * the {@link Phaser.GameObjects.Particles.ParticleEmitter#follow} target's visibility state. * * @name Phaser.GameObjects.Particles.ParticleEmitter#trackVisible * @type {boolean} * @default false * @since 3.0.0 * @see Phaser.GameObjects.Particles.ParticleEmitter#startFollow */ this.trackVisible = false; /** * The texture frames assigned to particles. * * @name Phaser.GameObjects.Particles.ParticleEmitter#frames * @type {Phaser.Textures.Frame[]} * @since 3.0.0 */ this.frames = []; /** * Whether texture {@link Phaser.GameObjects.Particles.ParticleEmitter#frames} are selected at random. * * @name Phaser.GameObjects.Particles.ParticleEmitter#randomFrame * @type {boolean} * @default true * @since 3.0.0 * @see Phaser.GameObjects.Particles.ParticleEmitter#setEmitterFrame */ this.randomFrame = true; /** * The number of consecutive particles that receive a single texture frame (per frame cycle). * * @name Phaser.GameObjects.Particles.ParticleEmitter#frameQuantity * @type {number} * @default 1 * @since 3.0.0 * @see Phaser.GameObjects.Particles.ParticleEmitter#setEmitterFrame */ this.frameQuantity = 1; /** * The animations assigned to particles. * * @name Phaser.GameObjects.Particles.ParticleEmitter#anims * @type {string[]} * @since 3.60.0 */ this.anims = []; /** * Whether animations {@link Phaser.GameObjects.Particles.ParticleEmitter#anims} are selected at random. * * @name Phaser.GameObjects.Particles.ParticleEmitter#randomAnim * @type {boolean} * @default true * @since 3.60.0 * @see Phaser.GameObjects.Particles.ParticleEmitter#setAnim */ this.randomAnim = true; /** * The number of consecutive particles that receive a single animation (per frame cycle). * * @name Phaser.GameObjects.Particles.ParticleEmitter#animQuantity * @type {number} * @default 1 * @since 3.60.0 * @see Phaser.GameObjects.Particles.ParticleEmitter#setAnim */ this.animQuantity = 1; /** * An array containing all currently inactive Particle instances. * * @name Phaser.GameObjects.Particles.ParticleEmitter#dead * @type {Phaser.GameObjects.Particles.Particle[]} * @private * @since 3.0.0 */ this.dead = []; /** * An array containing all currently live and rendering Particle instances. * * @name Phaser.GameObjects.Particles.ParticleEmitter#alive * @type {Phaser.GameObjects.Particles.Particle[]} * @private * @since 3.0.0 */ this.alive = []; /** * Internal array that holds counter data: * * 0 - flowCounter - The time until next flow cycle. * 1 - frameCounter - Counts up to {@link Phaser.GameObjects.Particles.ParticleEmitter#frameQuantity}. * 2 - animCounter - Counts up to animQuantity. * 3 - elapsed - The time remaining until the `duration` limit is reached. * 4 - stopCounter - The number of particles remaining until `stopAfter` limit is reached. * 5 - completeFlag - Has the COMPLETE event been emitted? * 6 - zoneIndex - The emit zone index counter. * 7 - zoneTotal - The emit zone total counter. * 8 - currentFrame - The current texture frame, as an index of {@link Phaser.GameObjects.Particles.ParticleEmitter#frames}. * 9 - currentAnim - The current animation, as an index of {@link Phaser.GameObjects.Particles.ParticleEmitter#anims}. * * @name Phaser.GameObjects.Particles.ParticleEmitter#counters * @type {Float32Array} * @private * @since 3.60.0 */ this.counters = new Float32Array(10); /** * An internal property used to tell when the emitter is in fast-forwarc mode. * * @name Phaser.GameObjects.Particles.ParticleEmitter#skipping * @type {boolean} * @default true * @since 3.60.0 */ this.skipping = false; /** * An internal Transform Matrix used to cache this emitters world matrix. * * @name Phaser.GameObjects.Particles.ParticleEmitter#worldMatrix * @type {Phaser.GameObjects.Components.TransformMatrix} * @since 3.60.0 */ this.worldMatrix = new TransformMatrix(); /** * Optionally sort the particles before they render based on this * property. The property must exist on the `Particle` class, such * as `y`, `lifeT`, `scaleX`, etc. * * When set this overrides the `particleBringToTop` setting. * * To reset this and disable sorting, so this property to an empty string. * * @name Phaser.GameObjects.Particles.ParticleEmitter#sortProperty * @type {string} * @since 3.60.0 */ this.sortProperty = ''; /** * When `sortProperty` is defined this controls the sorting order, * either ascending or descending. Toggle to control the visual effect. * * @name Phaser.GameObjects.Particles.ParticleEmitter#sortOrderAsc * @type {boolean} * @since 3.60.0 */ this.sortOrderAsc = true; /** * The callback used to sort the particles. Only used if `sortProperty` * has been set. Set this via the `setSortCallback` method. * * @name Phaser.GameObjects.Particles.ParticleEmitter#sortCallback * @type {?Phaser.Types.GameObjects.Particles.ParticleSortCallback} * @since 3.60.0 */ this.sortCallback = this.depthSortCallback; /** * A list of Particle Processors being managed by this Emitter. * * @name Phaser.GameObjects.Particles.ParticleEmitter#processors * @type {Phaser.Structs.List.} * @since 3.60.0 */ this.processors = new List(this); /** * The tint fill mode used by the Particles in this Emitter. * * `false` = An additive tint (the default), where vertices colors are blended with the texture. * `true` = A fill tint, where the vertices colors replace the texture, but respects texture alpha. * * @name Phaser.GameObjects.Particles.ParticleEmitter#tintFill * @type {boolean} * @default false * @since 3.60.0 */ this.tintFill = false; this.initPipeline(); this.initPostPipeline(); this.setPosition(x, y); this.setTexture(texture); if (config) { this.setConfig(config); } }, // Overrides Game Object method addedToScene: function () { this.scene.sys.updateList.add(this); }, // Overrides Game Object method removedFromScene: function () { this.scene.sys.updateList.remove(this); }, /** * Takes an Emitter Configuration file and resets this Emitter, using any * properties defined in the config to then set it up again. * * @method Phaser.GameObjects.Particles.ParticleEmitter#setConfig * @since 3.60.0 * * @param {Phaser.Types.GameObjects.Particles.ParticleEmitterConfig} config - Settings for this emitter. * * @return {this} This Particle Emitter. */ setConfig: function (config) { if (!config) { return this; } var i = 0; var key = ''; var ops = this.ops; for (i = 0; i < configOpMap.length; i++) { key = configOpMap[i]; ops[key].loadConfig(config); } for (i = 0; i < configFastMap.length; i++) { key = configFastMap[i]; // Only update properties from their current state if they exist in the given config if (HasValue(config, key)) { this[key] = GetFastValue(config, key); } } this.acceleration = (this.accelerationX !== 0 || this.accelerationY !== 0); this.moveTo = (this.moveToX !== 0 && this.moveToY !== 0); // Special 'speed' override if (HasValue(config, 'speed')) { ops.speedX.loadConfig(config, 'speed'); ops.speedY.active = false; } // If you specify speedX, speedY or moveTo then it changes the emitter from radial to a point emitter if (HasAny(config, [ 'speedX', 'speedY' ]) || this.moveTo) { this.radial = false; } // Special 'scale' override if (HasValue(config, 'scale')) { ops.scaleX.loadConfig(config, 'scale'); ops.scaleY.active = false; } if (HasValue(config, 'callbackScope')) { var callbackScope = GetFastValue(config, 'callbackScope', null); this.emitCallbackScope = callbackScope; this.deathCallbackScope = callbackScope; } if (HasValue(config, 'emitZone')) { this.addEmitZone(config.emitZone); } if (HasValue(config, 'deathZone')) { this.addDeathZone(config.deathZone); } if (HasValue(config, 'bounds')) { var bounds = this.addParticleBounds(config.bounds); bounds.collideLeft = GetFastValue(config, 'collideLeft', true); bounds.collideRight = GetFastValue(config, 'collideRight', true); bounds.collideTop = GetFastValue(config, 'collideTop', true); bounds.collideBottom = GetFastValue(config, 'collideBottom', true); } if (HasValue(config, 'followOffset')) { this.followOffset.setFromObject(GetFastValue(config, 'followOffset', 0)); } if (HasValue(config, 'texture')) { this.setTexture(config.texture); } if (HasValue(config, 'frame')) { this.setEmitterFrame(config.frame); } else if (HasValue(config, 'anim')) { this.setAnim(config.anim); } if (HasValue(config, 'reserve')) { this.reserve(config.reserve); } if (HasValue(config, 'advance')) { this.fastForward(config.advance); } this.resetCounters(this.frequency, this.emitting); if (this.emitting) { this.emit(Events.START, this); } return this; }, /** * Creates a description of this emitter suitable for JSON serialization. * * @method Phaser.GameObjects.Particles.ParticleEmitter#toJSON * @since 3.0.0 * * @return {Phaser.Types.GameObjects.JSONGameObject} A JSON representation of the Game Object. */ toJSON: function () { var output = ComponentsToJSON(this); var i = 0; var key = ''; for (i = 0; i < configFastMap.length; i++) { key = configFastMap[i]; output[key] = this[key]; } var ops = this.ops; for (i = 0; i < configOpMap.length; i++) { key = configOpMap[i]; if (ops[key]) { output[key] = ops[key].toJSON(); } } // special handlers if (!ops.speedY.active) { delete output.speedX; output.speed = ops.speedX.toJSON(); } if (this.scaleX === this.scaleY) { delete output.scaleX; delete output.scaleY; output.scale = ops.scaleX.toJSON(); } return output; }, /** * Resets the internal counter trackers. * * You shouldn't ever need to call this directly. * * @method Phaser.GameObjects.Particles.ParticleEmitter#resetCounters * @since 3.60.0 * * @param {number} frequency - The frequency counter. * @param {boolean} on - Set the complete flag. */ resetCounters: function (frequency, on) { var counters = this.counters; counters.fill(0); counters[0] = frequency; if (on) { counters[5] = 1; } }, /** * Continuously moves the particle origin to follow a Game Object's position. * * @method Phaser.GameObjects.Particles.ParticleEmitter#startFollow * @since 3.0.0 * * @param {Phaser.GameObjects.GameObject} target - The Game Object to follow. * @param {number} [offsetX=0] - Horizontal offset of the particle origin from the Game Object. * @param {number} [offsetY=0] - Vertical offset of the particle origin from the Game Object. * @param {boolean} [trackVisible=false] - Whether the emitter's visible state will track the target's visible state. * * @return {this} This Particle Emitter. */ startFollow: function (target, offsetX, offsetY, trackVisible) { if (offsetX === undefined) { offsetX = 0; } if (offsetY === undefined) { offsetY = 0; } if (trackVisible === undefined) { trackVisible = false; } this.follow = target; this.followOffset.set(offsetX, offsetY); this.trackVisible = trackVisible; return this; }, /** * Stops following a Game Object. * * @method Phaser.GameObjects.Particles.ParticleEmitter#stopFollow * @since 3.0.0 * * @return {this} This Particle Emitter. */ stopFollow: function () { this.follow = null; this.followOffset.set(0, 0); this.trackVisible = false; return this; }, /** * Chooses a texture frame from {@link Phaser.GameObjects.Particles.ParticleEmitter#frames}. * * @method Phaser.GameObjects.Particles.ParticleEmitter#getFrame * @since 3.0.0 * * @return {Phaser.Textures.Frame} The texture frame. */ getFrame: function () { var frames = this.frames; var len = frames.length; var current; if (len === 1) { current = frames[0]; } else if (this.randomFrame) { current = GetRandom(frames); } else { current = frames[this.currentFrame]; this.frameCounter++; if (this.frameCounter === this.frameQuantity) { this.frameCounter = 0; this.currentFrame++; if (this.currentFrame === len) { this.currentFrame = 0; } } } return this.texture.get(current); }, /** * Sets a pattern for assigning texture frames to emitted particles. The `frames` configuration can be any of: * * frame: 0 * frame: 'red' * frame: [ 0, 1, 2, 3 ] * frame: [ 'red', 'green', 'blue', 'pink', 'white' ] * frame: { frames: [ 'red', 'green', 'blue', 'pink', 'white' ], [cycle: bool], [quantity: int] } * * @method Phaser.GameObjects.Particles.ParticleEmitter#setEmitterFrame * @since 3.0.0 * * @param {(array|string|number|Phaser.Types.GameObjects.Particles.ParticleEmitterFrameConfig)} frames - One or more texture frames, or a configuration object. * @param {boolean} [pickRandom=true] - Whether frames should be assigned at random from `frames`. * @param {number} [quantity=1] - The number of consecutive particles that will receive each frame. * * @return {this} This Particle Emitter. */ setEmitterFrame: function (frames, pickRandom, quantity) { if (pickRandom === undefined) { pickRandom = true; } if (quantity === undefined) { quantity = 1; } this.randomFrame = pickRandom; this.frameQuantity = quantity; this.currentFrame = 0; var t = typeof (frames); this.frames.length = 0; if (Array.isArray(frames)) { this.frames = this.frames.concat(frames); } else if (t === 'string' || t === 'number') { this.frames.push(frames); } else if (t === 'object') { var frameConfig = frames; frames = GetFastValue(frameConfig, 'frames', null); if (frames) { this.frames = this.frames.concat(frames); } var isCycle = GetFastValue(frameConfig, 'cycle', false); this.randomFrame = (isCycle) ? false : true; this.frameQuantity = GetFastValue(frameConfig, 'quantity', quantity); } if (this.frames.length === 1) { this.frameQuantity = 1; this.randomFrame = false; } return this; }, /** * Chooses an animation from {@link Phaser.GameObjects.Particles.ParticleEmitter#anims}, if populated. * * @method Phaser.GameObjects.Particles.ParticleEmitter#getAnim * @since 3.60.0 * * @return {string} The animation to play, or `null` if there aren't any. */ getAnim: function () { var anims = this.anims; var len = anims.length; if (len === 0) { return null; } else if (len === 1) { return anims[0]; } else if (this.randomAnim) { return GetRandom(anims); } else { var anim = anims[this.currentAnim]; this.animCounter++; if (this.animCounter >= this.animQuantity) { this.animCounter = 0; this.currentAnim = Wrap(this.currentAnim + 1, 0, len - 1); } return anim; } }, /** * Sets a pattern for assigning animations to emitted particles. The `anims` configuration can be any of: * * anim: 'red' * anim: [ 'red', 'green', 'blue', 'pink', 'white' ] * anim: { anims: [ 'red', 'green', 'blue', 'pink', 'white' ], [cycle: bool], [quantity: int] } * * @method Phaser.GameObjects.Particles.ParticleEmitter#setAnim * @since 3.60.0 * * @param {(string|string[]|Phaser.Types.GameObjects.Particles.ParticleEmitterAnimConfig)} anims - One or more animations, or a configuration object. * @param {boolean} [pickRandom=true] - Whether animations should be assigned at random from `anims`. * @param {number} [quantity=1] - The number of consecutive particles that will receive each animation. * * @return {this} This Particle Emitter. */ setAnim: function (anims, pickRandom, quantity) { if (pickRandom === undefined) { pickRandom = true; } if (quantity === undefined) { quantity = 1; } this.randomAnim = pickRandom; this.animQuantity = quantity; this.currentAnim = 0; var t = typeof (anims); this.anims.length = 0; if (Array.isArray(anims)) { this.anims = this.anims.concat(anims); } else if (t === 'string') { this.anims.push(anims); } else if (t === 'object') { var animConfig = anims; anims = GetFastValue(animConfig, 'anims', null); if (anims) { this.anims = this.anims.concat(anims); } var isCycle = GetFastValue(animConfig, 'cycle', false); this.randomAnim = (isCycle) ? false : true; this.animQuantity = GetFastValue(animConfig, 'quantity', quantity); } if (this.anims.length === 1) { this.animQuantity = 1; this.randomAnim = false; } return this; }, /** * Turns {@link Phaser.GameObjects.Particles.ParticleEmitter#radial} particle movement on or off. * * @method Phaser.GameObjects.Particles.ParticleEmitter#setRadial * @since 3.0.0 * * @param {boolean} [value=true] - Radial mode (true) or point mode (true). * * @return {this} This Particle Emitter. */ setRadial: function (value) { if (value === undefined) { value = true; } this.radial = value; return this; }, /** * Creates a Particle Bounds processor and adds it to this Emitter. * * This processor will check to see if any of the active Particles hit * the defined boundary, as specified by a Rectangle shape in world-space. * * If so, they are 'rebounded' back again by having their velocity adjusted. * * The strength of the rebound is controlled by the `Particle.bounce` * property. * * You should be careful to ensure that you emit particles within a bounds, * if set, otherwise it will lead to unpredictable visual results as the * particles are hastily repositioned. * * The Particle Bounds processor is returned from this method. If you wish * to modify the area you can directly change its `bounds` property, along * with the `collideLeft` etc values. * * To disable the bounds you can either set its `active` property to `false`, * or if you no longer require it, call `ParticleEmitter.removeParticleProcessor`. * * @method Phaser.GameObjects.Particles.ParticleEmitter#addParticleBounds * @since 3.60.0 * * @param {(number|Phaser.Types.GameObjects.Particles.ParticleEmitterBounds|Phaser.Types.GameObjects.Particles.ParticleEmitterBoundsAlt)} x - The x-coordinate of the left edge of the boundary, or an object representing a rectangle. * @param {number} [y] - The y-coordinate of the top edge of the boundary. * @param {number} [width] - The width of the boundary. * @param {number} [height] - The height of the boundary. * @param {boolean} [collideLeft=true] - Whether particles interact with the left edge of the bounds. * @param {boolean} [collideRight=true] - Whether particles interact with the right edge of the bounds. * @param {boolean} [collideTop=true] - Whether particles interact with the top edge of the bounds. * @param {boolean} [collideBottom=true] - Whether particles interact with the bottom edge of the bounds. * * @return {Phaser.GameObjects.Particles.ParticleBounds} The Particle Bounds processor. */ addParticleBounds: function (x, y, width, height, collideLeft, collideRight, collideTop, collideBottom) { if (typeof x === 'object') { var obj = x; x = obj.x; y = obj.y; width = (HasValue(obj, 'w')) ? obj.w : obj.width; height = (HasValue(obj, 'h')) ? obj.h : obj.height; } return this.addParticleProcessor(new ParticleBounds(x, y, width, height, collideLeft, collideRight, collideTop, collideBottom)); }, /** * Sets the initial radial speed of emitted particles. * * Changes the emitter to radial mode. * * @method Phaser.GameObjects.Particles.ParticleEmitter#setParticleSpeed * @since 3.60.0 * * @param {number} x - The horizontal speed of the emitted Particles. * @param {number} [y=x] - The vertical speed of emitted Particles. If not set it will use the `x` value. * * @return {this} This Particle Emitter. */ setParticleSpeed: function (x, y) { if (y === undefined) { y = x; } this.ops.speedX.onChange(x); if (x === y) { this.ops.speedY.active = false; } else { this.ops.speedY.onChange(y); } // If you specify speedX and Y then it changes the emitter from radial to a point emitter this.radial = true; return this; }, /** * Sets the vertical and horizontal scale of the emitted particles. * * You can also set the scale of the entire emitter via `setScale`. * * @method Phaser.GameObjects.Particles.ParticleEmitter#setParticleScale * @since 3.60.0 * * @param {number} [x=1] - The horizontal scale of the emitted Particles. * @param {number} [y=x] - The vertical scale of emitted Particles. If not set it will use the `x` value. * * @return {this} This Particle Emitter. */ setParticleScale: function (x, y) { if (x === undefined) { x = 1; } if (y === undefined) { y = x; } this.ops.scaleX.onChange(x); this.ops.scaleY.onChange(y); return this; }, /** * Sets the gravity applied to emitted particles. * * @method Phaser.GameObjects.Particles.ParticleEmitter#setParticleGravity * @since 3.60.0 * * @param {number} x - Horizontal acceleration due to gravity, in pixels per second squared. Set to zero for no gravity. * @param {number} y - Vertical acceleration due to gravity, in pixels per second squared. Set to zero for no gravity. * * @return {this} This Particle Emitter. */ setParticleGravity: function (x, y) { this.gravityX = x; this.gravityY = y; return this; }, /** * Sets the opacity (alpha) of emitted particles. * * You can also set the alpha of the entire emitter via `setAlpha`. * * @method Phaser.GameObjects.Particles.ParticleEmitter#setParticleAlpha * @since 3.60.0 * * @param {(Phaser.Types.GameObjects.Particles.EmitterOpOnEmitType|Phaser.Types.GameObjects.Particles.EmitterOpOnUpdateType)} value - A value between 0 (transparent) and 1 (opaque). * * @return {this} This Particle Emitter. */ setParticleAlpha: function (value) { this.ops.alpha.onChange(value); return this; }, /** * Sets the color tint of emitted particles. * * This is a WebGL only feature. * * @method Phaser.GameObjects.Particles.ParticleEmitter#setParticleTint * @since 3.60.0 * @webglOnly * * @param {(Phaser.Types.GameObjects.Particles.EmitterOpOnEmitType|Phaser.Types.GameObjects.Particles.EmitterOpOnUpdateType)} value - A value between 0 and 0xffffff. * * @return {this} This Particle Emitter. */ setParticleTint: function (value) { this.ops.tint.onChange(value); return this; }, /** * Sets the angle of a {@link Phaser.GameObjects.Particles.ParticleEmitter#radial} particle stream. * * The value is given in degrees using Phaser's right-handed coordinate system. * * @method Phaser.GameObjects.Particles.ParticleEmitter#setEmitterAngle * @since 3.0.0 * * @param {Phaser.Types.GameObjects.Particles.EmitterOpOnEmitType} value - The angle of the initial velocity of emitted particles, in degrees. * * @return {this} This Particle Emitter. */ setEmitterAngle: function (value) { this.ops.angle.onChange(value); return this; }, /** * Sets the lifespan of newly emitted particles in milliseconds. * * @method Phaser.GameObjects.Particles.ParticleEmitter#setParticleLifespan * @since 3.60.0 * * @param {Phaser.Types.GameObjects.Particles.EmitterOpOnEmitType} value - The lifespan of a particle, in ms. * * @return {this} This Particle Emitter. */ setParticleLifespan: function (value) { this.ops.lifespan.onChange(value); return this; }, /** * Sets the number of particles released at each flow cycle or explosion. * * @method Phaser.GameObjects.Particles.ParticleEmitter#setQuantity * @since 3.0.0 * * @param {Phaser.Types.GameObjects.Particles.EmitterOpOnEmitType} quantity - The number of particles to release at each flow cycle or explosion. * * @return {this} This Particle Emitter. */ setQuantity: function (quantity) { this.quantity = quantity; return this; }, /** * Sets the emitter's {@link Phaser.GameObjects.Particles.ParticleEmitter#frequency} * and {@link Phaser.GameObjects.Particles.ParticleEmitter#quantity}. * * @method Phaser.GameObjects.Particles.ParticleEmitter#setFrequency * @since 3.0.0 * * @param {number} frequency - The time interval (>= 0) of each flow cycle, in ms; or -1 to put the emitter in explosion mode. * @param {Phaser.Types.GameObjects.Particles.EmitterOpOnEmitType} [quantity] - The number of particles to release at each flow cycle or explosion. * * @return {this} This Particle Emitter. */ setFrequency: function (frequency, quantity) { this.frequency = frequency; this.flowCounter = (frequency > 0) ? frequency : 0; if (quantity) { this.quantity = quantity; } return this; }, /** * Adds a new Particle Death Zone to this Emitter. * * A particle is immediately killed as soon as its x/y coordinates intersect * with any of the configured Death Zones. * * The `source` can be a Geometry Shape, such as a Circle, Rectangle or Triangle. * Any valid object from the `Phaser.Geometry` namespace is allowed, as long as * it supports a `contains` function. You can set the `type` to be either `onEnter` * or `onLeave`. * * A single Death Zone instance can only exist once within this Emitter, but can belong * to multiple Emitters. * * @method Phaser.GameObjects.Particles.ParticleEmitter#addDeathZone * @since 3.60.0 * * @param {Phaser.Types.GameObjects.Particles.DeathZoneObject|Phaser.Types.GameObjects.Particles.DeathZoneObject[]} config - A Death Zone configuration object, a Death Zone instance, a valid Geometry object or an array of them. * * @return {Phaser.GameObjects.Particles.Zones.DeathZone} The Death Zone that was added to this Emitter. */ addDeathZone: function (config) { if (!Array.isArray(config)) { config = [ config ]; } var zone; var deathZones = this.deathZones; for (var i = 0; i < config.length; i++) { zone = config[i]; if (zone instanceof DeathZone) { deathZones.push(zone); } else if (typeof zone.contains === 'function') { zone = new DeathZone(zone, true); deathZones.push(zone); } else { var type = GetFastValue(zone, 'type', 'onEnter'); var source = GetFastValue(zone, 'source', null); if (source && typeof source.contains === 'function') { var killOnEnter = (type === 'onEnter') ? true : false; zone = new DeathZone(source, killOnEnter); deathZones.push(zone); } } } return zone; }, /** * Removes the given Particle Death Zone from this Emitter. * * @method Phaser.GameObjects.Particles.ParticleEmitter#removeDeathZone * @since 3.60.0 * * @param {Phaser.GameObjects.Particles.Zones.DeathZone} zone - The Death Zone that should be removed from this Emitter. * * @return {this} This Particle Emitter. */ removeDeathZone: function (zone) { Remove(this.deathZones, zone); return this; }, /** * Clear all Death Zones from this Particle Emitter. * * @method Phaser.GameObjects.Particles.ParticleEmitter#clearDeathZones * @since 3.61.0 * * @return {this} This Particle Emitter. */ clearDeathZones: function () { this.deathZones.length = 0; return this; }, /** * Adds a new Particle Emission Zone to this Emitter. * * An {@link Phaser.Types.GameObjects.Particles.ParticleEmitterEdgeZoneConfig EdgeZone} places particles on its edges. * Its {@link Phaser.Types.GameObjects.Particles.EdgeZoneSource source} can be a Curve, Path, Circle, Ellipse, Line, Polygon, Rectangle, or Triangle; * or any object with a suitable {@link Phaser.Types.GameObjects.Particles.EdgeZoneSourceCallback getPoints} method. * * A {@link Phaser.Types.GameObjects.Particles.ParticleEmitterRandomZoneConfig RandomZone} places the particles randomly within its interior. * Its {@link RandomZoneSource source} can be a Circle, Ellipse, Line, Polygon, Rectangle, or Triangle; or any object with a suitable {@link Phaser.Types.GameObjects.Particles.RandomZoneSourceCallback getRandomPoint} method. * * An Emission Zone can only exist once within this Emitter. * * @method Phaser.GameObjects.Particles.ParticleEmitter#addEmitZone * @since 3.60.0 * * @param {Phaser.Types.GameObjects.Particles.EmitZoneObject|Phaser.Types.GameObjects.Particles.EmitZoneObject[]} zone - An Emission Zone configuration object, a RandomZone or EdgeZone instance, or an array of them. * * @return {Phaser.GameObjects.Particles.Zones.EdgeZone[]|Phaser.GameObjects.Particles.Zones.RandomZone[]} An array of the Emission Zones that were added to this Emitter. */ addEmitZone: function (config) { if (!Array.isArray(config)) { config = [ config ]; } var zone; var emitZones = this.emitZones; var output = []; for (var i = 0; i < config.length; i++) { zone = config[i]; if (zone instanceof RandomZone || zone instanceof EdgeZone) { emitZones.push(zone); } else { // Where source = Geom like Circle, or a Path or Curve // emitZone: { type: 'random', source: X } // emitZone: { type: 'edge', source: X, quantity: 32, [stepRate=0], [yoyo=false], [seamless=true], [total=1] } var type = GetFastValue(zone, 'type', 'random'); var source = GetFastValue(zone, 'source', null); if (type === 'random') { zone = new RandomZone(source); } else if (type === 'edge') { var quantity = GetFastValue(zone, 'quantity', 1); var stepRate = GetFastValue(zone, 'stepRate', 0); var yoyo = GetFastValue(zone, 'yoyo', false); var seamless = GetFastValue(zone, 'seamless', true); var total = GetFastValue(zone, 'total', -1); zone = new EdgeZone(source, quantity, stepRate, yoyo, seamless, total); } if (zone) { emitZones.push(zone); } } output.push(zone); } return output; }, /** * Removes the given Particle Emission Zone from this Emitter. * * @method Phaser.GameObjects.Particles.ParticleEmitter#removeEmitZone * @since 3.60.0 * * @param {Phaser.GameObjects.Particles.Zones.EdgeZone|Phaser.GameObjects.Particles.Zones.RandomZone} zone - The Emission Zone that should be removed from this Emitter. * * @return {this} This Particle Emitter. */ removeEmitZone: function (zone) { Remove(this.emitZones, zone); this.zoneIndex = 0; return this; }, /** * Clear all Emission Zones from this Particle Emitter. * * @method Phaser.GameObjects.Particles.ParticleEmitter#clearEmitZones * @since 3.61.0 * * @return {this} This Particle Emitter. */ clearEmitZones: function () { this.emitZones.length = 0; this.zoneIndex = 0; return this; }, /** * Takes the given particle and sets its x/y coordinates to match the next available * emission zone, if any have been configured. This method is called automatically * as part of the `Particle.fire` process. * * The Emit Zones are iterated in sequence. Once a zone has had a particle emitted * from it, then the next zone is used and so on, in a loop. * * @method Phaser.GameObjects.Particles.ParticleEmitter#getEmitZone * @since 3.60.0 * * @param {Phaser.GameObjects.Particles.Particle} particle - The particle to set the emission zone for. */ getEmitZone: function (particle) { var zones = this.emitZones; var len = zones.length; if (len === 0) { return; } else { var zone = zones[this.zoneIndex]; zone.getPoint(particle); if (zone.total > -1) { this.zoneTotal++; if (this.zoneTotal === zone.total) { this.zoneTotal = 0; this.zoneIndex++; if (this.zoneIndex === len) { this.zoneIndex = 0; } } } } }, /** * Takes the given particle and checks to see if any of the configured Death Zones * will kill it and returns the result. This method is called automatically as part * of the `Particle.update` process. * * @method Phaser.GameObjects.Particles.ParticleEmitter#getDeathZone * @fires Phaser.GameObjects.Particles.Events#DEATH_ZONE * @since 3.60.0 * * @param {Phaser.GameObjects.Particles.Particle} particle - The particle to test against the Death Zones. * * @return {boolean} `true` if the particle should be killed, otherwise `false`. */ getDeathZone: function (particle) { var zones = this.deathZones; for (var i = 0; i < zones.length; i++) { var zone = zones[i]; if (zone.willKill(particle)) { this.emit(Events.DEATH_ZONE, this, particle, zone); return true; } } return false; }, /** * Changes the currently active Emission Zone. The zones should have already * been added to this Emitter either via the emitter config, or the * `addEmitZone` method. * * Call this method by passing either a numeric zone index value, or * the zone instance itself. * * Prior to v3.60 an Emitter could only have a single Emit Zone and this * method was how you set it. From 3.60 and up it now performs a different * function and swaps between all available active zones. * * @method Phaser.GameObjects.Particles.ParticleEmitter#setEmitZone * @since 3.0.0 * * @param {number|Phaser.GameObjects.Particles.Zones.EdgeZone|Phaser.GameObjects.Particles.Zones.RandomZone} zone - The Emit Zone to set as the active zone. * * @return {this} This Particle Emitter. */ setEmitZone: function (zone) { var index; if (isFinite(zone)) { index = zone; } else { index = this.emitZones.indexOf(zone); } if (index >= 0) { this.zoneIndex = index; } return this; }, /** * Adds a Particle Processor, such as a Gravity Well, to this Emitter. * * It will start processing particles from the next update as long as its `active` * property is set. * * @method Phaser.GameObjects.Particles.ParticleEmitter#addParticleProcessor * @since 3.60.0 * * @generic {Phaser.GameObjects.Particles.ParticleProcessor} T * @param {T} processor - The Particle Processor to add to this Emitter Manager. * * @return {T} The Particle Processor that was added to this Emitter Manager. */ addParticleProcessor: function (processor) { if (!this.processors.exists(processor)) { if (processor.emitter) { processor.emitter.removeParticleProcessor(processor); } this.processors.add(processor); processor.emitter = this; } return processor; }, /** * Removes a Particle Processor from this Emitter. * * The Processor must belong to this Emitter to be removed. * * It is not destroyed when removed, allowing you to move it to another Emitter Manager, * so if you no longer require it you should call its `destroy` method directly. * * @method Phaser.GameObjects.Particles.ParticleEmitter#removeParticleProcessor * @since 3.60.0 * * @generic {Phaser.GameObjects.Particles.ParticleProcessor} T * @param {T} processor - The Particle Processor to remove from this Emitter Manager. * * @return {?T} The Particle Processor that was removed, or null if it could not be found. */ removeParticleProcessor: function (processor) { if (this.processors.exists(processor)) { this.processors.remove(processor, true); processor.emitter = null; } return processor; }, /** * Gets all active Particle Processors. * * @method Phaser.GameObjects.Particles.ParticleEmitter#getProcessors * @since 3.60.0 * * @return {Phaser.GameObjects.Particles.ParticleProcessor[]} - An array of active Particle Processors. */ getProcessors: function () { return this.processors.getAll('active', true); }, /** * Creates a new Gravity Well, adds it to this Emitter and returns a reference to it. * * @method Phaser.GameObjects.Particles.ParticleEmitter#createGravityWell * @since 3.60.0 * * @param {Phaser.Types.GameObjects.Particles.GravityWellConfig} config - Configuration settings for the Gravity Well to create. * * @return {Phaser.GameObjects.Particles.GravityWell} The Gravity Well that was created. */ createGravityWell: function (config) { return this.addParticleProcessor(new GravityWell(config)); }, /** * Creates inactive particles and adds them to this emitter's pool. * * If `ParticleEmitter.maxParticles` is set it will limit the * value passed to this method to make sure it's not exceeded. * * @method Phaser.GameObjects.Particles.ParticleEmitter#reserve * @since 3.0.0 * * @param {number} count - The number of particles to create. * * @return {this} This Particle Emitter. */ reserve: function (count) { var dead = this.dead; if (this.maxParticles > 0) { var total = this.getParticleCount(); if (total + count > this.maxParticles) { count = this.maxParticles - (total + count); } } for (var i = 0; i < count; i++) { dead.push(new this.particleClass(this)); } return this; }, /** * Gets the number of active (in-use) particles in this emitter. * * @method Phaser.GameObjects.Particles.ParticleEmitter#getAliveParticleCount * @since 3.0.0 * * @return {number} The number of particles with `active=true`. */ getAliveParticleCount: function () { return this.alive.length; }, /** * Gets the number of inactive (available) particles in this emitter. * * @method Phaser.GameObjects.Particles.ParticleEmitter#getDeadParticleCount * @since 3.0.0 * * @return {number} The number of particles with `active=false`. */ getDeadParticleCount: function () { return this.dead.length; }, /** * Gets the total number of particles in this emitter. * * @method Phaser.GameObjects.Particles.ParticleEmitter#getParticleCount * @since 3.0.0 * * @return {number} The number of particles, including both alive and dead. */ getParticleCount: function () { return this.getAliveParticleCount() + this.getDeadParticleCount(); }, /** * Whether this emitter is at either its hard-cap limit (maxParticles), if set, or * the max allowed number of 'alive' particles (maxAliveParticles). * * @method Phaser.GameObjects.Particles.ParticleEmitter#atLimit * @since 3.0.0 * * @return {boolean} Returns `true` if this Emitter is at its limit, or `false` if no limit, or below the `maxParticles` level. */ atLimit: function () { if (this.maxParticles > 0 && this.getParticleCount() >= this.maxParticles) { return true; } return (this.maxAliveParticles > 0 && this.getAliveParticleCount() >= this.maxAliveParticles); }, /** * Sets a function to call for each newly emitted particle. * * @method Phaser.GameObjects.Particles.ParticleEmitter#onParticleEmit * @since 3.0.0 * * @param {Phaser.Types.GameObjects.Particles.ParticleEmitterCallback} callback - The function. * @param {*} [context] - The calling context. * * @return {this} This Particle Emitter. */ onParticleEmit: function (callback, context) { if (callback === undefined) { // Clear any previously set callback this.emitCallback = null; this.emitCallbackScope = null; } else if (typeof callback === 'function') { this.emitCallback = callback; if (context) { this.emitCallbackScope = context; } } return this; }, /** * Sets a function to call for each particle death. * * @method Phaser.GameObjects.Particles.ParticleEmitter#onParticleDeath * @since 3.0.0 * * @param {Phaser.Types.GameObjects.Particles.ParticleDeathCallback} callback - The function. * @param {*} [context] - The function's calling context. * * @return {this} This Particle Emitter. */ onParticleDeath: function (callback, context) { if (callback === undefined) { // Clear any previously set callback this.deathCallback = null; this.deathCallbackScope = null; } else if (typeof callback === 'function') { this.deathCallback = callback; if (context) { this.deathCallbackScope = context; } } return this; }, /** * Deactivates every particle in this emitter immediately. * * This particles are killed but do not emit an event or callback. * * @method Phaser.GameObjects.Particles.ParticleEmitter#killAll * @since 3.0.0 * * @return {this} This Particle Emitter. */ killAll: function () { var dead = this.dead; var alive = this.alive; while (alive.length > 0) { dead.push(alive.pop()); } return this; }, /** * Calls a function for each active particle in this emitter. The function is * sent two parameters: a reference to the Particle instance and to this Emitter. * * @method Phaser.GameObjects.Particles.ParticleEmitter#forEachAlive * @since 3.0.0 * * @param {Phaser.Types.GameObjects.Particles.ParticleEmitterCallback} callback - The function. * @param {*} context - The functions calling context. * * @return {this} This Particle Emitter. */ forEachAlive: function (callback, context) { var alive = this.alive; var length = alive.length; for (var i = 0; i < length; i++) { // Sends the Particle and the Emitter callback.call(context, alive[i], this); } return this; }, /** * Calls a function for each inactive particle in this emitter. * * @method Phaser.GameObjects.Particles.ParticleEmitter#forEachDead * @since 3.0.0 * * @param {Phaser.Types.GameObjects.Particles.ParticleEmitterCallback} callback - The function. * @param {*} context - The functions calling context. * * @return {this} This Particle Emitter. */ forEachDead: function (callback, context) { var dead = this.dead; var length = dead.length; for (var i = 0; i < length; i++) { callback.call(context, dead[i], this); } return this; }, /** * Turns {@link Phaser.GameObjects.Particles.ParticleEmitter#on} the emitter and resets the flow counter. * * If this emitter is in flow mode (frequency >= 0; the default), the particle flow will start (or restart). * * If this emitter is in explode mode (frequency = -1), nothing will happen. * Use {@link Phaser.GameObjects.Particles.ParticleEmitter#explode} or {@link Phaser.GameObjects.Particles.ParticleEmitter#flow} instead. * * Calling this method will emit the `START` event. * * @method Phaser.GameObjects.Particles.ParticleEmitter#start * @fires Phaser.GameObjects.Particles.Events#START * @since 3.0.0 * * @param {number} [advance=0] - Advance this number of ms in time through the emitter. * @param {number} [duration=0] - Limit this emitter to only emit particles for the given number of ms. Setting this parameter will override any duration already set in the Emitter configuration object. * * @return {this} This Particle Emitter. */ start: function (advance, duration) { if (advance === undefined) { advance = 0; } if (!this.emitting) { if (advance > 0) { this.fastForward(advance); } this.emitting = true; this.resetCounters(this.frequency, true); if (duration !== undefined) { this.duration = Math.abs(duration); } this.emit(Events.START, this); } return this; }, /** * Turns {@link Phaser.GameObjects.Particles.ParticleEmitter#emitting off} the emitter and * stops it from emitting further particles. Currently alive particles will remain * active until they naturally expire unless you set the `kill` parameter to `true`. * * Calling this method will emit the `STOP` event. When the final particle has * expired the `COMPLETE` event will be emitted. * * @method Phaser.GameObjects.Particles.ParticleEmitter#stop * @fires Phaser.GameObjects.Particles.Events#STOP * @since 3.11.0 * * @param {boolean} [kill=false] - Kill all particles immediately (true), or leave them to die after their lifespan expires? (false, the default) * * @return {this} This Particle Emitter. */ stop: function (kill) { if (kill === undefined) { kill = false; } if (this.emitting) { this.emitting = false; if (kill) { this.killAll(); } this.emit(Events.STOP, this); } return this; }, /** * {@link Phaser.GameObjects.Particles.ParticleEmitter#active Deactivates} the emitter. * * @method Phaser.GameObjects.Particles.ParticleEmitter#pause * @since 3.0.0 * * @return {this} This Particle Emitter. */ pause: function () { this.active = false; return this; }, /** * {@link Phaser.GameObjects.Particles.ParticleEmitter#active Activates} the emitter. * * @method Phaser.GameObjects.Particles.ParticleEmitter#resume * @since 3.0.0 * * @return {this} This Particle Emitter. */ resume: function () { this.active = true; return this; }, /** * Set the property by which active particles are sorted prior to be rendered. * * It allows you to control the rendering order of the particles. * * This can be any valid property of the `Particle` class, such as `y`, `alpha` * or `lifeT`. * * The 'alive' particles array is sorted in place each game frame. Setting a * sort property will override the `particleBringToTop` setting. * * If you wish to use your own sorting function, see `setSortCallback` instead. * * @method Phaser.GameObjects.Particles.ParticleEmitter#setSortProperty * @since 3.60.0 * * @param {string} [property] - The property on the `Particle` class to sort by. * @param {boolean} [ascending=true] - Should the particles be sorted in ascending or descending order? * * @return {this} This Particle Emitter. */ setSortProperty: function (property, ascending) { if (property === undefined) { property = ''; } if (ascending === undefined) { ascending = this.true; } this.sortProperty = property; this.sortOrderAsc = ascending; this.sortCallback = this.depthSortCallback; return this; }, /** * Sets a callback to be used to sort the particles before rendering each frame. * * This allows you to define your own logic and behavior in the callback. * * The callback will be sent two parameters: the two Particles being compared, * and must adhere to the criteria of the `compareFn` in `Array.sort`: * * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#description * * Call this method with no parameters to reset the sort callback. * * Setting your own callback will override both the `particleBringToTop` and * `sortProperty` settings of this Emitter. * * @method Phaser.GameObjects.Particles.ParticleEmitter#setSortCallback * @since 3.60.0 * * @param {Phaser.Types.GameObjects.Particles.ParticleSortCallback} [callback] - The callback to invoke when the particles are sorted. Leave undefined to reset to the default. * * @return {this} This Particle Emitter. */ setSortCallback: function (callback) { if (this.sortProperty !== '') { callback = this.depthSortCallback; } else { callback = null; } this.sortCallback = callback; return this; }, /** * Sorts active particles with {@link Phaser.GameObjects.Particles.ParticleEmitter#depthSortCallback}. * * @method Phaser.GameObjects.Particles.ParticleEmitter#depthSort * @since 3.0.0 * * @return {this} This Particle Emitter. */ depthSort: function () { StableSort(this.alive, this.sortCallback.bind(this)); return this; }, /** * Calculates the difference of two particles, for sorting them by depth. * * @method Phaser.GameObjects.Particles.ParticleEmitter#depthSortCallback * @since 3.0.0 * * @param {object} a - The first particle. * @param {object} b - The second particle. * * @return {number} The difference of a and b's y coordinates. */ depthSortCallback: function (a, b) { var key = this.sortProperty; if (this.sortOrderAsc) { return a[key] - b[key]; } else { return b[key] - a[key]; } }, /** * Puts the emitter in flow mode (frequency >= 0) and starts (or restarts) a particle flow. * * To resume a flow at the current frequency and quantity, use {@link Phaser.GameObjects.Particles.ParticleEmitter#start} instead. * * @method Phaser.GameObjects.Particles.ParticleEmitter#flow * @fires Phaser.GameObjects.Particles.Events#START * @since 3.0.0 * * @param {number} frequency - The time interval (>= 0) of each flow cycle, in ms. * @param {Phaser.Types.GameObjects.Particles.EmitterOpOnEmitType} [count=1] - The number of particles to emit at each flow cycle. * @param {number} [stopAfter] - Stop this emitter from firing any more particles once this value is reached. Set to zero for unlimited. Setting this parameter will override any `stopAfter` value already set in the Emitter configuration object. * * @return {this} This Particle Emitter. */ flow: function (frequency, count, stopAfter) { if (count === undefined) { count = 1; } this.emitting = false; this.frequency = frequency; this.quantity = count; if (stopAfter !== undefined) { this.stopAfter = stopAfter; } return this.start(); }, /** * Puts the emitter in explode mode (frequency = -1), stopping any current particle flow, and emits several particles all at once. * * @method Phaser.GameObjects.Particles.ParticleEmitter#explode * @fires Phaser.GameObjects.Particles.Events#EXPLODE * @since 3.0.0 * * @param {number} [count=this.quantity] - The number of Particles to emit. * @param {number} [x=this.x] - The x coordinate to emit the Particles from. * @param {number} [y=this.x] - The y coordinate to emit the Particles from. * * @return {Phaser.GameObjects.Particles.Particle} The most recently emitted Particle. */ explode: function (count, x, y) { this.frequency = -1; this.resetCounters(-1, true); var particle = this.emitParticle(count, x, y); this.emit(Events.EXPLODE, this, particle); return particle; }, /** * Emits particles at the given position. If no position is given, it will * emit from this Emitters current location. * * @method Phaser.GameObjects.Particles.ParticleEmitter#emitParticleAt * @since 3.0.0 * * @param {number} [x=this.x] - The x coordinate to emit the Particles from. * @param {number} [y=this.x] - The y coordinate to emit the Particles from. * @param {number} [count=this.quantity] - The number of Particles to emit. * * @return {Phaser.GameObjects.Particles.Particle} The most recently emitted Particle. */ emitParticleAt: function (x, y, count) { return this.emitParticle(count, x, y); }, /** * Emits particles at a given position (or the emitters current position). * * @method Phaser.GameObjects.Particles.ParticleEmitter#emitParticle * @since 3.0.0 * * @param {number} [count=this.quantity] - The number of Particles to emit. * @param {number} [x=this.x] - The x coordinate to emit the Particles from. * @param {number} [y=this.x] - The y coordinate to emit the Particles from. * * @return {Phaser.GameObjects.Particles.Particle} The most recently emitted Particle. * * @see Phaser.GameObjects.Particles.Particle#fire */ emitParticle: function (count, x, y) { if (this.atLimit()) { return; } if (count === undefined) { count = this.ops.quantity.onEmit(); } var dead = this.dead; var stopAfter = this.stopAfter; var followX = (this.follow) ? this.follow.x + this.followOffset.x : x; var followY = (this.follow) ? this.follow.y + this.followOffset.y : y; for (var i = 0; i < count; i++) { var particle = dead.pop(); if (!particle) { particle = new this.particleClass(this); } if (particle.fire(followX, followY)) { if (this.particleBringToTop) { this.alive.push(particle); } else { this.alive.unshift(particle); } if (this.emitCallback) { this.emitCallback.call(this.emitCallbackScope, particle, this); } } else { this.dead.push(particle); } if (stopAfter > 0) { this.stopCounter++; if (this.stopCounter >= stopAfter) { break; } } if (this.atLimit()) { break; } } return particle; }, /** * Fast forwards this Particle Emitter and all of its particles. * * Works by running the Emitter `preUpdate` handler in a loop until the `time` * has been reached at `delta` steps per loop. * * All callbacks and emitter related events that would normally be fired * will still be invoked. * * You can make an emitter 'fast forward' via the emitter config using the * `advance` property. Set this value to the number of ms you wish the * emitter to be fast-forwarded by. Or, call this method post-creation. * * @method Phaser.GameObjects.Particles.ParticleEmitter#fastForward * @since 3.60.0 * * @param {number} time - The number of ms to advance the Particle Emitter by. * @param {number} [delta] - The amount of delta to use for each step. Defaults to 1000 / 60. * * @return {this} This Particle Emitter. */ fastForward: function (time, delta) { if (delta === undefined) { delta = 1000 / 60; } var total = 0; this.skipping = true; while (total < Math.abs(time)) { this.preUpdate(0, delta); total += delta; } this.skipping = false; return this; }, /** * Updates this emitter and its particles. * * @method Phaser.GameObjects.Particles.ParticleEmitter#preUpdate * @fires Phaser.GameObjects.Particles.Events#COMPLETE * @since 3.0.0 * * @param {number} time - The current timestamp as generated by the Request Animation Frame or SetTimeout. * @param {number} delta - The delta time, in ms, elapsed since the last frame. */ preUpdate: function (time, delta) { // Scale the delta delta *= this.timeScale; var step = (delta / 1000); if (this.trackVisible) { this.visible = this.follow.visible; } this.getWorldTransformMatrix(this.worldMatrix); // Any particle processors? var processors = this.getProcessors(); var particles = this.alive; var dead = this.dead; var i = 0; var rip = []; var length = particles.length; for (i = 0; i < length; i++) { var particle = particles[i]; // update returns `true` if the particle is now dead (lifeCurrent <= 0) if (particle.update(delta, step, processors)) { rip.push({ index: i, particle: particle }); } } // Move dead particles to the dead array length = rip.length; if (length > 0) { var deathCallback = this.deathCallback; var deathCallbackScope = this.deathCallbackScope; for (i = length - 1; i >= 0; i--) { var entry = rip[i]; // Remove from particles array particles.splice(entry.index, 1); // Add to dead array dead.push(entry.particle); // Callback if (deathCallback) { deathCallback.call(deathCallbackScope, entry.particle); } entry.particle.setPosition(); } } if (!this.emitting && !this.skipping) { if (this.completeFlag === 1 && particles.length === 0) { this.completeFlag = 0; this.emit(Events.COMPLETE, this); } return; } if (this.frequency === 0) { this.emitParticle(); } else if (this.frequency > 0) { this.flowCounter -= delta; while (this.flowCounter <= 0) { // Emits the 'quantity' number of particles this.emitParticle(); // counter = frequency - remainder from previous delta this.flowCounter += this.frequency; } } // Duration or stopAfter set? if (!this.skipping) { if (this.duration > 0) { // elapsed this.elapsed += delta; if (this.elapsed >= this.duration) { this.stop(); } } if (this.stopAfter > 0 && this.stopCounter >= this.stopAfter) { this.stop(); } } }, /** * Takes either a Rectangle Geometry object or an Arcade Physics Body and tests * to see if it intersects with any currently alive Particle in this Emitter. * * Overlapping particles are returned in an array, where you can perform further * processing on them. If nothing overlaps then the array will be empty. * * @method Phaser.GameObjects.Particles.ParticleEmitter#overlap * @since 3.60.0 * * @param {(Phaser.Geom.Rectangle|Phaser.Physics.Arcade.Body)} target - A Rectangle or Arcade Physics Body to check for intersection against all alive particles. * * @return {Phaser.GameObjects.Particles.Particle[]} An array of Particles that overlap with the given target. */ overlap: function (target) { var matrix = this.getWorldTransformMatrix(); var alive = this.alive; var length = alive.length; var output = []; for (var i = 0; i < length; i++) { var particle = alive[i]; if (RectangleToRectangle(target, particle.getBounds(matrix))) { output.push(particle); } } return output; }, /** * Returns a bounds Rectangle calculated from the bounds of all currently * _active_ Particles in this Emitter. If this Emitter has only just been * created and not yet rendered, then calling this method will return a Rectangle * with a max safe integer for dimensions. Use the `advance` parameter to * avoid this. * * Typically it takes a few seconds for a flow Emitter to 'warm up'. You can * use the `advance` and `delta` parameters to force the Emitter to * 'fast forward' in time to try and allow the bounds to be more accurate, * as it will calculate the bounds based on the particle bounds across all * timesteps, giving a better result. * * You can also use the `padding` parameter to increase the size of the * bounds. Emitters with a lot of randomness in terms of direction or lifespan * can often return a bounds smaller than their possible maximum. By using * the `padding` (and `advance` if needed) you can help limit this. * * @method Phaser.GameObjects.Particles.ParticleEmitter#getBounds * @since 3.60.0 * * @param {number} [padding] - The amount of padding, in pixels, to add to the bounds Rectangle. * @param {number} [advance] - The number of ms to advance the Particle Emitter by. Defaults to 0, i.e. not used. * @param {number} [delta] - The amount of delta to use for each step. Defaults to 1000 / 60. * @param {Phaser.Geom.Rectangle} [output] - The Rectangle to store the results in. If not given a new one will be created. * * @return {Phaser.Geom.Rectangle} A Rectangle containing the calculated bounds of this Emitter. */ getBounds: function (padding, advance, delta, output) { if (padding === undefined) { padding = 0; } if (advance === undefined) { advance = 0; } if (delta === undefined) { delta = 1000 / 60; } if (output === undefined) { output = new Rectangle(); } var matrix = this.getWorldTransformMatrix(); var i; var bounds; var alive = this.alive; var setFirst = false; output.setTo(0, 0, 0, 0); if (advance > 0) { var total = 0; this.skipping = true; while (total < Math.abs(advance)) { this.preUpdate(0, delta); for (i = 0; i < alive.length; i++) { bounds = alive[i].getBounds(matrix); if (!setFirst) { setFirst = true; CopyFrom(bounds, output); } else { MergeRect(output, bounds); } } total += delta; } this.skipping = false; } else { for (i = 0; i < alive.length; i++) { bounds = alive[i].getBounds(matrix); if (!setFirst) { setFirst = true; CopyFrom(bounds, output); } else { MergeRect(output, bounds); } } } if (padding > 0) { Inflate(output, padding, padding); } return output; }, /** * Prints a warning to the console if you mistakenly call this function * thinking it works the same way as Phaser v3.55. * * @method Phaser.GameObjects.Particles.ParticleEmitter#createEmitter * @since 3.60.0 */ createEmitter: function () { throw new Error('createEmitter removed. See ParticleEmitter docs for info'); }, /** * The x coordinate the particles are emitted from. * * This is relative to the Emitters x coordinate and that of any parent. * * Accessing this property should typically return a number. * However, it can be set to any valid EmitterOp onEmit type. * * @name Phaser.GameObjects.Particles.ParticleEmitter#particleX * @type {Phaser.Types.GameObjects.Particles.EmitterOpOnEmitType|Phaser.Types.GameObjects.Particles.EmitterOpOnUpdateType} * @since 3.60.0 */ particleX: { get: function () { return this.ops.x.current; }, set: function (value) { this.ops.x.onChange(value); } }, /** * The y coordinate the particles are emitted from. * * This is relative to the Emitters x coordinate and that of any parent. * * Accessing this property should typically return a number. * However, it can be set to any valid EmitterOp onEmit type. * * @name Phaser.GameObjects.Particles.ParticleEmitter#particleY * @type {Phaser.Types.GameObjects.Particles.EmitterOpOnEmitType|Phaser.Types.GameObjects.Particles.EmitterOpOnUpdateType} * @since 3.60.0 */ particleY: { get: function () { return this.ops.y.current; }, set: function (value) { this.ops.y.onChange(value); } }, /** * The horizontal acceleration applied to emitted particles, in pixels per second squared. * * Accessing this property should typically return a number. * However, it can be set to any valid EmitterOp onEmit type. * * @name Phaser.GameObjects.Particles.ParticleEmitter#accelerationX * @type {Phaser.Types.GameObjects.Particles.EmitterOpOnEmitType} * @since 3.60.0 */ accelerationX: { get: function () { return this.ops.accelerationX.current; }, set: function (value) { this.ops.accelerationX.onChange(value); } }, /** * The vertical acceleration applied to emitted particles, in pixels per second squared. * * Accessing this property should typically return a number. * However, it can be set to any valid EmitterOp onEmit type. * * @name Phaser.GameObjects.Particles.ParticleEmitter#accelerationY * @type {Phaser.Types.GameObjects.Particles.EmitterOpOnEmitType} * @since 3.60.0 */ accelerationY: { get: function () { return this.ops.accelerationY.current; }, set: function (value) { this.ops.accelerationY.onChange(value); } }, /** * The maximum horizontal velocity emitted particles can reach, in pixels per second squared. * * Accessing this property should typically return a number. * However, it can be set to any valid EmitterOp onEmit type. * * @name Phaser.GameObjects.Particles.ParticleEmitter#maxVelocityX * @type {Phaser.Types.GameObjects.Particles.EmitterOpOnEmitType} * @since 3.60.0 * @default 10000 */ maxVelocityX: { get: function () { return this.ops.maxVelocityX.current; }, set: function (value) { this.ops.maxVelocityX.onChange(value); } }, /** * The maximum vertical velocity emitted particles can reach, in pixels per second squared. * * Accessing this property should typically return a number. * However, it can be set to any valid EmitterOp onEmit type. * * @name Phaser.GameObjects.Particles.ParticleEmitter#maxVelocityY * @type {Phaser.Types.GameObjects.Particles.EmitterOpOnEmitType} * @since 3.60.0 * @default 10000 */ maxVelocityY: { get: function () { return this.ops.maxVelocityY.current; }, set: function (value) { this.ops.maxVelocityY.onChange(value); } }, /** * The initial speed of emitted particles, in pixels per second. * * If using this as a getter it will return the `speedX` value. * * If using it as a setter it will update both `speedX` and `speedY` to the * given value. * * Accessing this property should typically return a number. * However, it can be set to any valid EmitterOp onEmit type. * * @name Phaser.GameObjects.Particles.ParticleEmitter#speed * @type {Phaser.Types.GameObjects.Particles.EmitterOpOnEmitType} * @since 3.60.0 */ speed: { get: function () { return this.ops.speedX.current; }, set: function (value) { this.ops.speedX.onChange(value); this.ops.speedY.onChange(value); } }, /** * The initial horizontal speed of emitted particles, in pixels per second. * * Accessing this property should typically return a number. * However, it can be set to any valid EmitterOp onEmit type. * * @name Phaser.GameObjects.Particles.ParticleEmitter#speedX * @type {Phaser.Types.GameObjects.Particles.EmitterOpOnEmitType} * @since 3.60.0 */ speedX: { get: function () { return this.ops.speedX.current; }, set: function (value) { this.ops.speedX.onChange(value); } }, /** * The initial vertical speed of emitted particles, in pixels per second. * * Accessing this property should typically return a number. * However, it can be set to any valid EmitterOp onEmit type. * * @name Phaser.GameObjects.Particles.ParticleEmitter#speedY * @type {Phaser.Types.GameObjects.Particles.EmitterOpOnEmitType} * @since 3.60.0 */ speedY: { get: function () { return this.ops.speedY.current; }, set: function (value) { this.ops.speedY.onChange(value); } }, /** * The x coordinate emitted particles move toward, when {@link Phaser.GameObjects.Particles.ParticleEmitter#moveTo} is true. * * Accessing this property should typically return a number. * However, it can be set to any valid EmitterOp onEmit type. * * @name Phaser.GameObjects.Particles.ParticleEmitter#moveToX * @type {Phaser.Types.GameObjects.Particles.EmitterOpOnEmitType} * @since 3.60.0 */ moveToX: { get: function () { return this.ops.moveToX.current; }, set: function (value) { this.ops.moveToX.onChange(value); } }, /** * The y coordinate emitted particles move toward, when {@link Phaser.GameObjects.Particles.ParticleEmitter#moveTo} is true. * * Accessing this property should typically return a number. * However, it can be set to any valid EmitterOp onEmit type. * * @name Phaser.GameObjects.Particles.ParticleEmitter#moveToY * @type {Phaser.Types.GameObjects.Particles.EmitterOpOnEmitType} * @since 3.60.0 */ moveToY: { get: function () { return this.ops.moveToY.current; }, set: function (value) { this.ops.moveToY.onChange(value); } }, /** * The amount of velocity particles will use when rebounding off the * emitter bounds, if set. A value of 0 means no bounce. A value of 1 * means a full rebound. * * Accessing this property should typically return a number. * However, it can be set to any valid EmitterOp onEmit type. * * @name Phaser.GameObjects.Particles.ParticleEmitter#bounce * @type {Phaser.Types.GameObjects.Particles.EmitterOpOnEmitType} * @since 3.60.0 */ bounce: { get: function () { return this.ops.bounce.current; }, set: function (value) { this.ops.bounce.onChange(value); } }, /** * The horizontal scale of emitted particles. * * This is relative to the Emitters scale and that of any parent. * * Accessing this property should typically return a number. * However, it can be set to any valid EmitterOp onEmit type. * * @name Phaser.GameObjects.Particles.ParticleEmitter#particleScaleX * @type {Phaser.Types.GameObjects.Particles.EmitterOpOnEmitType} * @since 3.60.0 */ particleScaleX: { get: function () { return this.ops.scaleX.current; }, set: function (value) { this.ops.scaleX.onChange(value); } }, /** * The vertical scale of emitted particles. * * This is relative to the Emitters scale and that of any parent. * * Accessing this property should typically return a number. * However, it can be set to any valid EmitterOp onEmit type. * * @name Phaser.GameObjects.Particles.ParticleEmitter#particleScaleY * @type {Phaser.Types.GameObjects.Particles.EmitterOpOnEmitType} * @since 3.60.0 */ particleScaleY: { get: function () { return this.ops.scaleY.current; }, set: function (value) { this.ops.scaleY.onChange(value); } }, /** * A color tint value that is applied to the texture of the emitted * particle. The value should be given in hex format, i.e. 0xff0000 * for a red tint, and should not include the alpha channel. * * Tints are additive, meaning a tint value of white (0xffffff) will * effectively reset the tint to nothing. * * Modify the `ParticleEmitter.tintFill` property to change between * an additive and replacement tint mode. * * When you define the color via the Emitter config you should give * it as an array of color values. The Particle will then interpolate * through these colors over the course of its lifespan. Setting this * will override any `tint` value that may also be given. * * This is a WebGL only feature. * * Accessing this property should typically return a number. * However, it can be set to any valid EmitterOp onEmit type. * * @name Phaser.GameObjects.Particles.ParticleEmitter#particleColor * @type {Phaser.Types.GameObjects.Particles.EmitterOpOnEmitType} * @since 3.60.0 */ particleColor: { get: function () { return this.ops.color.current; }, set: function (value) { this.ops.color.onChange(value); } }, /** * Controls the easing function used when you have created an * Emitter that uses the `color` property to interpolate the * tint of Particles over their lifetime. * * Setting this has no effect if you haven't also applied a * `particleColor` to this Emitter. * * @name Phaser.GameObjects.Particles.ParticleEmitter#colorEase * @type {string} * @since 3.60.0 */ colorEase: { get: function () { return this.ops.color.easeName; }, set: function (value) { this.ops.color.setEase(value); } }, /** * A color tint value that is applied to the texture of the emitted * particle. The value should be given in hex format, i.e. 0xff0000 * for a red tint, and should not include the alpha channel. * * Tints are additive, meaning a tint value of white (0xffffff) will * effectively reset the tint to nothing. * * Modify the `ParticleEmitter.tintFill` property to change between * an additive and replacement tint mode. * * The `tint` value will be overriden if a `color` array is provided. * * This is a WebGL only feature. * * Accessing this property should typically return a number. * However, it can be set to any valid EmitterOp onEmit type. * * @name Phaser.GameObjects.Particles.ParticleEmitter#particleTint * @type {Phaser.Types.GameObjects.Particles.EmitterOpOnEmitType} * @since 3.60.0 */ particleTint: { get: function () { return this.ops.tint.current; }, set: function (value) { this.ops.tint.onChange(value); } }, /** * The alpha value of the emitted particles. This is a value * between 0 and 1. Particles with alpha zero are invisible * and are therefore not rendered, but are still processed * by the Emitter. * * Accessing this property should typically return a number. * However, it can be set to any valid EmitterOp onEmit type. * * @name Phaser.GameObjects.Particles.ParticleEmitter#particleAlpha * @type {Phaser.Types.GameObjects.Particles.EmitterOpOnEmitType} * @since 3.60.0 */ particleAlpha: { get: function () { return this.ops.alpha.current; }, set: function (value) { this.ops.alpha.onChange(value); } }, /** * The lifespan of the emitted particles. This value is given * in milliseconds and defaults to 1000ms (1 second). When a * particle reaches this amount it is killed. * * Accessing this property should typically return a number. * However, it can be set to any valid EmitterOp onEmit type. * * @name Phaser.GameObjects.Particles.ParticleEmitter#lifespan * @type {Phaser.Types.GameObjects.Particles.EmitterOpOnEmitType} * @since 3.60.0 */ lifespan: { get: function () { return this.ops.lifespan.current; }, set: function (value) { this.ops.lifespan.onChange(value); } }, /** * The angle at which the particles are emitted. The values are * given in degrees. This allows you to control the direction * of the emitter. If you wish instead to change the rotation * of the particles themselves, see the `particleRotate` property. * * Accessing this property should typically return a number. * However, it can be set to any valid EmitterOp onEmit type. * * @name Phaser.GameObjects.Particles.ParticleEmitter#particleAngle * @type {Phaser.Types.GameObjects.Particles.EmitterOpOnEmitType} * @since 3.60.0 */ particleAngle: { get: function () { return this.ops.angle.current; }, set: function (value) { this.ops.angle.onChange(value); } }, /** * The rotation (or angle) of each particle when it is emitted. * The value is given in degrees and uses a right-handed * coordinate system, where 0 degrees points to the right, 90 degrees * points down and -90 degrees points up. * * Accessing this property should typically return a number. * However, it can be set to any valid EmitterOp onEmit type. * * @name Phaser.GameObjects.Particles.ParticleEmitter#particleRotate * @type {Phaser.Types.GameObjects.Particles.EmitterOpOnEmitType} * @since 3.60.0 */ particleRotate: { get: function () { return this.ops.rotate.current; }, set: function (value) { this.ops.rotate.onChange(value); } }, /** * The number of particles that are emitted each time an emission * occurs, i.e. from one 'explosion' or each frame in a 'flow' cycle. * * The default is 1. * * Accessing this property should typically return a number. * However, it can be set to any valid EmitterOp onEmit type. * * @name Phaser.GameObjects.Particles.ParticleEmitter#quantity * @type {Phaser.Types.GameObjects.Particles.EmitterOpOnEmitType} * @see Phaser.GameObjects.Particles.ParticleEmitter#setFrequency * @see Phaser.GameObjects.Particles.ParticleEmitter#setQuantity * @since 3.60.0 */ quantity: { get: function () { return this.ops.quantity.current; }, set: function (value) { this.ops.quantity.onChange(value); } }, /** * The number of milliseconds to wait after emission before * the particles start updating. This allows you to emit particles * that appear 'static' or still on-screen and then, after this value, * begin to move. * * Accessing this property should typically return a number. * However, it can be set to any valid EmitterOp onEmit type. * * @name Phaser.GameObjects.Particles.ParticleEmitter#delay * @type {Phaser.Types.GameObjects.Particles.EmitterOpOnEmitType} * @since 3.60.0 */ delay: { get: function () { return this.ops.delay.current; }, set: function (value) { this.ops.delay.onChange(value); } }, /** * The number of milliseconds to wait after a particle has finished * its life before it will be removed. This allows you to 'hold' a * particle on the screen once it has reached its final state * before it then vanishes. * * Note that all particle updates will cease, including changing * alpha, scale, movement or animation. * * Accessing this property should typically return a number. * However, it can be set to any valid EmitterOp onEmit type. * * @name Phaser.GameObjects.Particles.ParticleEmitter#hold * @type {Phaser.Types.GameObjects.Particles.EmitterOpOnEmitType} * @since 3.60.0 */ hold: { get: function () { return this.ops.hold.current; }, set: function (value) { this.ops.hold.onChange(value); } }, /** * The internal flow counter. * * Treat this property as read-only. * * @name Phaser.GameObjects.Particles.ParticleEmitter#flowCounter * @type {number} * @since 3.60.0 */ flowCounter: { get: function () { return this.counters[0]; }, set: function (value) { this.counters[0] = value; } }, /** * The internal frame counter. * * Treat this property as read-only. * * @name Phaser.GameObjects.Particles.ParticleEmitter#frameCounter * @type {number} * @since 3.60.0 */ frameCounter: { get: function () { return this.counters[1]; }, set: function (value) { this.counters[1] = value; } }, /** * The internal animation counter. * * Treat this property as read-only. * * @name Phaser.GameObjects.Particles.ParticleEmitter#animCounter * @type {number} * @since 3.60.0 */ animCounter: { get: function () { return this.counters[2]; }, set: function (value) { this.counters[2] = value; } }, /** * The internal elasped counter. * * Treat this property as read-only. * * @name Phaser.GameObjects.Particles.ParticleEmitter#elapsed * @type {number} * @since 3.60.0 */ elapsed: { get: function () { return this.counters[3]; }, set: function (value) { this.counters[3] = value; } }, /** * The internal stop counter. * * Treat this property as read-only. * * @name Phaser.GameObjects.Particles.ParticleEmitter#stopCounter * @type {number} * @since 3.60.0 */ stopCounter: { get: function () { return this.counters[4]; }, set: function (value) { this.counters[4] = value; } }, /** * The internal complete flag. * * Treat this property as read-only. * * @name Phaser.GameObjects.Particles.ParticleEmitter#completeFlag * @type {boolean} * @since 3.60.0 */ completeFlag: { get: function () { return this.counters[5]; }, set: function (value) { this.counters[5] = value; } }, /** * The internal zone index. * * Treat this property as read-only. * * @name Phaser.GameObjects.Particles.ParticleEmitter#zoneIndex * @type {number} * @since 3.60.0 */ zoneIndex: { get: function () { return this.counters[6]; }, set: function (value) { this.counters[6] = value; } }, /** * The internal zone total. * * Treat this property as read-only. * * @name Phaser.GameObjects.Particles.ParticleEmitter#zoneTotal * @type {number} * @since 3.60.0 */ zoneTotal: { get: function () { return this.counters[7]; }, set: function (value) { this.counters[7] = value; } }, /** * The current frame index. * * Treat this property as read-only. * * @name Phaser.GameObjects.Particles.ParticleEmitter#currentFrame * @type {number} * @since 3.60.0 */ currentFrame: { get: function () { return this.counters[8]; }, set: function (value) { this.counters[8] = value; } }, /** * The current animation index. * * Treat this property as read-only. * * @name Phaser.GameObjects.Particles.ParticleEmitter#currentAnim * @type {number} * @since 3.60.0 */ currentAnim: { get: function () { return this.counters[9]; }, set: function (value) { this.counters[9] = value; } }, /** * Destroys this Particle Emitter and all Particles it owns. * * @method Phaser.GameObjects.Particles.ParticleEmitter#preDestroy * @since 3.60.0 */ preDestroy: function () { this.texture = null; this.frames = null; this.anims = null; this.emitCallback = null; this.emitCallbackScope = null; this.deathCallback = null; this.deathCallbackScope = null; this.emitZones = null; this.deathZones = null; this.bounds = null; this.follow = null; this.counters = null; var i; var ops = this.ops; for (i = 0; i < configOpMap.length; i++) { var key = configOpMap[i]; ops[key].destroy(); } for (i = 0; i < this.alive.length; i++) { this.alive[i].destroy(); } for (i = 0; i < this.dead.length; i++) { this.dead[i].destroy(); } this.ops = null; this.alive = []; this.dead = []; this.worldMatrix.destroy(); } }); module.exports = ParticleEmitter;