'use strict'; /** * @author Jeremy Dowell <jeremy@codevinsky.com> * @license {@link http://www.wtfpl.net/txt/copying/|WTFPL} */ /** * Creates a new `Juicy` object. * * @class Phaser.Plugin.Juicy * @constructor * * @param {Phaser.Game} game Current game instance. */ Phaser.Plugin.Juicy = function (game) { Phaser.Plugin.call(this, game); /** * @property {Phaser.Rectangle} _boundsCache - A reference to the current world bounds. * @private */ this._boundsCache = Phaser.Utils.extend(false, {}, this.game.world.bounds); /** * @property {number} _shakeWorldMax - The maximum world shake radius * @private */ this._shakeWorldMax = 20; /** * @property {number} _shakeWorldTime - The maximum world shake time * @private */ this._shakeWorldTime = 0; /** * @property {number} _trailCounter - A count of how many trails we're tracking * @private */ this._trailCounter = 0; /** * @property {object} _overScales - An object containing overscaling configurations * @private */ this._overScales = {}; /** * @property {number} _overScalesCounter - A count of how many overScales we're tracking * @private */ this._overScalesCounter = 0; }; Phaser.Plugin.Juicy.prototype = Object.create(Phaser.Plugin.prototype); Phaser.Plugin.Juicy.prototype.constructor = Phaser.Plugin.Juicy; /** * Creates a new `Juicy.ScreenFlash` object. * * @class Phaser.Plugin.Juicy.ScreenFlash * @constructor * * @param {Phaser.Game} game - Current game instance. * @param {string} color='white' - The color to flash the screen. * @memberof Phaser.Plugin.Juicy */ Phaser.Plugin.Juicy.ScreenFlash = function(game, color) { color = color || 'white'; var bmd = game.add.bitmapData(game.width, game.height); bmd.ctx.fillStyle = color; bmd.ctx.fillRect(0,0, game.width, game.height); Phaser.Sprite.call(this, game, 0,0, bmd); this.alpha = 0; }; Phaser.Plugin.Juicy.ScreenFlash.prototype = Object.create(Phaser.Sprite.prototype); Phaser.Plugin.Juicy.ScreenFlash.prototype.constructor = Phaser.Plugin.Juicy.ScreenFlash; /** * Flashes the screen * * @param {number} [maxAlpha=1] - The maximum alpha to flash the screen to * @param {number} [duration=100] - The duration of the flash in milliseconds * @method Phaser.Plugin.Juicy.ScreenFlash#flash * @memberof Phaser.Plugin.Juicy.ScreenFlash */ Phaser.Plugin.Juicy.ScreenFlash.prototype.flash = function(maxAlpha, duration) { maxAlpha = maxAlpha || 1; duration = duration || 100; var flashTween = this.game.add.tween(this).to({alpha: maxAlpha}, 100, Phaser.Easing.Bounce.InOut, true,0, 0, true); flashTween.onComplete.add(function() { this.alpha = 0; }, this); }; /** * Creates a new `Juicy.Trail` object. * * @class Phaser.Plugin.Juicy.Trail * @constructor * * @param {Phaser.Game} game - Current game instance. * @param {number} [trailLength=100] - The length of the trail * @param {number} [color=0xFFFFFF] - The color of the trail * @memberof Phaser.Plugin.Juicy */ Phaser.Plugin.Juicy.Trail = function(game, trailLength, color) { Phaser.Graphics.call(this, game, 0,0); /** * @property {Phaser.Sprite} target - The target sprite whose movement we want to create the trail from */ this.target = null; /** * @property {number} trailLength - The number of segments to use to create the trail */ this.trailLength = trailLength || 100; /** * @property {number} trailWidth - The width of the trail */ this.trailWidth = 15.0; /** * @property {boolean} trailScale - Whether or not to taper the trail towards the end */ this.trailScaling = false; /** * @property {Phaser.Sprite} trailColor - The color of the trail */ this.trailColor = color || 0xFFFFFF; /** * @property {Array<Phaser.Point>} _segments - A historical collection of the previous position of the target * @private */ this._segments = []; /** * @property {Array<number>} _verts - A collection of vertices created from _segments * @private */ this._verts = []; /** * @property {Array<Phaser.Point>} _segments - A collection of indices created from _verts * @private */ this._indices = []; }; Phaser.Plugin.Juicy.Trail.prototype = Object.create(Phaser.Graphics.prototype); Phaser.Plugin.Juicy.Trail.prototype.constructor = Phaser.Plugin.Juicy.Trail; /** * Updates the Trail if a target is set * * @method Phaser.Plugin.Juicy.Trail#update * @memberof Phaser.Plugin.Juicy.Trail */ Phaser.Plugin.Juicy.Trail.prototype.update = function() { if(this.target) { this.x = this.target.x; this.y = this.target.y; this.addSegment(this.target.x, this.target.y); this.redrawSegments(this.target.x, this.target.y); } }; /** * Adds a segment to the segments list and culls the list if it is too long * * @param {number} [x] - The x position of the point * @param {number} [y] - The y position of the point * * @method Phaser.Plugin.Juicy.Trail#addSegment * @memberof Phaser.Plugin.Juicy.Trail */ Phaser.Plugin.Juicy.Trail.prototype.addSegment = function(x, y) { var segment; while(this._segments.length > this.trailLength) { segment = this._segments.shift(); } if(!segment) { segment = new Phaser.Point(); } segment.x = x; segment.y = y; this._segments.push(segment); }; /** * Creates and draws the triangle trail from segments * * @param {number} [offsetX] - The x position of the object * @param {number} [offsetY] - The y position of the object * * @method Phaser.Plugin.Juicy.Trail#redrawSegments * @memberof Phaser.Plugin.Juicy.Trail */ Phaser.Plugin.Juicy.Trail.prototype.redrawSegments = function(offsetX, offsetY) { this.clear(); var s1, // current segment s2, // previous segment vertIndex = 0, // keeps track of which vertex index we're at offset, // temporary storage for amount to extend line outwards, bigger = wider ang, //temporary storage of the inter-segment angles sin = 0, // as above cos = 0; // again as above // first we make sure that the vertice list is the same length as we we want // each segment (except the first) will create to vertices with two values each if (this._verts.length !== (this._segments.length -1) * 4) { // if it's not correct, we clear the entire list this._verts = []; } // now we loop over all the segments, the list has the "youngest" segment at the end var prevAng = 0; for(var j = 0; j < this._segments.length; ++j) { // store the active segment for convenience s1 = this._segments[j]; // if there's a previous segment, time to do some math if(s2) { // we calculate the angle between the two segments // the result will be in radians, so adding half of pi will "turn" the angle 90 degrees // that means we can use the sin and cos values to "expand" the line outwards ang = Math.atan2(s1.y - s2.y, s1.x - s2.x) + Math.PI / 2; sin = Math.sin(ang); cos = Math.cos(ang); // now it's time to creat ethe two vertices that will represent this pair of segments // using a loop here is probably a bit overkill since it's only two iterations for(var i = 0; i < 2; ++i) { // this makes the first segment stand out to the "left" of the line // annd the second to the right, changing that magic number at the end will alther the line width offset = ( -0.5 + i / 1) * this.trailWidth; // if trail scale effect is enabled, we scale down the offset as we move down the list if(this.trailScaling) { offset *= j / this._segments.length; } // finally we put to values in the vert list // using the segment coordinates as a base we add the "extended" point // offsetX and offsetY are used her to move the entire trail this._verts[vertIndex++] = s1.x + cos * offset - offsetX; this._verts[vertIndex++] = s1.y + sin * offset - offsetY; } } // finally store the current segment as the previous segment and go for another round s2 = s1.copyTo({}); } // we need at least four vertices to draw something if(this._verts.length >= 8) { // now, we have a triangle "strip", but flash can't draw that without // instructions for which vertices to connect, so it's time to make those // here, we loop over all the vertices and pair them together in triangles // each group of four vertices forms two triangles for(var k = 0; k < this._verts.length; k++) { this._indices[k * 6 + 0] = k * 2 + 0; this._indices[k * 6 + 1] = k * 2 + 1; this._indices[k * 6 + 2] = k * 2 + 2; this._indices[k * 6 + 3] = k * 2 + 1; this._indices[k * 6 + 4] = k * 2 + 2; this._indices[k * 6 + 5] = k * 2 + 3; } this.beginFill(this.trailColor); this.drawTriangles(this._verts, this._indices); this.endFill(); } }; /** * Add a Sprite reference to this Plugin. * All this plugin does is move the Sprite across the screen slowly. * @type {Phaser.Sprite} */ /** * Begins the screen shake effect * * @param {number} [duration=20] - The duration of the screen shake * @param {number} [strength=20] - The strength of the screen shake * * @method Phaser.Plugin.Juicy#shake * @memberof Phaser.Plugin.Juicy */ Phaser.Plugin.Juicy.prototype.shake = function (duration, strength) { this._shakeWorldTime = duration || 20; this._shakeWorldMax = strength || 20; this.game.world.setBounds(this._boundsCache.x - this._shakeWorldMax, this._boundsCache.y - this._shakeWorldMax, this._boundsCache.width + this._shakeWorldMax, this._boundsCache.height + this._shakeWorldMax); }; /** * Creates a 'Juicy.ScreenFlash' object * * @param {string} color - The color of the screen flash * * @type {Phaser.Plugin.Juicy.ScreenFlash} */ Phaser.Plugin.Juicy.prototype.createScreenFlash = function(color) { return new Phaser.Plugin.Juicy.ScreenFlash(this.game, color); }; /** * Creates a 'Juicy.Trail' object * * @param {number} length - The length of the trail * @param {number} color - The color of the trail * * @type {Phaser.Plugin.Juicy.Trail} */ Phaser.Plugin.Juicy.prototype.createTrail = function(length, color) { return new Phaser.Plugin.Juicy.Trail(this.game, length, color); }; /** * Creates the over scale effect on the given object * * @param {Phaser.Sprite} object - The object to over scale * @param {number} [scale=1.5] - The scale amount to overscale by * @param {Phaser.Point} [initialScale=new Phaser.Point(1,1)] - The initial scale of the object * */ Phaser.Plugin.Juicy.prototype.overScale = function(object, scale, initialScale) { scale = scale || 1.5; var id = this._overScalesCounter++; initialScale = initialScale || new Phaser.Point(1,1); var scaleObj = this._overScales[id]; if(!scaleObj) { scaleObj = { object: object, cache: initialScale.copyTo({}) }; } scaleObj.scale = scale; this._overScales[id] = scaleObj; }; /** * Creates the jelly effect on the given object * * @param {Phaser.Sprite} object - The object to gelatinize * @param {number} [strength=0.2] - The strength of the effect * @param {number} [delay=0] - The delay of the snap-back tween. 50ms are automaticallly added to whatever the delay amount is. * @param {Phaser.Point} [initialScale=new Phaser.Point(1,1)] - The initial scale of the object * */ Phaser.Plugin.Juicy.prototype.jelly = function(object, strength, delay, initialScale) { strength = strength || 0.2; delay = delay || 0; initialScale = initialScale || new Phaser.Point(1, 1); this.game.add.tween(object.scale).to({x: initialScale.x + (initialScale.x * strength)}, 50, Phaser.Easing.Quadratic.InOut, true, delay) .to({x: initialScale.x}, 600, Phaser.Easing.Elastic.Out, true); this.game.add.tween(object.scale).to({y: initialScale.y + (initialScale.y * strength)}, 50, Phaser.Easing.Quadratic.InOut, true, delay + 50) .to({y: initialScale.y}, 600, Phaser.Easing.Elastic.Out, true); }; /** * Creates the mouse stretch effect on the given object * * @param {Phaser.Sprite} object - The object to mouse stretch * @param {number} [strength=0.5] - The strength of the effect * @param {Phaser.Point} [initialScale=new Phaser.Point(1,1)] - The initial scale of the object * */ Phaser.Plugin.Juicy.prototype.mouseStretch = function(object, strength, initialScale) { strength = strength || 0.5; initialScale = initialScale || new Phaser.Point(1,1); object.scale.x = initialScale.x + (Math.abs(object.x - this.game.input.activePointer.x) / 100) * strength; object.scale.y = initialScale.y + (initialScale.y * strength) - (object.scale.x * strength); }; /** * Runs the core update function and causes screen shake and overscaling effects to occur if they are queued to do so. * * @method Phaser.Plugin.Juicy#update * @memberof Phaser.Plugin.Juicy */ Phaser.Plugin.Juicy.prototype.update = function () { var scaleObj; // Screen Shake if(this._shakeWorldTime > 0) { var magnitude = (this._shakeWorldTime / this._shakeWorldMax) * this._shakeWorldMax; var x = this.game.rnd.integerInRange(-magnitude, magnitude); var y = this.game.rnd.integerInRange(-magnitude, magnitude); this.game.camera.x = x; this.game.camera.y = y; this._shakeWorldTime--; if(this._shakeWorldTime <= 0) { this.game.world.setBounds(this._boundsCache.x, this._boundsCache.x, this._boundsCache.width, this._boundsCache.height); } } // over scales for(var s in this._overScales) { if(this._overScales.hasOwnProperty(s)) { scaleObj = this._overScales[s]; if(scaleObj.scale > 0.01) { scaleObj.object.scale.x = scaleObj.scale * scaleObj.cache.x; scaleObj.object.scale.y = scaleObj.scale * scaleObj.cache.y; scaleObj.scale -= this.game.time.elapsed * scaleObj.scale * 0.35; } else { scaleObj.object.scale.x = scaleObj.cache.x; scaleObj.object.scale.y = scaleObj.cache.y; delete this._overScales[s]; } } } }; // for browserify compatibility if(typeof module === 'object' && module.exports) { module.exports = Phaser.Plugin.Juicy; } // Draw Triangles Polyfill for back compatibility if(!Phaser.Graphics.prototype.drawTriangle) { Phaser.Graphics.prototype.drawTriangle = function(points, cull) { var triangle = new Phaser.Polygon(points); if (cull) { var cameraToFace = new Phaser.Point(this.game.camera.x - points[0].x, this.game.camera.y - points[0].y); var ab = new Phaser.Point(points[1].x - points[0].x, points[1].y - points[0].y); var cb = new Phaser.Point(points[1].x - points[2].x, points[1].y - points[2].y); var faceNormal = cb.cross(ab); if (cameraToFace.dot(faceNormal) > 0) { this.drawPolygon(triangle); } } else { this.drawPolygon(triangle); } return; }; /* * Draws {Phaser.Polygon} triangles * * @param {Array<Phaser.Point>|Array<number>} vertices - An array of Phaser.Points or numbers that make up the vertices of the triangles * @param {Array<number>} {indices=null} - An array of numbers that describe what order to draw the vertices in * @param {boolean} [cull=false] - Should we check if the triangle is back-facing * @method Phaser.Graphics.prototype.drawTriangles */ Phaser.Graphics.prototype.drawTriangles = function(vertices, indices, cull) { var point1 = new Phaser.Point(), point2 = new Phaser.Point(), point3 = new Phaser.Point(), points = [], i; if (!indices) { if(vertices[0] instanceof Phaser.Point) { for(i = 0; i < vertices.length / 3; i++) { this.drawTriangle([vertices[i * 3], vertices[i * 3 + 1], vertices[i * 3 + 2]], cull); } } else { for (i = 0; i < vertices.length / 6; i++) { point1.x = vertices[i * 6 + 0]; point1.y = vertices[i * 6 + 1]; point2.x = vertices[i * 6 + 2]; point2.y = vertices[i * 6 + 3]; point3.x = vertices[i * 6 + 4]; point3.y = vertices[i * 6 + 5]; this.drawTriangle([point1, point2, point3], cull); } } } else { if(vertices[0] instanceof Phaser.Point) { for(i = 0; i < indices.length /3; i++) { points.push(vertices[indices[i * 3 ]]); points.push(vertices[indices[i * 3 + 1]]); points.push(vertices[indices[i * 3 + 2]]); if(points.length === 3) { this.drawTriangle(points, cull); points = []; } } } else { for (i = 0; i < indices.length; i++) { point1.x = vertices[indices[i] * 2]; point1.y = vertices[indices[i] * 2 + 1]; points.push(point1.copyTo({})); if (points.length === 3) { this.drawTriangle(points, cull); points = []; } } } } }; }