Iavra

Particle Engine - Core

Jan 25th, 2016 (edited)
522
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /*:
  2.  * @plugindesc v1.04 Extensible particle engine, that creates effects by applying behaviours to particle objects.
  3.  * @author Iavra
  4.  *
  5.  * Changelog:
  6.  * - 1.01: The engine has been revamped be reintroducing behaviours.
  7.  * - 1.02: Moved particle aging from behaviours into the emitter.
  8.  * - 1.03: Introduced activities, which run during each update, but before particles are updated.
  9.  * - 1.04: Added a way for behaviours to store and read data on a particle.
  10.  *
  11.  * @help
  12.  * The following script call creates a sample emitter, that displays a bunch of particles wandering around the screen:
  13.  *
  14.  * var emitter = new IAVRA.PARTICLE.Emitter()
  15.  *     .setCounter({
  16.  *         start: function(emitter) {},
  17.  *         update: function(emitter) { return 1; }
  18.  *     })
  19.  *     .addBehaviour({
  20.  *         setup: function(emitter, particle) {
  21.  *             particle.texture = PIXI.Texture.fromImage('img/particle.png');
  22.  *             particle.position.set(Graphics.width / 2, Graphics.height / 2);
  23.  *             particle.life = 200;
  24.  *         },
  25.  *         update: function(emitter, particle, index) {
  26.  *             particle.velocity.x += Math.random() - 0.5;
  27.  *             particle.velocity.y += Math.random() - 0.5;
  28.  *         }
  29.  *     });
  30.  *
  31.  * As you can see, after creating the emitter itself, we are adding two properties to it: A counter and a behaviour.
  32.  *
  33.  * Counters regulate the emission rate and instruct the emitter, how many new particles it has to create at any given
  34.  * time. In our example, we create 1 new particle every frame and no extra particles, when the emitter starts. Each
  35.  * emitter can only have 1 counter and defaults to a dummy instance, that causes no particles to be created.
  36.  *
  37.  * Behaviours control, how particles are created and updated, how the look and interact with each other and their
  38.  * surroundings. They are powerful, but can get very complex. In our example, we assign a texture to the particle and
  39.  * set its starting position to the center of the screen. During each update, it will accelerate in a random direction
  40.  * and increase its age counter by 1, before finally dying after 200 updates. Each emitter can have any number of
  41.  * behaviours assigned and they are executed in the order they were added.
  42.  *
  43.  * Now, the only thing left to do is to start the emitter and add it to the scene. The Emitter class contains a number
  44.  * of functions to interact with emitters and each of them returns the emitter itself, so they can be chained:
  45.  *
  46.  * emitter.start();                 Starts the emitter. Before calling this function, it will simply do nothing.
  47.  * emitter.stop();                  Stops the emitter, causing all particle creation and updating to stop.
  48.  * emitter.resume();                Resumes the emitter, after it has previously been stopped.
  49.  * emitter.skip(count);             Fast forwards a number of update cycles. Creates and updates particles accordingly.
  50.  * emitter.clear();                 Removes all current particles from the emitter.
  51.  * emitter.setCounter(counter);     Sets the counter to be used. Needs to implement "start" and "update" functions.
  52.  * emitter.setFactory(factory);     Replaces the default particle factory. Needs to implement "create" and "dispose".
  53.  * emitter.addBehaviour(...b);      Adds one or more behaviours to the emitter.
  54.  * emitter.removeBehaviour(b);      Removes all occurences of a given behaviour from the emitter.
  55.  * emitter.removeAllBehaviours();   Removes all behaviours from the emitter.
  56.  *
  57.  * Adding or removing behaviours while the emitter is running, affects both new and existing particles, so keep that in
  58.  * mind. Inside a behaviour, the following properties of the particle can be accessed:
  59.  *
  60.  * texture        (PIXI.Texture)    Image to be displayed for the particle. If this is not set, you won't see anything.
  61.  * life           (number)          The particle's lifespan. Defaults to Infinity.
  62.  * age            (number)          Read-only. Starts at 0 and can be used to track the particle's age.
  63.  * energy         (number)          Starts at 1 and is used by some behaviours to apply transformation effects.
  64.  * dead           (boolean)         If this gets set to true, the particle will be disposed by the emitter.
  65.  * oldPosition    (PIXI.Point)      Read-only. Gets set to the particle's position before the last update.
  66.  * position       (PIXI.Point)      The particle's current position. Automatically gets updated every update.
  67.  * velocity       (PIXI.Point)      Marks the rate, at which the particle's position changes.
  68.  * scale          (PIXI.Point)      Scaling factor of the particle. Its x and y value should be kept synchron.
  69.  * radius         (number)          Gets used by some behaviours to implement collision.
  70.  * alpha          (number)          Handles the particle's transparency on a scale from 0 (transparent) to 1 (opaque).
  71.  * rotation       (number)          The particle's rotation around its center point, in radians.
  72.  * tint           (number)          Tinting color of the particle, as hex number. 0xFFFFFF removes all tinting.
  73.  * blendMode      (number)          Blend mode to be used. Support variies between renderers. Default is 0.
  74.  */
  75.  
  76. (function($, undefined) {
  77.     "use strict";
  78.  
  79.     /**
  80.      * Used to assign unique id values to behaviours, when they are added to an emitter.
  81.      */
  82.     var _behaviourId = 0;
  83.    
  84.     /**
  85.      * Extends a given object with all properties from another given object. Mainly used to simplify subclassing.
  86.      * @param {Object} base - Base object to be extended.
  87.      * @param {Object} extend - Object containing the properties to be appended to the given base object.
  88.      * @returns {Object} The base object, after it has been extended.
  89.      */
  90.     var _extend = function(base, extend) {
  91.         for(var key in extend) { base[key] = extend[key]; }
  92.         return base;
  93.     };
  94.  
  95.     //=============================================================================
  96.     // IAVRA.PARTICLE
  97.     //=============================================================================
  98.  
  99.     $.PARTICLE = {
  100.         Particle: function() { this._initialize(); },
  101.         Factory: function() { this._initialize(); },
  102.         Emitter: function() { this._initialize(); },
  103.         Counter: function() {},
  104.         Behaviour: function() {},
  105.         Zone: function() {}
  106.     };
  107.  
  108.     //=============================================================================
  109.     // IAVRA.PARTICLE.Particle
  110.     //=============================================================================
  111.  
  112.     /**
  113.      * In this engine, the particle only serves as a data holder and visual object, instead of managing its own update
  114.      * function. This also means, that you should never need to extend this class.
  115.      */
  116.     $.PARTICLE.Particle.prototype = _extend(Object.create(PIXI.Sprite.prototype), {
  117.  
  118.         /**
  119.          * Initializes the particle. Since particles are reused by the factory, this is usually only called once and
  120.          * the main initialization happens inside the "reset" function.
  121.          */
  122.         _initialize: function() {
  123.             PIXI.Sprite.call(this);
  124.             this.oldPosition = new PIXI.Point();
  125.             this.velocity = new PIXI.Point();
  126.             this.reset();
  127.         },
  128.  
  129.         /**
  130.          * Resets the particle to its default state. This is called, whenever a particle is disposed and pushed back
  131.          * inside the factory's particle pool, so it can be reused later.
  132.          */
  133.         reset: function() {
  134.             this.anchor.x = this.anchor.y = 0.5;
  135.             this.pivot.x = this.pivot.y = 0.5;
  136.             this.texture = PIXI.Texture.emptyTexture;
  137.             this.life = Infinity;
  138.             this.age = 0;
  139.             this.energy = 1;
  140.             this.dead = false;
  141.             this.oldPosition.x = this.oldPosition.y = 0;
  142.             this.position.x = this.position.y = 0;
  143.             this.velocity.x = this.velocity.y = 0;
  144.             this.scale.x = this.scale.y = 1;
  145.             this.radius = 1;
  146.             this.alpha = 1;
  147.             this.rotation = 0;
  148.             this.tint = 0xFFFFFF;
  149.             this.blendMode = PIXI.blendModes.NORMAL;
  150.             this._behaviourData = {};
  151.         },
  152.        
  153.         /**
  154.          * Can be used by behaviours to store data inside the particle. Behaviours are uniquely identified by an id,
  155.          * that gets assigned to them, when they are added to an emitter.
  156.          * @param {IAVRA.PARTICLE.Behaviour} behaviour - The behaviour, that wants to store data.
  157.          * @param {(string|number)} key - Key used to store the given data.
  158.          * @param {*} value - Data, that should be stored inside the particle.
  159.          * @returns {*} The value, that has been set.
  160.          */
  161.         setData: function(behaviour, key, value) {
  162.             return this._behaviourData[behaviour.__id + ':' + key] = value;
  163.         },
  164.        
  165.         /**
  166.          * Can be used by behaviours to retrieve data, that has previously been stored inside the particle. Behaviours
  167.          * are uniquely identified by an id, that gets assigned to them, when they are added to an emitter.
  168.          * @param {IAVRA.PARTICLE.Behaviour} behaviour - The behaviour, that wants to read data.
  169.          * @param {(string|number)} key - Key used to read data.
  170.          * @returns {*} The requested data.
  171.          */
  172.         getData: function(behaviour, key) {
  173.             return this._behaviourData[behaviour.__id + ':' + key];
  174.         }
  175.  
  176.     });
  177.  
  178.     //=============================================================================
  179.     // IAVRA.PARTICLE.Factory
  180.     //=============================================================================
  181.  
  182.     /**
  183.      * Manages the creation and disposing of particles. Disposed ones are kept within the particle pool, so they can
  184.      * be reused later.
  185.      */
  186.     $.PARTICLE.Factory.prototype = {
  187.  
  188.         /**
  189.          * Initializes the factory and creates an empty particle pool.
  190.          */
  191.         _initialize: function() {
  192.             this._pool = [];
  193.         },
  194.  
  195.         /**
  196.          * When requested by the emitter, the factory tries to reuse an existing particle first. If the pool is empty,
  197.          * a new one is created and returned, instead.
  198.          * @returns {IAVRA.PARTICLE.Particle} A particle instance to be used by the emitter.
  199.          */
  200.         create: function() {
  201.             return this._pool.length ? this._pool.pop() : new $.PARTICLE.Particle();
  202.         },
  203.  
  204.         /**
  205.          * Resets the given particle and pushes it back into the particle pool, so it can be reused later.
  206.          * @param {IAVRA.PARTICLE.Particle} Particle instance, that should be disposed.
  207.          */
  208.         dispose: function(particle) {
  209.             particle.reset();
  210.             this._pool.push(particle);
  211.         },
  212.  
  213.         /**
  214.          * Clears the particle pool. It will automatically get cleared, once the emitter (and the contained factory)
  215.          * gets disposed, but you might want to call this, if you need to free memory.
  216.          */
  217.         clear: function() {
  218.             this._pool.length = 0;
  219.         }
  220.  
  221.     };
  222.  
  223.     //=============================================================================
  224.     // IAVRA.PARTICLE.Emitter
  225.     //=============================================================================
  226.  
  227.     /**
  228.      * The emitter sits at the heart of the engine and handles emitter creation, update and disposing. It also serves
  229.      * as a container for all created particles, so it can easily be added to the scene.
  230.      */
  231.     $.PARTICLE.Emitter.prototype = _extend(Object.create(PIXI.DisplayObjectContainer.prototype), {
  232.  
  233.         /**
  234.          * Initalizes the emitter, creating default instances for the internal counter and factory.
  235.          */
  236.         _initialize: function() {
  237.             PIXI.DisplayObjectContainer.call(this);
  238.             this._counter = { start: function() {}, update: function() {} };
  239.             this._factory = new $.PARTICLE.Factory();
  240.             this._activities = [];
  241.             this._initializers = [];
  242.             this._behaviours = [];
  243.             this._started = this._running = this._reverse = false;
  244.         },
  245.  
  246.         /**
  247.          * Sets the counter used to control this emitter's emission rate. A counter needs to implement the functions
  248.          * "start" and "update". For more information, take a look at the documentation for IAVRA.PARTICLE.Counter.
  249.          * @param {IAVRA.PARTICLE.Counter} counter - Counter instance to be used by the emitter.
  250.          * @returns {IAVRA.PARTICLE.Emitter} This object, to allow chaining.
  251.          */
  252.         setCounter: function(counter) {
  253.             this._counter = counter;
  254.             return this;
  255.         },
  256.  
  257.         /**
  258.          * Sets the factory used to create and dispose particle instances. A factory need to implement the functions
  259.          * "create" and "dispose". For more information, take a look at the documentation for IAVRA.PARTICLE.Factory.
  260.          * @param {IAVRA.PARTICLE.Factory} factory - Factory instance to be used by the emitter.
  261.          * @returns {IAVRA.PARTICLE.Emitter} This object, to allow chaining.
  262.          */
  263.         setFactory: function(factory) {
  264.             this._factory = factory;
  265.             return this;
  266.         },
  267.  
  268.         /**
  269.          * Adds one or more behaviours to the emitter. Behaviours control how particles are created and updated. Also,
  270.          * each behaviour gets assigned a unique id, if it doesn't already exist. This is used inside the particle
  271.          * class to provide a way for behaviours to store data. For more information, take a look at the documentation
  272.          * for IAVRA.PARTICLE.Behaviour.
  273.          * @param {IAVRA.PARTICLE.Behaviour[]} behaviour - One ore more behaviour objects to register.
  274.          * @returns {IAVRA.PARTICLE.Emitter} This object, to allow chaining.
  275.          */
  276.         addBehaviour: function(behaviour) {
  277.             for(var i = 0, b; b = arguments[i++]; ) {
  278.                 if(b.__id === undefined) { b.__id = _behaviourId++; }
  279.                 if(b.activity) { this._activities.push(b); }
  280.                 if(b.setup) { this._initializers.push(b); }
  281.                 if(b.update) { this._behaviours.push(b); }
  282.             }
  283.             return this;
  284.         },
  285.  
  286.         /**
  287.          * Removes all instances of a given Behaviour object from this emitter.
  288.          * @param {IAVRA.PARTICLE.Behaviour} behaviour - Behaviour to be removed from this emitter.
  289.          * @returns {IAVRA.PARTICLE.Emitter} This object, to allow chaining.
  290.          */
  291.         removeBehaviour: function(behaviour) {
  292.             for(var i; (i = this._activities.indexOf(behaviour)) > -1; ) { this._activities.splice(i, 1); }
  293.             for(var i; (i = this._initializers.indexOf(behaviour)) > -1; ) { this._initializers.splice(i, 1); }
  294.             for(var i; (i = this._behaviours.indexOf(behaviour)) > -1; ) { this._behaviours.splice(i, 1); }
  295.             return this;
  296.         },
  297.  
  298.         /**
  299.          * Removes all Behaviour objects from this emitter.
  300.          * @returns {IAVRA.PARTICLE.Emitter} This object, to allow chaining.
  301.          */
  302.         removeAllBehaviours: function() {
  303.             this._activities.length = this._initializers.length = this._behaviours.length = 0;
  304.             return this;
  305.         },
  306.  
  307.         /**
  308.          * Starts the emitter, which causes it to create and update particles during update cycles. Calls the internal
  309.          * counter's "start" function to determine, how many particles should be created on startup.
  310.          * @returns {IAVRA.PARTICLE.Emitter} This object, to allow chaining.
  311.          */
  312.         start: function() {
  313.             if(this._started) { return this; }
  314.             this._started = this._running = true;
  315.             for(var i = 0, max = this._counter.start(this)|0; i < max; ++i) { this._createParticle(); }
  316.             return this;
  317.         },
  318.  
  319.         /**
  320.          * Stops the emitter (temporarily), causing it to not create or update particles anymore, until its "resume"
  321.          * function gets called.
  322.          * @returns {IAVRA.PARTICLE.Emitter} This object, to allow chaining.
  323.          */
  324.         stop: function() {
  325.             this._running = false;
  326.             return this;
  327.         },
  328.  
  329.         /**
  330.          * Resumes emitter execution, after it has previously been stopped.
  331.          * @returns {IAVRA.PARTICLE.Emitter} This object, to allow chaining.
  332.          */
  333.         resume: function() {
  334.             if(this._started) { this._running = true; }
  335.             return this;
  336.         },
  337.  
  338.         /**
  339.          * Causes the emitter to fast forward a given number of update cycles, making it appear as though it has been
  340.          * running for a while.
  341.          * @param {number} count - How many updates should be executed.
  342.          * @returns {IAVRA.PARTICLE.Emitter} This object, to allow chaining.
  343.          */
  344.         skip: function(count) {
  345.             for(var i = 0; i < count; ++i) { this.update(); }
  346.             return this;
  347.         },
  348.  
  349.         /**
  350.          * Instantly destroys all current particles on the emitter. Does not stop new particles from being created.
  351.          * @returns {IAVRA.PARTICLE.Emitter} This object, to allow chaining.
  352.          */
  353.         clear: function() {
  354.             for(var i = this.children.length; i--; ) { this._disposeParticle(i); }
  355.             return this;
  356.         },
  357.  
  358.         /**
  359.          * Updates the emitter, causing it to create, update and dispose particles as instructed by the given counter
  360.          * and behaviours. The loop direction changes between each update, to iron out possible errors.
  361.          */
  362.         update: function() {
  363.             if(!this._running) { return; }
  364.             for(var i = 0, max = this._counter.update(this)|0; i < max; ++i) { this._createParticle(); }
  365.             for(var i = 0, behaviour; behaviour = this._activities[i++]; ) { behaviour.activity(this); }
  366.             if(this.children.length) {
  367.                 var length = this.children.length, i;
  368.                 if(this._reverse ^= true) {
  369.                     for(i = length; i--; ) { this._updateParticle(this.children[i], i); }
  370.                 } else {
  371.                     for(i = 0; i < length; ++i) { this._updateParticle(this.children[i], i); }
  372.                 }
  373.                 for(i = length; i--; ) { if(this.children[i].dead) { this._disposeParticle(i); } }
  374.             }
  375.         },
  376.  
  377.         /**
  378.          * Creates a new particle, initializes it according to the registered behaviours and adds it to the emitter.
  379.          */
  380.         _createParticle: function() {
  381.             var particle = this._factory.create();
  382.             for(var i = 0, behaviour; behaviour = this._initializers[i++]; ) { behaviour.setup(this, particle); }
  383.             this.addChild(particle);
  384.         },
  385.  
  386.         /**
  387.          * Updates the given particle by applying registered behaviours to it, increasing its age and incrementing its
  388.          * position according to its velocity. Particles, that have met the end of their lifetime are marked as dead,
  389.          * so they are removed during the next step.
  390.          * @param {IAVRA.PARTICLE.Particle} particle - Particle instance to be udpdated.
  391.          * @param {number} index - The particle's position inside the emitter's children array.
  392.          */
  393.         _updateParticle: function(particle, index) {
  394.             if(++particle.age > particle.life) { particle.dead = true; }
  395.             for(var i = 0, behaviour; behaviour = this._behaviours[i++]; ) { behaviour.update(this, particle, index); }
  396.             particle.oldPosition.x = particle.position.x;
  397.             particle.oldPosition.y = particle.position.y;
  398.             particle.position.x += particle.velocity.x;
  399.             particle.position.y += particle.velocity.y;
  400.         },
  401.  
  402.         /**
  403.          * Destroys a particle, by removing it from the emitter and giving it to the factory, so it can be disposed.
  404.          * @param {number} index - The particle's position inside the emitter's children array.
  405.          */
  406.         _disposeParticle: function(index) {
  407.             this._factory.dispose(this.removeChildAt(index));
  408.         }
  409.  
  410.     });
  411.  
  412.     //=============================================================================
  413.     // IAVRA.PARTICLE.Counter
  414.     //=============================================================================
  415.  
  416.     /**
  417.      * A counter object instructs the emitter, when and how many new particles it has to create. It can do so either
  418.      * when the emitter gets started or at the beginning of every update cycle.
  419.      */
  420.     $.PARTICLE.Counter.prototype = {
  421.  
  422.         /**
  423.          * Gets called, when the emitter is started. Can specify any number of particles to be created at this point.
  424.          * @param {IAVRA.PARTICLE.Emitter} emitter - The enclosing emitter instance.
  425.          * @returns {number|*} Any return value of this function will be treated as an integer by applying a binary
  426.          * OR to it. If this value is higher than 0, the emitter will create that many particles.
  427.          */
  428.         start: function(emitter) {},
  429.  
  430.         /**
  431.          * Gets called, whenever the emitter updates. Can specify any number of particles to be created at this point.
  432.          * @param {IAVRA.PARTICLE.Emitter} emitter - The enclosing emitter instance.
  433.          * @returns {number|*} Any return value of this function will be treated as an integer by applying a binary
  434.          * OR to it. If this value is higher than 0, the emitter will create that many particles.
  435.          */
  436.         update: function(emitter) {}
  437.  
  438.     };
  439.  
  440.     //=============================================================================
  441.     // IAVRA.PARTICLE.Behaviour
  442.     //=============================================================================
  443.  
  444.     /**
  445.      * A behaviour object modifies the way particles are created and updated. It can define both a setup and/or update
  446.      * function and will automatically be called at the appropiate times.
  447.      */
  448.     $.PARTICLE.Behaviour.prototype = {
  449.  
  450.         /**
  451.          * Gets called, whenever a new particle is created. If this is not a function, it will be skipped.
  452.          * @param {IAVRA.PARTICLE.Emitter} emitter - Emitter instance, that created the particle.
  453.          * @param {IAVRA.PARTICLE.Particle} particle - A new particle, that has just been created by the emitter.
  454.          */
  455.         setup: null,
  456.  
  457.         /**
  458.          * Gets called on every update cycle, for each particle. If this is not a function, it will be skipped.
  459.          * @param {IAVRA.PARTICLE.Emitter} emitter - Emitter instance containing the given particle.
  460.          * @param {IAVRA.PARTICLE.Particle} particle - Particle to be updated.
  461.          * @param {number} index - The given particle's position inside the given emitter's children array.
  462.          */
  463.         update: null,
  464.        
  465.         /**
  466.          * Gets called on every update cycle, before particles are updated. If this is not a function, it will be
  467.          * skipped.
  468.          * @param {IAVRA.PARTICLE.Emitter} emitter - Emitter instance containing this behaviour.
  469.          */
  470.         activity: null
  471.  
  472.     };
  473.  
  474.     //=============================================================================
  475.     // IAVRA.PARTICLE.Zone
  476.     //=============================================================================
  477.  
  478.     /**
  479.      * A zone object defines a shape (like a point, line or circle), that can be used by behaviours to create effects
  480.      * revolving around it.
  481.      */
  482.     $.PARTICLE.Zone.prototype = {
  483.  
  484.         /**
  485.          * Tests, whether a given point is located inside the zone.
  486.          * @param {number} x - X coordinate of the point to be tested.
  487.          * @param {number} y - Y coordinate of the point to be tested.
  488.          * @returns {boolean} True, if the given coordinates are located inside the zone. False, otherwise.
  489.          */
  490.         contains: function(x, y) {},
  491.  
  492.         /**
  493.          * Returns a random point inside the zone. This can be used to create a random particle spread inside an area.
  494.          * @returns {PIXI.Point} A random point inside this zone.
  495.          */
  496.         getPoint: function() {},
  497.  
  498.         /**
  499.          * Returns the area of this zone. This may be used to balance multiple zones in regard to their areas.
  500.          * @returns {number} This zone's area.
  501.          */
  502.         getArea: function() {},
  503.  
  504.         /**
  505.          * Handles collision between the given particle and this zone.
  506.          * @param {IAVRA.PARTICLE.Particle} particle - Particle instance to test for collision with this zone.
  507.          * @param {number} [bounce=1] - Bounce coefficient. Values between 0 and 1 cause the particle to lose energy,
  508.          * while values higher than 1 cause it to gain energy. Values lower than 0 might lead to unpredictable results.
  509.          * @returns {boolean} True, if a collision was handled. False, otherwise.
  510.          */
  511.         collide: function(particle, bounce) {}
  512.  
  513.     };
  514.  
  515. })(this.IAVRA || (this.IAVRA = {}));
Add Comment
Please, Sign In to add comment