Juicy.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  1. 'use strict';
  2. /**
  3. * @author Jeremy Dowell <jeremy@codevinsky.com>
  4. * @license {@link http://www.wtfpl.net/txt/copying/|WTFPL}
  5. */
  6. /**
  7. * Creates a new `Juicy` object.
  8. *
  9. * @class Phaser.Plugin.Juicy
  10. * @constructor
  11. *
  12. * @param {Phaser.Game} game Current game instance.
  13. */
  14. Phaser.Plugin.Juicy = function (game) {
  15. Phaser.Plugin.call(this, game);
  16. /**
  17. * @property {Phaser.Rectangle} _boundsCache - A reference to the current world bounds.
  18. * @private
  19. */
  20. this._boundsCache = Phaser.Utils.extend(false, {}, this.game.world.bounds);
  21. /**
  22. * @property {number} _shakeWorldMax - The maximum world shake radius
  23. * @private
  24. */
  25. this._shakeWorldMax = 20;
  26. /**
  27. * @property {number} _shakeWorldTime - The maximum world shake time
  28. * @private
  29. */
  30. this._shakeWorldTime = 0;
  31. /**
  32. * @property {number} _trailCounter - A count of how many trails we're tracking
  33. * @private
  34. */
  35. this._trailCounter = 0;
  36. /**
  37. * @property {object} _overScales - An object containing overscaling configurations
  38. * @private
  39. */
  40. this._overScales = {};
  41. /**
  42. * @property {number} _overScalesCounter - A count of how many overScales we're tracking
  43. * @private
  44. */
  45. this._overScalesCounter = 0;
  46. };
  47. Phaser.Plugin.Juicy.prototype = Object.create(Phaser.Plugin.prototype);
  48. Phaser.Plugin.Juicy.prototype.constructor = Phaser.Plugin.Juicy;
  49. /**
  50. * Creates a new `Juicy.ScreenFlash` object.
  51. *
  52. * @class Phaser.Plugin.Juicy.ScreenFlash
  53. * @constructor
  54. *
  55. * @param {Phaser.Game} game - Current game instance.
  56. * @param {string} color='white' - The color to flash the screen.
  57. * @memberof Phaser.Plugin.Juicy
  58. */
  59. Phaser.Plugin.Juicy.ScreenFlash = function(game, color) {
  60. color = color || 'white';
  61. var bmd = game.add.bitmapData(game.width, game.height);
  62. bmd.ctx.fillStyle = color;
  63. bmd.ctx.fillRect(0,0, game.width, game.height);
  64. Phaser.Sprite.call(this, game, 0,0, bmd);
  65. this.alpha = 0;
  66. };
  67. Phaser.Plugin.Juicy.ScreenFlash.prototype = Object.create(Phaser.Sprite.prototype);
  68. Phaser.Plugin.Juicy.ScreenFlash.prototype.constructor = Phaser.Plugin.Juicy.ScreenFlash;
  69. /**
  70. * Flashes the screen
  71. *
  72. * @param {number} [maxAlpha=1] - The maximum alpha to flash the screen to
  73. * @param {number} [duration=100] - The duration of the flash in milliseconds
  74. * @method Phaser.Plugin.Juicy.ScreenFlash#flash
  75. * @memberof Phaser.Plugin.Juicy.ScreenFlash
  76. */
  77. Phaser.Plugin.Juicy.ScreenFlash.prototype.flash = function(maxAlpha, duration) {
  78. maxAlpha = maxAlpha || 1;
  79. duration = duration || 100;
  80. var flashTween = this.game.add.tween(this).to({alpha: maxAlpha}, 100, Phaser.Easing.Bounce.InOut, true,0, 0, true);
  81. flashTween.onComplete.add(function() {
  82. this.alpha = 0;
  83. }, this);
  84. };
  85. /**
  86. * Creates a new `Juicy.Trail` object.
  87. *
  88. * @class Phaser.Plugin.Juicy.Trail
  89. * @constructor
  90. *
  91. * @param {Phaser.Game} game - Current game instance.
  92. * @param {number} [trailLength=100] - The length of the trail
  93. * @param {number} [color=0xFFFFFF] - The color of the trail
  94. * @memberof Phaser.Plugin.Juicy
  95. */
  96. Phaser.Plugin.Juicy.Trail = function(game, trailLength, color) {
  97. Phaser.Graphics.call(this, game, 0,0);
  98. /**
  99. * @property {Phaser.Sprite} target - The target sprite whose movement we want to create the trail from
  100. */
  101. this.target = null;
  102. /**
  103. * @property {number} trailLength - The number of segments to use to create the trail
  104. */
  105. this.trailLength = trailLength || 100;
  106. /**
  107. * @property {number} trailWidth - The width of the trail
  108. */
  109. this.trailWidth = 15.0;
  110. /**
  111. * @property {boolean} trailScale - Whether or not to taper the trail towards the end
  112. */
  113. this.trailScaling = false;
  114. /**
  115. * @property {Phaser.Sprite} trailColor - The color of the trail
  116. */
  117. this.trailColor = color || 0xFFFFFF;
  118. /**
  119. * @property {Array<Phaser.Point>} _segments - A historical collection of the previous position of the target
  120. * @private
  121. */
  122. this._segments = [];
  123. /**
  124. * @property {Array<number>} _verts - A collection of vertices created from _segments
  125. * @private
  126. */
  127. this._verts = [];
  128. /**
  129. * @property {Array<Phaser.Point>} _segments - A collection of indices created from _verts
  130. * @private
  131. */
  132. this._indices = [];
  133. };
  134. Phaser.Plugin.Juicy.Trail.prototype = Object.create(Phaser.Graphics.prototype);
  135. Phaser.Plugin.Juicy.Trail.prototype.constructor = Phaser.Plugin.Juicy.Trail;
  136. /**
  137. * Updates the Trail if a target is set
  138. *
  139. * @method Phaser.Plugin.Juicy.Trail#update
  140. * @memberof Phaser.Plugin.Juicy.Trail
  141. */
  142. Phaser.Plugin.Juicy.Trail.prototype.update = function() {
  143. if(this.target) {
  144. this.x = this.target.x;
  145. this.y = this.target.y;
  146. this.addSegment(this.target.x, this.target.y);
  147. this.redrawSegments(this.target.x, this.target.y);
  148. }
  149. };
  150. /**
  151. * Adds a segment to the segments list and culls the list if it is too long
  152. *
  153. * @param {number} [x] - The x position of the point
  154. * @param {number} [y] - The y position of the point
  155. *
  156. * @method Phaser.Plugin.Juicy.Trail#addSegment
  157. * @memberof Phaser.Plugin.Juicy.Trail
  158. */
  159. Phaser.Plugin.Juicy.Trail.prototype.addSegment = function(x, y) {
  160. var segment;
  161. while(this._segments.length > this.trailLength) {
  162. segment = this._segments.shift();
  163. }
  164. if(!segment) {
  165. segment = new Phaser.Point();
  166. }
  167. segment.x = x;
  168. segment.y = y;
  169. this._segments.push(segment);
  170. };
  171. /**
  172. * Creates and draws the triangle trail from segments
  173. *
  174. * @param {number} [offsetX] - The x position of the object
  175. * @param {number} [offsetY] - The y position of the object
  176. *
  177. * @method Phaser.Plugin.Juicy.Trail#redrawSegments
  178. * @memberof Phaser.Plugin.Juicy.Trail
  179. */
  180. Phaser.Plugin.Juicy.Trail.prototype.redrawSegments = function(offsetX, offsetY) {
  181. this.clear();
  182. var s1, // current segment
  183. s2, // previous segment
  184. vertIndex = 0, // keeps track of which vertex index we're at
  185. offset, // temporary storage for amount to extend line outwards, bigger = wider
  186. ang, //temporary storage of the inter-segment angles
  187. sin = 0, // as above
  188. cos = 0; // again as above
  189. // first we make sure that the vertice list is the same length as we we want
  190. // each segment (except the first) will create to vertices with two values each
  191. if (this._verts.length !== (this._segments.length -1) * 4) {
  192. // if it's not correct, we clear the entire list
  193. this._verts = [];
  194. }
  195. // now we loop over all the segments, the list has the "youngest" segment at the end
  196. var prevAng = 0;
  197. for(var j = 0; j < this._segments.length; ++j) {
  198. // store the active segment for convenience
  199. s1 = this._segments[j];
  200. // if there's a previous segment, time to do some math
  201. if(s2) {
  202. // we calc the angle between the two segments
  203. // the result will be in radians, so adding half of pi will "turn" the angle 90 degrees
  204. // that means we can use the sin and cos values to "expand" the line outwards
  205. ang = Math.atan2(s1.y - s2.y, s1.x - s2.x) + Math.PI / 2;
  206. sin = Math.sin(ang);
  207. cos = Math.cos(ang);
  208. // now it's time to creat ethe two vertices that will represent this pair of segments
  209. // using a loop here is probably a bit overkill since it's only two iterations
  210. for(var i = 0; i < 2; ++i) {
  211. // this makes the first segment stand out to the "left" of the line
  212. // annd the second to the right, changing that magic number at the end will alther the line width
  213. offset = ( -0.5 + i / 1) * this.trailWidth;
  214. // if trail scale effect is enabled, we scale down the offset as we move down the list
  215. if(this.trailScaling) {
  216. offset *= j / this._segments.length;
  217. }
  218. // finally we put to values in the vert list
  219. // using the segment coordinates as a base we add the "extended" point
  220. // offsetX and offsetY are used her to move the entire trail
  221. this._verts[vertIndex++] = s1.x + cos * offset - offsetX;
  222. this._verts[vertIndex++] = s1.y + sin * offset - offsetY;
  223. }
  224. }
  225. // finally store the current segment as the previous segment and go for another round
  226. s2 = s1.copyTo({});
  227. }
  228. // we need at least four vertices to draw something
  229. if(this._verts.length >= 8) {
  230. // now, we have a triangle "strip", but flash can't draw that without
  231. // instructions for which vertices to connect, so it's time to make those
  232. // here, we loop over all the vertices and pair them together in triangles
  233. // each group of four vertices forms two triangles
  234. for(var k = 0; k < this._verts.length; k++) {
  235. this._indices[k * 6 + 0] = k * 2 + 0;
  236. this._indices[k * 6 + 1] = k * 2 + 1;
  237. this._indices[k * 6 + 2] = k * 2 + 2;
  238. this._indices[k * 6 + 3] = k * 2 + 1;
  239. this._indices[k * 6 + 4] = k * 2 + 2;
  240. this._indices[k * 6 + 5] = k * 2 + 3;
  241. }
  242. this.beginFill(this.trailColor);
  243. this.drawTriangles(this._verts, this._indices);
  244. this.endFill();
  245. }
  246. };
  247. /**
  248. * Add a Sprite reference to this Plugin.
  249. * All this plugin does is move the Sprite across the screen slowly.
  250. * @type {Phaser.Sprite}
  251. */
  252. /**
  253. * Begins the screen shake effect
  254. *
  255. * @param {number} [duration=20] - The duration of the screen shake
  256. * @param {number} [strength=20] - The strength of the screen shake
  257. *
  258. * @method Phaser.Plugin.Juicy#shake
  259. * @memberof Phaser.Plugin.Juicy
  260. */
  261. Phaser.Plugin.Juicy.prototype.shake = function (duration, strength) {
  262. this._shakeWorldTime = duration || 20;
  263. this._shakeWorldMax = strength || 20;
  264. this.game.world.setBounds(this._boundsCache.x - this._shakeWorldMax, this._boundsCache.y - this._shakeWorldMax, this._boundsCache.width + this._shakeWorldMax, this._boundsCache.height + this._shakeWorldMax);
  265. };
  266. /**
  267. * Creates a 'Juicy.ScreenFlash' object
  268. *
  269. * @param {string} color - The color of the screen flash
  270. *
  271. * @type {Phaser.Plugin.Juicy.ScreenFlash}
  272. */
  273. Phaser.Plugin.Juicy.prototype.createScreenFlash = function(color) {
  274. return new Phaser.Plugin.Juicy.ScreenFlash(this.game, color);
  275. };
  276. /**
  277. * Creates a 'Juicy.Trail' object
  278. *
  279. * @param {number} length - The length of the trail
  280. * @param {number} color - The color of the trail
  281. *
  282. * @type {Phaser.Plugin.Juicy.Trail}
  283. */
  284. Phaser.Plugin.Juicy.prototype.createTrail = function(length, color) {
  285. return new Phaser.Plugin.Juicy.Trail(this.game, length, color);
  286. };
  287. /**
  288. * Creates the over scale effect on the given object
  289. *
  290. * @param {Phaser.Sprite} object - The object to over scale
  291. * @param {number} [scale=1.5] - The scale amount to overscale by
  292. * @param {Phaser.Point} [initialScale=new Phaser.Point(1,1)] - The initial scale of the object
  293. *
  294. */
  295. Phaser.Plugin.Juicy.prototype.overScale = function(object, scale, initialScale) {
  296. scale = scale || 1.5;
  297. var id = this._overScalesCounter++;
  298. initialScale = initialScale || new Phaser.Point(1,1);
  299. var scaleObj = this._overScales[id];
  300. if(!scaleObj) {
  301. scaleObj = {
  302. object: object,
  303. cache: initialScale.copyTo({})
  304. };
  305. }
  306. scaleObj.scale = scale;
  307. this._overScales[id] = scaleObj;
  308. };
  309. /**
  310. * Creates the jelly effect on the given object
  311. *
  312. * @param {Phaser.Sprite} object - The object to gelatinize
  313. * @param {number} [strength=0.2] - The strength of the effect
  314. * @param {number} [delay=0] - The delay of the snap-back tween. 50ms are automaticallly added to whatever the delay amount is.
  315. * @param {Phaser.Point} [initialScale=new Phaser.Point(1,1)] - The initial scale of the object
  316. *
  317. */
  318. Phaser.Plugin.Juicy.prototype.jelly = function(object, strength, delay, initialScale) {
  319. strength = strength || 0.2;
  320. delay = delay || 0;
  321. initialScale = initialScale || new Phaser.Point(1, 1);
  322. this.game.add.tween(object.scale).to({x: initialScale.x + (initialScale.x * strength)}, 50, Phaser.Easing.Quadratic.InOut, true, delay)
  323. .to({x: initialScale.x}, 600, Phaser.Easing.Elastic.Out, true);
  324. this.game.add.tween(object.scale).to({y: initialScale.y + (initialScale.y * strength)}, 50, Phaser.Easing.Quadratic.InOut, true, delay + 50)
  325. .to({y: initialScale.y}, 600, Phaser.Easing.Elastic.Out, true);
  326. };
  327. /**
  328. * Creates the mouse stretch effect on the given object
  329. *
  330. * @param {Phaser.Sprite} object - The object to mouse stretch
  331. * @param {number} [strength=0.5] - The strength of the effect
  332. * @param {Phaser.Point} [initialScale=new Phaser.Point(1,1)] - The initial scale of the object
  333. *
  334. */
  335. Phaser.Plugin.Juicy.prototype.mouseStretch = function(object, strength, initialScale) {
  336. strength = strength || 0.5;
  337. initialScale = initialScale || new Phaser.Point(1,1);
  338. object.scale.x = initialScale.x + (Math.abs(object.x - this.game.input.activePointer.x) / 100) * strength;
  339. object.scale.y = initialScale.y + (initialScale.y * strength) - (object.scale.x * strength);
  340. };
  341. /**
  342. * Runs the core update function and causes screen shake and overscaling effects to occur if they are queued to do so.
  343. *
  344. * @method Phaser.Plugin.Juicy#update
  345. * @memberof Phaser.Plugin.Juicy
  346. */
  347. Phaser.Plugin.Juicy.prototype.update = function () {
  348. var scaleObj;
  349. // Screen Shake
  350. if(this._shakeWorldTime > 0) {
  351. var magnitude = (this._shakeWorldTime / this._shakeWorldMax) * this._shakeWorldMax;
  352. var x = this.game.rnd.integerInRange(-magnitude, magnitude);
  353. var y = this.game.rnd.integerInRange(-magnitude, magnitude);
  354. this.game.camera.x = x;
  355. this.game.camera.y = y;
  356. this._shakeWorldTime--;
  357. if(this._shakeWorldTime <= 0) {
  358. this.game.world.setBounds(this._boundsCache.x, this._boundsCache.x, this._boundsCache.width, this._boundsCache.height);
  359. }
  360. }
  361. // over scales
  362. for(var s in this._overScales) {
  363. if(this._overScales.hasOwnProperty(s)) {
  364. scaleObj = this._overScales[s];
  365. if(scaleObj.scale > 0.01) {
  366. scaleObj.object.scale.x = scaleObj.scale * scaleObj.cache.x;
  367. scaleObj.object.scale.y = scaleObj.scale * scaleObj.cache.y;
  368. scaleObj.scale -= this.game.time.elapsed * scaleObj.scale * 0.35;
  369. } else {
  370. scaleObj.object.scale.x = scaleObj.cache.x;
  371. scaleObj.object.scale.y = scaleObj.cache.y;
  372. delete this._overScales[s];
  373. }
  374. }
  375. }
  376. };
  377. // for browserify compatibility
  378. if(typeof module === 'object' && module.exports) {
  379. module.exports = Phaser.Plugin.Juicy;
  380. }
  381. // Draw Triangles Polyfill for back compatibility
  382. if(!Phaser.Graphics.prototype.drawTriangle) {
  383. Phaser.Graphics.prototype.drawTriangle = function(points, cull) {
  384. var triangle = new Phaser.Polygon(points);
  385. if (cull) {
  386. var cameraToFace = new Phaser.Point(this.game.camera.x - points[0].x, this.game.camera.y - points[0].y);
  387. var ab = new Phaser.Point(points[1].x - points[0].x, points[1].y - points[0].y);
  388. var cb = new Phaser.Point(points[1].x - points[2].x, points[1].y - points[2].y);
  389. var faceNormal = cb.cross(ab);
  390. if (cameraToFace.dot(faceNormal) > 0) {
  391. this.drawPolygon(triangle);
  392. }
  393. } else {
  394. this.drawPolygon(triangle);
  395. }
  396. return;
  397. };
  398. /*
  399. * Draws {Phaser.Polygon} triangles
  400. *
  401. * @param {Array<Phaser.Point>|Array<number>} vertices - An array of Phaser.Points or numbers that make up the vertices of the triangles
  402. * @param {Array<number>} {indices=null} - An array of numbers that describe what order to draw the vertices in
  403. * @param {boolean} [cull=false] - Should we check if the triangle is back-facing
  404. * @method Phaser.Graphics.prototype.drawTriangles
  405. */
  406. Phaser.Graphics.prototype.drawTriangles = function(vertices, indices, cull) {
  407. var point1 = new Phaser.Point(),
  408. point2 = new Phaser.Point(),
  409. point3 = new Phaser.Point(),
  410. points = [],
  411. i;
  412. if (!indices) {
  413. if(vertices[0] instanceof Phaser.Point) {
  414. for(i = 0; i < vertices.length / 3; i++) {
  415. this.drawTriangle([vertices[i * 3], vertices[i * 3 + 1], vertices[i * 3 + 2]], cull);
  416. }
  417. } else {
  418. for (i = 0; i < vertices.length / 6; i++) {
  419. point1.x = vertices[i * 6 + 0];
  420. point1.y = vertices[i * 6 + 1];
  421. point2.x = vertices[i * 6 + 2];
  422. point2.y = vertices[i * 6 + 3];
  423. point3.x = vertices[i * 6 + 4];
  424. point3.y = vertices[i * 6 + 5];
  425. this.drawTriangle([point1, point2, point3], cull);
  426. }
  427. }
  428. } else {
  429. if(vertices[0] instanceof Phaser.Point) {
  430. for(i = 0; i < indices.length /3; i++) {
  431. points.push(vertices[indices[i * 3 ]]);
  432. points.push(vertices[indices[i * 3 + 1]]);
  433. points.push(vertices[indices[i * 3 + 2]]);
  434. if(points.length === 3) {
  435. this.drawTriangle(points, cull);
  436. points = [];
  437. }
  438. }
  439. } else {
  440. for (i = 0; i < indices.length; i++) {
  441. point1.x = vertices[indices[i] * 2];
  442. point1.y = vertices[indices[i] * 2 + 1];
  443. points.push(point1.copyTo({}));
  444. if (points.length === 3) {
  445. this.drawTriangle(points, cull);
  446. points = [];
  447. }
  448. }
  449. }
  450. }
  451. };
  452. }