UNPKG

phaser

Version:

A fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers from the team at Phaser Studio Inc.

1,447 lines (1,303 loc) 127 kB
/** * @author Richard Davey <rich@phaser.io> * @copyright 2013-2025 Phaser Studio Inc. * @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 HasAll = require('../../utils/object/HasAll'); 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 MergeRight = require('../../utils/object/MergeRight'); var Particle = require('./Particle'); var ParticleBounds = require('./ParticleBounds'); 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'); /** * 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 the configuration for the Emitter. * * These are populated as part of the Emitter configuration parsing. * * You typically do not access them directly, but instead use the * `ParticleEmitter.setConfig` or `ParticleEmitter.updateConfig` methods. * * @name Phaser.GameObjects.Particles.ParticleEmitter#config * @type {Phaser.Types.GameObjects.Particles.ParticleEmitterConfig} * @since 3.85.0 */ this.config = null; /** * 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.Types.GameObjects.Particles.EmitZoneObject[]} * @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.Types.Math.Vector2Like} * @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.<Phaser.GameObjects.Particles.ParticleProcessor>} * @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; } this.config = config; 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 = HasAll(config, [ 'moveToX', 'moveToY' ]); // 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; }, /** * Takes an existing Emitter Configuration file and updates this Emitter. * Existing properties are overriden while new properties are added. The * updated configuration is then passed to the `setConfig` method to reset * the Emitter with the updated configuration. * * @method Phaser.GameObjects.Particles.ParticleEmitter#updateConfig * @since 3.85.0 * * @param {Phaser.Types.GameObjects.Particles.ParticleEmitterConfig} config - Settings for this emitter. * * @return {this} This Particle Emitter. */ updateConfig: function (config) { if (config) { if (!this.config) { this.setConfig(config); } else { this.setConfig(MergeRight(this.config, config)); } } 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.Types.Math.Vector2Like} target - The 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); } 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] } * * Call this method at least once before any particles are created, or set `anim` in the Particle Emitter's configuration when creating the Emitter. * * @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`. If a config object is given, this parameter is ignored. * @param {number} [quantity=1] - The number of consecutive particles that will receive each animation. If a config object is given, this parameter is ignored. * * @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=tru