Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
 
 
 
 
 

9082 рядки
310 KiB

  1. /*
  2. Sound.js
  3. ===============
  4. A complete micro library of useful, modular functions that help you load, play, control
  5. and generate sound effects and music for games and interactive applications. All the
  6. code targets the WebAudio API.
  7. */
  8. /*
  9. Fixing the WebAudio API
  10. --------------------------
  11. The WebAudio API is so new that it's API is not consistently implemented properly across
  12. all modern browsers. Thankfully, Chris Wilson's Audio Context Monkey Patch script
  13. normalizes the API for maximum compatibility.
  14. https://github.com/cwilso/AudioContext-MonkeyPatch/blob/gh-pages/AudioContextMonkeyPatch.js
  15. It's included here.
  16. Thank you, Chris!
  17. */
  18. (function (global, exports, perf) {
  19. 'use strict';
  20. function fixSetTarget(param) {
  21. if (!param) // if NYI, just return
  22. return;
  23. if (!param.setTargetAtTime)
  24. param.setTargetAtTime = param.setTargetValueAtTime;
  25. }
  26. if (window.hasOwnProperty('webkitAudioContext') &&
  27. !window.hasOwnProperty('AudioContext')) {
  28. window.AudioContext = webkitAudioContext;
  29. if (!AudioContext.prototype.hasOwnProperty('createGain'))
  30. AudioContext.prototype.createGain = AudioContext.prototype.createGainNode;
  31. if (!AudioContext.prototype.hasOwnProperty('createDelay'))
  32. AudioContext.prototype.createDelay = AudioContext.prototype.createDelayNode;
  33. if (!AudioContext.prototype.hasOwnProperty('createScriptProcessor'))
  34. AudioContext.prototype.createScriptProcessor = AudioContext.prototype.createJavaScriptNode;
  35. if (!AudioContext.prototype.hasOwnProperty('createPeriodicWave'))
  36. AudioContext.prototype.createPeriodicWave = AudioContext.prototype.createWaveTable;
  37. AudioContext.prototype.internal_createGain = AudioContext.prototype.createGain;
  38. AudioContext.prototype.createGain = function() {
  39. var node = this.internal_createGain();
  40. fixSetTarget(node.gain);
  41. return node;
  42. };
  43. AudioContext.prototype.internal_createDelay = AudioContext.prototype.createDelay;
  44. AudioContext.prototype.createDelay = function(maxDelayTime) {
  45. var node = maxDelayTime ? this.internal_createDelay(maxDelayTime) : this.internal_createDelay();
  46. fixSetTarget(node.delayTime);
  47. return node;
  48. };
  49. AudioContext.prototype.internal_createBufferSource = AudioContext.prototype.createBufferSource;
  50. AudioContext.prototype.createBufferSource = function() {
  51. var node = this.internal_createBufferSource();
  52. if (!node.start) {
  53. node.start = function ( when, offset, duration ) {
  54. if ( offset || duration )
  55. this.noteGrainOn( when || 0, offset, duration );
  56. else
  57. this.noteOn( when || 0 );
  58. };
  59. } else {
  60. node.internal_start = node.start;
  61. node.start = function( when, offset, duration ) {
  62. if( typeof duration !== 'undefined' )
  63. node.internal_start( when || 0, offset, duration );
  64. else
  65. node.internal_start( when || 0, offset || 0 );
  66. };
  67. }
  68. if (!node.stop) {
  69. node.stop = function ( when ) {
  70. this.noteOff( when || 0 );
  71. };
  72. } else {
  73. node.internal_stop = node.stop;
  74. node.stop = function( when ) {
  75. node.internal_stop( when || 0 );
  76. };
  77. }
  78. fixSetTarget(node.playbackRate);
  79. return node;
  80. };
  81. AudioContext.prototype.internal_createDynamicsCompressor = AudioContext.prototype.createDynamicsCompressor;
  82. AudioContext.prototype.createDynamicsCompressor = function() {
  83. var node = this.internal_createDynamicsCompressor();
  84. fixSetTarget(node.threshold);
  85. fixSetTarget(node.knee);
  86. fixSetTarget(node.ratio);
  87. fixSetTarget(node.reduction);
  88. fixSetTarget(node.attack);
  89. fixSetTarget(node.release);
  90. return node;
  91. };
  92. AudioContext.prototype.internal_createBiquadFilter = AudioContext.prototype.createBiquadFilter;
  93. AudioContext.prototype.createBiquadFilter = function() {
  94. var node = this.internal_createBiquadFilter();
  95. fixSetTarget(node.frequency);
  96. fixSetTarget(node.detune);
  97. fixSetTarget(node.Q);
  98. fixSetTarget(node.gain);
  99. return node;
  100. };
  101. if (AudioContext.prototype.hasOwnProperty( 'createOscillator' )) {
  102. AudioContext.prototype.internal_createOscillator = AudioContext.prototype.createOscillator;
  103. AudioContext.prototype.createOscillator = function() {
  104. var node = this.internal_createOscillator();
  105. if (!node.start) {
  106. node.start = function ( when ) {
  107. this.noteOn( when || 0 );
  108. };
  109. } else {
  110. node.internal_start = node.start;
  111. node.start = function ( when ) {
  112. node.internal_start( when || 0);
  113. };
  114. }
  115. if (!node.stop) {
  116. node.stop = function ( when ) {
  117. this.noteOff( when || 0 );
  118. };
  119. } else {
  120. node.internal_stop = node.stop;
  121. node.stop = function( when ) {
  122. node.internal_stop( when || 0 );
  123. };
  124. }
  125. if (!node.setPeriodicWave)
  126. node.setPeriodicWave = node.setWaveTable;
  127. fixSetTarget(node.frequency);
  128. fixSetTarget(node.detune);
  129. return node;
  130. };
  131. }
  132. }
  133. if (window.hasOwnProperty('webkitOfflineAudioContext') &&
  134. !window.hasOwnProperty('OfflineAudioContext')) {
  135. window.OfflineAudioContext = webkitOfflineAudioContext;
  136. }
  137. }(window));
  138. /*
  139. Define the audio context
  140. ------------------------
  141. All this code uses a single `AudioContext` If you want to use any of these functions
  142. independently of this file, make sure that have an `AudioContext` called `actx`.
  143. */
  144. var actx = new AudioContext();
  145. /*
  146. sounds
  147. ------
  148. `sounds` is an object that you can use to store all your loaded sound fles.
  149. It also has a helpful `load` method that manages asset loading. You can load sounds at
  150. any time during the game by using the `sounds.load` method. You don't have to use
  151. the `sounds` object or its `load` method, but it's a really convenient way to
  152. work with sound file assets.
  153. Here's how could use the `sound` object to load three sound files from a `sounds` folder and
  154. call a `setup` method when all the files have finished loading:
  155. sounds.load([
  156. "sounds/shoot.wav",
  157. "sounds/music.wav",
  158. "sounds/bounce.mp3"
  159. ]);
  160. sounds.whenLoaded = setup;
  161. You can now access these loaded sounds in your application code like this:
  162. var shoot = sounds["sounds/shoot.wav"],
  163. music = sounds["sounds/music.wav"],
  164. bounce = sounds["sounds/bounce.mp3"];
  165. */
  166. var sounds = {
  167. //Properties to help track the assets being loaded.
  168. toLoad: 0,
  169. loaded: 0,
  170. //File extensions for different types of sounds.
  171. audioExtensions: ["mp3", "ogg", "wav", "webm"],
  172. //The callback function that should run when all assets have loaded.
  173. //Assign this when you load the fonts, like this: `assets.whenLoaded = makeSprites;`.
  174. whenLoaded: undefined,
  175. //The callback function to run after each asset is loaded
  176. onProgress: undefined,
  177. //The callback function to run if an asset fails to load or decode
  178. onFailed: function(source, error) {
  179. throw new Error("Audio could not be loaded: " + source);
  180. },
  181. //The load method creates and loads all the assets. Use it like this:
  182. //`assets.load(["images/anyImage.png", "fonts/anyFont.otf"]);`.
  183. load: function(sources) {
  184. console.log("Loading sounds..");
  185. //Get a reference to this asset object so we can
  186. //refer to it in the `forEach` loop ahead.
  187. var self = this;
  188. //Find the number of files that need to be loaded.
  189. self.toLoad = sources.length;
  190. sources.forEach(function(source){
  191. //Find the file extension of the asset.
  192. var extension = source.split('.').pop();
  193. //#### Sounds
  194. //Load audio files that have file extensions that match
  195. //the `audioExtensions` array.
  196. if (self.audioExtensions.indexOf(extension) !== -1) {
  197. //Create a sound sprite.
  198. var soundSprite = makeSound(source, self.loadHandler.bind(self), true, false, self.onFailed);
  199. //Get the sound file name.
  200. soundSprite.name = source;
  201. //If you just want to extract the file name with the
  202. //extension, you can do it like this:
  203. //soundSprite.name = source.split("/").pop();
  204. //Assign the sound as a property of the assets object so
  205. //we can access it like this: `assets["sounds/sound.mp3"]`.
  206. self[soundSprite.name] = soundSprite;
  207. }
  208. //Display a message if the file type isn't recognized.
  209. else {
  210. console.log("File type not recognized: " + source);
  211. }
  212. });
  213. },
  214. //#### loadHandler
  215. //The `loadHandler` will be called each time an asset finishes loading.
  216. loadHandler: function () {
  217. var self = this;
  218. self.loaded += 1;
  219. if (self.onProgress) {
  220. self.onProgress(100 * self.loaded / self.toLoad);
  221. }
  222. //Check whether everything has loaded.
  223. if (self.toLoad === self.loaded) {
  224. //If it has, run the callback function that was assigned to the `whenLoaded` property
  225. console.log("Sounds finished loading");
  226. //Reset `loaded` and `toLoaded` so we can load more assets
  227. //later if we want to.
  228. self.toLoad = 0;
  229. self.loaded = 0;
  230. self.whenLoaded();
  231. }
  232. }
  233. };
  234. /*
  235. makeSound
  236. ---------
  237. `makeSound` is the function you want to use to load and play sound files.
  238. It creates and returns and WebAudio sound object with lots of useful methods you can
  239. use to control the sound.
  240. You can use it to load a sound like this:
  241. var anySound = makeSound("sounds/anySound.mp3", loadHandler);
  242. The code above will load the sound and then call the `loadHandler`
  243. when the sound has finished loading.
  244. (However, it's more convenient to load the sound file using
  245. the `sounds.load` method described above, so I don't recommend loading sounds
  246. like this unless you need more low-level control.)
  247. After the sound has been loaded you can access and use it like this:
  248. function loadHandler() {
  249. anySound.loop = true;
  250. anySound.pan = 0.8;
  251. anySound.volume = 0.5;
  252. anySound.play();
  253. anySound.pause();
  254. anySound.playFrom(second);
  255. anySound.restart();
  256. anySound.setReverb(2, 2, false);
  257. anySound.setEcho(0.2, 0.2, 0);
  258. anySound.playbackRate = 0.5;
  259. }
  260. For advanced configurations, you can optionally supply `makeSound` with optional 3rd and
  261. 4th arguments:
  262. var anySound = makeSound(source, loadHandler, loadTheSound?, xhrObject);
  263. `loadTheSound?` is a Boolean (true/false) value that, if `false` prevents the sound file
  264. from being loaded. You would only want to set it to `false` like this if you were
  265. using another file loading library to load the sound, and didn't want it to be loaded
  266. twice.
  267. `xhrObject`, the optional 4th argument, is the XHR object that was used to load the sound. Again, you
  268. would only supply this if you were using another file loading library to load the sound,
  269. and that library had generated its own XHR object. If you supply the `xhr` argument, `makeSound`
  270. will skip the file loading step (because you've already done that), but still decode the audio buffer for you.
  271. (If you are loading the sound file using another file loading library, make sure that your sound
  272. files are loaded with the XHR `responseType = "arraybuffer"` option.)
  273. For example, here's how you could use this advanced configuration to decode a sound that you've already loaded
  274. using your own custom loading system:
  275. var soundSprite = makeSound(source, decodeHandler.bind(this), false, xhr);
  276. When the file has finished being decoded, your custom `decodeHandler` will run, which tells you
  277. that the file has finished decoding.
  278. If you're creating more than one sound like this, use counter variables to track the number of sounds
  279. you need to decode, and the number of sounds that have been decoded. When both sets of counters are the
  280. same, you'll know that all your sound files have finished decoding and you can proceed with the rest
  281. of you application. (The [Hexi game engine](https://github.com/kittykatattack/hexi) uses `makeSound` in this way.)
  282. */
  283. function makeSound(source, loadHandler, loadSound, xhr, failHandler) {
  284. //The sound object that this function returns.
  285. var o = {};
  286. //Set the default properties.
  287. o.volumeNode = actx.createGain();
  288. //Create the pan node using the efficient `createStereoPanner`
  289. //method, if it's available.
  290. if (!actx.createStereoPanner) {
  291. o.panNode = actx.createPanner();
  292. } else {
  293. o.panNode = actx.createStereoPanner();
  294. }
  295. o.delayNode = actx.createDelay();
  296. o.feedbackNode = actx.createGain();
  297. o.filterNode = actx.createBiquadFilter();
  298. o.convolverNode = actx.createConvolver();
  299. o.soundNode = null;
  300. o.buffer = null;
  301. o.source = source;
  302. o.loop = false;
  303. o.playing = false;
  304. //The function that should run when the sound is loaded.
  305. o.loadHandler = undefined;
  306. //Values for the `pan` and `volume` getters/setters.
  307. o.panValue = 0;
  308. o.volumeValue = 1;
  309. //Values to help track and set the start and pause times.
  310. o.startTime = 0;
  311. o.startOffset = 0;
  312. //Set the playback rate.
  313. o.playbackRate = 1;
  314. //Echo properties.
  315. o.echo = false;
  316. o.delayValue = 0.3;
  317. o.feebackValue = 0.3;
  318. o.filterValue = 0;
  319. //Reverb properties
  320. o.reverb = false;
  321. o.reverbImpulse = null;
  322. //The sound object's methods.
  323. o.play = function() {
  324. //Set the start time (it will be `0` when the sound
  325. //first starts.
  326. o.startTime = actx.currentTime;
  327. //Create a sound node.
  328. o.soundNode = actx.createBufferSource();
  329. //Set the sound node's buffer property to the loaded sound.
  330. o.soundNode.buffer = o.buffer;
  331. //Set the playback rate
  332. o.soundNode.playbackRate.value = this.playbackRate;
  333. //Connect the sound to the pan, connect the pan to the
  334. //volume, and connect the volume to the destination.
  335. o.soundNode.connect(o.volumeNode);
  336. //If there's no reverb, bypass the convolverNode
  337. if (o.reverb === false) {
  338. o.volumeNode.connect(o.panNode);
  339. }
  340. //If there is reverb, connect the `convolverNode` and apply
  341. //the impulse response
  342. else {
  343. o.volumeNode.connect(o.convolverNode);
  344. o.convolverNode.connect(o.panNode);
  345. o.convolverNode.buffer = o.reverbImpulse;
  346. }
  347. //Connect the `panNode` to the destination to complete the chain.
  348. o.panNode.connect(actx.destination);
  349. //Add optional echo.
  350. if (o.echo) {
  351. //Set the values.
  352. o.feedbackNode.gain.value = o.feebackValue;
  353. o.delayNode.delayTime.value = o.delayValue;
  354. o.filterNode.frequency.value = o.filterValue;
  355. //Create the delay loop, with optional filtering.
  356. o.delayNode.connect(o.feedbackNode);
  357. if (o.filterValue > 0) {
  358. o.feedbackNode.connect(o.filterNode);
  359. o.filterNode.connect(o.delayNode);
  360. } else {
  361. o.feedbackNode.connect(o.delayNode);
  362. }
  363. //Capture the sound from the main node chain, send it to the
  364. //delay loop, and send the final echo effect to the `panNode` which
  365. //will then route it to the destination.
  366. o.volumeNode.connect(o.delayNode);
  367. o.delayNode.connect(o.panNode);
  368. }
  369. //Will the sound loop? This can be `true` or `false`.
  370. o.soundNode.loop = o.loop;
  371. //Finally, use the `start` method to play the sound.
  372. //The start time will either be `0`,
  373. //or a later time if the sound was paused.
  374. o.soundNode.start(
  375. 0, o.startOffset % o.buffer.duration
  376. );
  377. //Set `playing` to `true` to help control the
  378. //`pause` and `restart` methods.
  379. o.playing = true;
  380. };
  381. o.pause = function() {
  382. //Pause the sound if it's playing, and calculate the
  383. //`startOffset` to save the current position.
  384. if (o.playing) {
  385. o.soundNode.stop(0);
  386. o.startOffset += actx.currentTime - o.startTime;
  387. o.playing = false;
  388. }
  389. };
  390. o.restart = function() {
  391. //Stop the sound if it's playing, reset the start and offset times,
  392. //then call the `play` method again.
  393. if (o.playing) {
  394. o.soundNode.stop(0);
  395. }
  396. o.startOffset = 0;
  397. o.play();
  398. };
  399. o.playFrom = function(value) {
  400. if (o.playing) {
  401. o.soundNode.stop(0);
  402. }
  403. o.startOffset = value;
  404. o.play();
  405. };
  406. o.setEcho = function(delayValue, feedbackValue, filterValue) {
  407. if (delayValue === undefined) delayValue = 0.3;
  408. if (feedbackValue === undefined) feedbackValue = 0.3;
  409. if (filterValue === undefined) filterValue = 0;
  410. o.delayValue = delayValue;
  411. o.feebackValue = feedbackValue;
  412. o.filterValue = filterValue;
  413. o.echo = true;
  414. };
  415. o.setReverb = function(duration, decay, reverse) {
  416. if (duration === undefined) duration = 2;
  417. if (decay === undefined) decay = 2;
  418. if (reverse === undefined) reverse = false;
  419. o.reverbImpulse = impulseResponse(duration, decay, reverse, actx);
  420. o.reverb = true;
  421. };
  422. //A general purpose `fade` method for fading sounds in or out.
  423. //The first argument is the volume that the sound should
  424. //fade to, and the second value is the duration, in seconds,
  425. //that the fade should last.
  426. o.fade = function(endValue, durationInSeconds) {
  427. if (o.playing) {
  428. o.volumeNode.gain.linearRampToValueAtTime(
  429. o.volumeNode.gain.value, actx.currentTime
  430. );
  431. o.volumeNode.gain.linearRampToValueAtTime(
  432. endValue, actx.currentTime + durationInSeconds
  433. );
  434. }
  435. };
  436. //Fade a sound in, from an initial volume level of zero.
  437. o.fadeIn = function(durationInSeconds) {
  438. //Set the volume to 0 so that you can fade
  439. //in from silence
  440. o.volumeNode.gain.value = 0;
  441. o.fade(1, durationInSeconds);
  442. };
  443. //Fade a sound out, from its current volume level to zero.
  444. o.fadeOut = function(durationInSeconds) {
  445. o.fade(0, durationInSeconds);
  446. };
  447. //Volume and pan getters/setters.
  448. Object.defineProperties(o, {
  449. volume: {
  450. get: function() {
  451. return o.volumeValue;
  452. },
  453. set: function(value) {
  454. o.volumeNode.gain.value = value;
  455. o.volumeValue = value;
  456. },
  457. enumerable: true, configurable: true
  458. },
  459. //The pan node uses the high-efficiency stereo panner, if it's
  460. //available. But, because this is a new addition to the
  461. //WebAudio spec, it might not be available on all browsers.
  462. //So the code checks for this and uses the older 3D panner
  463. //if 2D isn't available.
  464. pan: {
  465. get: function() {
  466. if (!actx.createStereoPanner) {
  467. return o.panValue;
  468. } else {
  469. return o.panNode.pan.value;
  470. }
  471. },
  472. set: function(value) {
  473. if (!actx.createStereoPanner) {
  474. //Panner objects accept x, y and z coordinates for 3D
  475. //sound. However, because we're only doing 2D left/right
  476. //panning we're only interested in the x coordinate,
  477. //the first one. However, for a natural effect, the z
  478. //value also has to be set proportionately.
  479. var x = value,
  480. y = 0,
  481. z = 1 - Math.abs(x);
  482. o.panNode.setPosition(x, y, z);
  483. o.panValue = value;
  484. } else {
  485. o.panNode.pan.value = value;
  486. }
  487. },
  488. enumerable: true, configurable: true
  489. }
  490. });
  491. //Optionally Load and decode the sound.
  492. if (loadSound) {
  493. this.loadSound(o, source, loadHandler, failHandler);
  494. }
  495. //Optionally, if you've loaded the sound using some other loader, just decode the sound
  496. if (xhr) {
  497. this.decodeAudio(o, xhr, loadHandler, failHandler);
  498. }
  499. //Return the sound object.
  500. return o;
  501. }
  502. //The `loadSound` function loads the sound file using XHR
  503. function loadSound(o, source, loadHandler, failHandler) {
  504. var xhr = new XMLHttpRequest();
  505. //Use xhr to load the sound file.
  506. xhr.open("GET", source, true);
  507. xhr.responseType = "arraybuffer";
  508. //When the sound has finished loading, decode it using the
  509. //`decodeAudio` function (which you'll see ahead)
  510. xhr.addEventListener("load", decodeAudio.bind(this, o, xhr, loadHandler, failHandler));
  511. //Send the request to load the file.
  512. xhr.send();
  513. }
  514. //The `decodeAudio` function decodes the audio file for you and
  515. //launches the `loadHandler` when it's done
  516. function decodeAudio(o, xhr, loadHandler, failHandler) {
  517. //Decode the sound and store a reference to the buffer.
  518. actx.decodeAudioData(
  519. xhr.response,
  520. function(buffer) {
  521. o.buffer = buffer;
  522. o.hasLoaded = true;
  523. //This next bit is optional, but important.
  524. //If you have a load manager in your game, call it here so that
  525. //the sound is registered as having loaded.
  526. if (loadHandler) {
  527. loadHandler();
  528. }
  529. },
  530. function(error) {
  531. if (failHandler) failHandler(o.source, error);
  532. }
  533. );
  534. }
  535. /*
  536. soundEffect
  537. -----------
  538. The `soundEffect` function let's you generate your sounds and musical notes from scratch
  539. (Reverb effect requires the `impulseResponse` function that you'll see further ahead in this file)
  540. To create a custom sound effect, define all the parameters that characterize your sound. Here's how to
  541. create a laser shooting sound:
  542. soundEffect(
  543. 1046.5, //frequency
  544. 0, //attack
  545. 0.3, //decay
  546. "sawtooth", //waveform
  547. 1, //Volume
  548. -0.8, //pan
  549. 0, //wait before playing
  550. 1200, //pitch bend amount
  551. false, //reverse bend
  552. 0, //random pitch range
  553. 25, //dissonance
  554. [0.2, 0.2, 2000], //echo: [delay, feedback, filter]
  555. undefined //reverb: [duration, decay, reverse?]
  556. 3 //Maximum duration of sound, in seconds
  557. );
  558. Experiment by changing these parameters to see what kinds of effects you can create, and build
  559. your own library of custom sound effects for games.
  560. */
  561. function soundEffect(
  562. frequencyValue, //The sound's fequency pitch in Hertz
  563. attack, //The time, in seconds, to fade the sound in
  564. decay, //The time, in seconds, to fade the sound out
  565. type, //waveform type: "sine", "triangle", "square", "sawtooth"
  566. volumeValue, //The sound's maximum volume
  567. panValue, //The speaker pan. left: -1, middle: 0, right: 1
  568. wait, //The time, in seconds, to wait before playing the sound
  569. pitchBendAmount, //The number of Hz in which to bend the sound's pitch down
  570. reverse, //If `reverse` is true the pitch will bend up
  571. randomValue, //A range, in Hz, within which to randomize the pitch
  572. dissonance, //A value in Hz. It creates 2 dissonant frequencies above and below the target pitch
  573. echo, //An array: [delayTimeInSeconds, feedbackTimeInSeconds, filterValueInHz]
  574. reverb, //An array: [durationInSeconds, decayRateInSeconds, reverse]
  575. timeout //A number, in seconds, which is the maximum duration for sound effects
  576. ) {
  577. //Set the default values
  578. if (frequencyValue === undefined) frequencyValue = 200;
  579. if (attack === undefined) attack = 0;
  580. if (decay === undefined) decay = 1;
  581. if (type === undefined) type = "sine";
  582. if (volumeValue === undefined) volumeValue = 1;
  583. if (panValue === undefined) panValue = 0;
  584. if (wait === undefined) wait = 0;
  585. if (pitchBendAmount === undefined) pitchBendAmount = 0;
  586. if (reverse === undefined) reverse = false;
  587. if (randomValue === undefined) randomValue = 0;
  588. if (dissonance === undefined) dissonance = 0;
  589. if (echo === undefined) echo = undefined;
  590. if (reverb === undefined) reverb = undefined;
  591. if (timeout === undefined) timeout = undefined;
  592. //Create an oscillator, gain and pan nodes, and connect them
  593. //together to the destination
  594. var oscillator, volume, pan;
  595. oscillator = actx.createOscillator();
  596. volume = actx.createGain();
  597. if (!actx.createStereoPanner) {
  598. pan = actx.createPanner();
  599. } else {
  600. pan = actx.createStereoPanner();
  601. }
  602. oscillator.connect(volume);
  603. volume.connect(pan);
  604. pan.connect(actx.destination);
  605. //Set the supplied values
  606. volume.gain.value = volumeValue;
  607. if (!actx.createStereoPanner) {
  608. pan.setPosition(panValue, 0, 1 - Math.abs(panValue));
  609. } else {
  610. pan.pan.value = panValue;
  611. }
  612. oscillator.type = type;
  613. //Optionally randomize the pitch. If the `randomValue` is greater
  614. //than zero, a random pitch is selected that's within the range
  615. //specified by `frequencyValue`. The random pitch will be either
  616. //above or below the target frequency.
  617. var frequency;
  618. var randomInt = function(min, max){
  619. return Math.floor(Math.random() * (max - min + 1)) + min
  620. };
  621. if (randomValue > 0) {
  622. frequency = randomInt(
  623. frequencyValue - randomValue / 2,
  624. frequencyValue + randomValue / 2
  625. );
  626. } else {
  627. frequency = frequencyValue;
  628. }
  629. oscillator.frequency.value = frequency;
  630. //Apply effects
  631. if (attack > 0) fadeIn(volume);
  632. fadeOut(volume);
  633. if (pitchBendAmount > 0) pitchBend(oscillator);
  634. if (echo) addEcho(volume);
  635. if (reverb) addReverb(volume);
  636. if (dissonance > 0) addDissonance();
  637. //Play the sound
  638. play(oscillator);
  639. //The helper functions:
  640. function addReverb(volumeNode) {
  641. var convolver = actx.createConvolver();
  642. convolver.buffer = impulseResponse(reverb[0], reverb[1], reverb[2], actx);
  643. volumeNode.connect(convolver);
  644. convolver.connect(pan);
  645. }
  646. function addEcho(volumeNode) {
  647. //Create the nodes
  648. var feedback = actx.createGain(),
  649. delay = actx.createDelay(),
  650. filter = actx.createBiquadFilter();
  651. //Set their values (delay time, feedback time and filter frequency)
  652. delay.delayTime.value = echo[0];
  653. feedback.gain.value = echo[1];
  654. if (echo[2]) filter.frequency.value = echo[2];
  655. //Create the delay feedback loop, with
  656. //optional filtering
  657. delay.connect(feedback);
  658. if (echo[2]) {
  659. feedback.connect(filter);
  660. filter.connect(delay);
  661. } else {
  662. feedback.connect(delay);
  663. }
  664. //Connect the delay loop to the oscillator's volume
  665. //node, and then to the destination
  666. volumeNode.connect(delay);
  667. //Connect the delay loop to the main sound chain's
  668. //pan node, so that the echo effect is directed to
  669. //the correct speaker
  670. delay.connect(pan);
  671. }
  672. //The `fadeIn` function
  673. function fadeIn(volumeNode) {
  674. //Set the volume to 0 so that you can fade
  675. //in from silence
  676. volumeNode.gain.value = 0;
  677. volumeNode.gain.linearRampToValueAtTime(
  678. 0, actx.currentTime + wait
  679. );
  680. volumeNode.gain.linearRampToValueAtTime(
  681. volumeValue, actx.currentTime + wait + attack
  682. );
  683. }
  684. //The `fadeOut` function
  685. function fadeOut(volumeNode) {
  686. volumeNode.gain.linearRampToValueAtTime(
  687. volumeValue, actx.currentTime + attack + wait
  688. );
  689. volumeNode.gain.linearRampToValueAtTime(
  690. 0, actx.currentTime + wait + attack + decay
  691. );
  692. }
  693. //The `pitchBend` function
  694. function pitchBend(oscillatorNode) {
  695. //If `reverse` is true, make the note drop in frequency. Useful for
  696. //shooting sounds
  697. //Get the frequency of the current oscillator
  698. var frequency = oscillatorNode.frequency.value;
  699. //If `reverse` is true, make the sound drop in pitch
  700. if (!reverse) {
  701. oscillatorNode.frequency.linearRampToValueAtTime(
  702. frequency,
  703. actx.currentTime + wait
  704. );
  705. oscillatorNode.frequency.linearRampToValueAtTime(
  706. frequency - pitchBendAmount,
  707. actx.currentTime + wait + attack + decay
  708. );
  709. }
  710. //If `reverse` is false, make the note rise in pitch. Useful for
  711. //jumping sounds
  712. else {
  713. oscillatorNode.frequency.linearRampToValueAtTime(
  714. frequency,
  715. actx.currentTime + wait
  716. );
  717. oscillatorNode.frequency.linearRampToValueAtTime(
  718. frequency + pitchBendAmount,
  719. actx.currentTime + wait + attack + decay
  720. );
  721. }
  722. }
  723. //The `addDissonance` function
  724. function addDissonance() {
  725. //Create two more oscillators and gain nodes
  726. var d1 = actx.createOscillator(),
  727. d2 = actx.createOscillator(),
  728. d1Volume = actx.createGain(),
  729. d2Volume = actx.createGain();
  730. //Set the volume to the `volumeValue`
  731. d1Volume.gain.value = volumeValue;
  732. d2Volume.gain.value = volumeValue;
  733. //Connect the oscillators to the gain and destination nodes
  734. d1.connect(d1Volume);
  735. d1Volume.connect(actx.destination);
  736. d2.connect(d2Volume);
  737. d2Volume.connect(actx.destination);
  738. //Set the waveform to "sawtooth" for a harsh effect
  739. d1.type = "sawtooth";
  740. d2.type = "sawtooth";
  741. //Make the two oscillators play at frequencies above and
  742. //below the main sound's frequency. Use whatever value was
  743. //supplied by the `dissonance` argument
  744. d1.frequency.value = frequency + dissonance;
  745. d2.frequency.value = frequency - dissonance;
  746. //Fade in/out, pitch bend and play the oscillators
  747. //to match the main sound
  748. if (attack > 0) {
  749. fadeIn(d1Volume);
  750. fadeIn(d2Volume);
  751. }
  752. if (decay > 0) {
  753. fadeOut(d1Volume);
  754. fadeOut(d2Volume);
  755. }
  756. if (pitchBendAmount > 0) {
  757. pitchBend(d1);
  758. pitchBend(d2);
  759. }
  760. if (echo) {
  761. addEcho(d1Volume);
  762. addEcho(d2Volume);
  763. }
  764. if (reverb) {
  765. addReverb(d1Volume);
  766. addReverb(d2Volume);
  767. }
  768. play(d1);
  769. play(d2);
  770. }
  771. //The `play` function
  772. function play(node) {
  773. node.start(actx.currentTime + wait);
  774. //Oscillators have to be stopped otherwise they accumulate in
  775. //memory and tax the CPU. They'll be stopped after a default
  776. //timeout of 2 seconds, which should be enough for most sound
  777. //effects. Override this in the `soundEffect` parameters if you
  778. //need a longer sound
  779. node.stop(actx.currentTime + wait + 2);
  780. }
  781. }
  782. /*
  783. impulseResponse
  784. ---------------
  785. The `makeSound` and `soundEffect` functions uses `impulseResponse` to help create an optional reverb effect.
  786. It simulates a model of sound reverberation in an acoustic space which
  787. a convolver node can blend with the source sound. Make sure to include this function along with `makeSound`
  788. and `soundEffect` if you need to use the reverb feature.
  789. */
  790. function impulseResponse(duration, decay, reverse, actx) {
  791. //The length of the buffer.
  792. var length = actx.sampleRate * duration;
  793. //Create an audio buffer (an empty sound container) to store the reverb effect.
  794. var impulse = actx.createBuffer(2, length, actx.sampleRate);
  795. //Use `getChannelData` to initialize empty arrays to store sound data for
  796. //the left and right channels.
  797. var left = impulse.getChannelData(0),
  798. right = impulse.getChannelData(1);
  799. //Loop through each sample-frame and fill the channel
  800. //data with random noise.
  801. for (var i = 0; i < length; i++){
  802. //Apply the reverse effect, if `reverse` is `true`.
  803. var n;
  804. if (reverse) {
  805. n = length - i;
  806. } else {
  807. n = i;
  808. }
  809. //Fill the left and right channels with random white noise which
  810. //decays exponentially.
  811. left[i] = (Math.random() * 2 - 1) * Math.pow(1 - n / length, decay);
  812. right[i] = (Math.random() * 2 - 1) * Math.pow(1 - n / length, decay);
  813. }
  814. //Return the `impulse`.
  815. return impulse;
  816. }
  817. /*
  818. keyboard
  819. --------
  820. This isn't really necessary - I just included it for fun to help with the
  821. examples in the `index.html` files.
  822. The `keyboard` helper function creates `key` objects
  823. that listen for keyboard events. Create a new key object like
  824. this:
  825. var keyObject = g.keyboard(asciiKeyCodeNumber);
  826. Then assign `press` and `release` methods like this:
  827. keyObject.press = function() {
  828. //key object pressed
  829. };
  830. keyObject.release = function() {
  831. //key object released
  832. };
  833. Keyboard objects also have `isDown` and `isUp` Booleans that you can check.
  834. This is so much easier than having to write out tedious keyboard even capture
  835. code from scratch.
  836. Like I said, the `keyboard` function has nothing to do with generating sounds,
  837. so just delete it if you don't want it!
  838. */
  839. function keyboard(keyCode) {
  840. var key = {};
  841. key.code = keyCode;
  842. key.isDown = false;
  843. key.isUp = true;
  844. key.press = undefined;
  845. key.release = undefined;
  846. //The `downHandler`
  847. key.downHandler = function(event) {
  848. if (event.keyCode === key.code) {
  849. if (key.isUp && key.press) key.press();
  850. key.isDown = true;
  851. key.isUp = false;
  852. }
  853. event.preventDefault();
  854. };
  855. //The `upHandler`
  856. key.upHandler = function(event) {
  857. if (event.keyCode === key.code) {
  858. if (key.isDown && key.release) key.release();
  859. key.isDown = false;
  860. key.isUp = true;
  861. }
  862. event.preventDefault();
  863. };
  864. //Attach event listeners
  865. window.addEventListener(
  866. "keydown", key.downHandler.bind(key), false
  867. );
  868. window.addEventListener(
  869. "keyup", key.upHandler.bind(key), false
  870. );
  871. return key;
  872. }
  873. function scaleToWindow(canvas, backgroundColor) {
  874. var scaleX, scaleY, scale, center;
  875. //1. Scale the canvas to the correct size
  876. //Figure out the scale amount on each axis
  877. scaleX = window.innerWidth / canvas.offsetWidth;
  878. scaleY = window.innerHeight / canvas.offsetHeight;
  879. //Scale the canvas based on whichever value is less: `scaleX` or `scaleY`
  880. scale = Math.min(scaleX, scaleY);
  881. canvas.style.transformOrigin = "0 0";
  882. canvas.style.transform = "scale(" + scale + ")";
  883. //2. Center the canvas.
  884. //Decide whether to center the canvas vertically or horizontally.
  885. //Wide canvases should be centered vertically, and
  886. //square or tall canvases should be centered horizontally
  887. if (canvas.offsetWidth > canvas.offsetHeight) {
  888. if (canvas.offsetWidth * scale < window.innerWidth) {
  889. center = "horizontally";
  890. } else {
  891. center = "vertically";
  892. }
  893. } else {
  894. if (canvas.offsetHeight * scale < window.innerHeight) {
  895. center = "vertically";
  896. } else {
  897. center = "horizontally";
  898. }
  899. }
  900. //Center horizontally (for square or tall canvases)
  901. var margin;
  902. if (center === "horizontally") {
  903. margin = (window.innerWidth - canvas.offsetWidth * scale) / 2;
  904. canvas.style.marginTop = 0 + "px";
  905. canvas.style.marginBottom = 0 + "px";
  906. canvas.style.marginLeft = margin + "px";
  907. canvas.style.marginRight = margin + "px";
  908. }
  909. //Center vertically (for wide canvases)
  910. if (center === "vertically") {
  911. margin = (window.innerHeight - canvas.offsetHeight * scale) / 2;
  912. canvas.style.marginTop = margin + "px";
  913. canvas.style.marginBottom = margin + "px";
  914. canvas.style.marginLeft = 0 + "px";
  915. canvas.style.marginRight = 0 + "px";
  916. }
  917. //3. Remove any padding from the canvas and body and set the canvas
  918. //display style to "block"
  919. canvas.style.paddingLeft = 0 + "px";
  920. canvas.style.paddingRight = 0 + "px";
  921. canvas.style.paddingTop = 0 + "px";
  922. canvas.style.paddingBottom = 0 + "px";
  923. canvas.style.display = "block";
  924. //4. Set the color of the HTML body background
  925. document.body.style.backgroundColor = backgroundColor;
  926. //Fix some quirkiness in scaling for Safari
  927. var ua = navigator.userAgent.toLowerCase();
  928. if (ua.indexOf("safari") != -1) {
  929. if (ua.indexOf("chrome") > -1) {
  930. // Chrome
  931. } else {
  932. // Safari
  933. //canvas.style.maxHeight = "100%";
  934. //canvas.style.minHeight = "100%";
  935. }
  936. }
  937. //5. Return the `scale` value. This is important, because you'll nee this value
  938. //for correct hit testing between the pointer and sprites
  939. return scale;
  940. }"use strict";
  941. var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
  942. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  943. var Bump = (function () {
  944. function Bump() {
  945. var renderingEngine = arguments.length <= 0 || arguments[0] === undefined ? PIXI : arguments[0];
  946. _classCallCheck(this, Bump);
  947. if (renderingEngine === undefined) throw new Error("Please assign a rendering engine in the constructor before using bump.js");
  948. this.renderer = "pixi";
  949. }
  950. //`addCollisionProperties` adds extra properties to sprites to help
  951. //simplify the collision code. It won't add these properties if they
  952. //already exist on the sprite. After these properties have been
  953. //added, this methods adds a Boolean property to the sprite called `_bumpPropertiesAdded`
  954. //and sets it to `true` to flag that the sprite has these
  955. //new properties
  956. _createClass(Bump, [{
  957. key: "addCollisionProperties",
  958. value: function addCollisionProperties(sprite) {
  959. //Add properties to Pixi sprites
  960. if (this.renderer === "pixi") {
  961. //gx
  962. if (sprite.gx === undefined) {
  963. Object.defineProperty(sprite, "gx", {
  964. get: function get() {
  965. return sprite.getGlobalPosition().x;
  966. },
  967. enumerable: true, configurable: true
  968. });
  969. }
  970. //gy
  971. if (sprite.gy === undefined) {
  972. Object.defineProperty(sprite, "gy", {
  973. get: function get() {
  974. return sprite.getGlobalPosition().y;
  975. },
  976. enumerable: true, configurable: true
  977. });
  978. }
  979. //centerX
  980. if (sprite.centerX === undefined) {
  981. Object.defineProperty(sprite, "centerX", {
  982. get: function get() {
  983. return sprite.x + sprite.width / 2;
  984. },
  985. enumerable: true, configurable: true
  986. });
  987. }
  988. //centerY
  989. if (sprite.centerY === undefined) {
  990. Object.defineProperty(sprite, "centerY", {
  991. get: function get() {
  992. return sprite.y + sprite.height / 2;
  993. },
  994. enumerable: true, configurable: true
  995. });
  996. }
  997. //halfWidth
  998. if (sprite.halfWidth === undefined) {
  999. Object.defineProperty(sprite, "halfWidth", {
  1000. get: function get() {
  1001. return sprite.width / 2;
  1002. },
  1003. enumerable: true, configurable: true
  1004. });
  1005. }
  1006. //halfHeight
  1007. if (sprite.halfHeight === undefined) {
  1008. Object.defineProperty(sprite, "halfHeight", {
  1009. get: function get() {
  1010. return sprite.height / 2;
  1011. },
  1012. enumerable: true, configurable: true
  1013. });
  1014. }
  1015. //xAnchorOffset
  1016. if (sprite.xAnchorOffset === undefined) {
  1017. Object.defineProperty(sprite, "xAnchorOffset", {
  1018. get: function get() {
  1019. if (sprite.anchor !== undefined) {
  1020. return sprite.width * sprite.anchor.x;
  1021. } else {
  1022. return 0;
  1023. }
  1024. },
  1025. enumerable: true, configurable: true
  1026. });
  1027. }
  1028. //yAnchorOffset
  1029. if (sprite.yAnchorOffset === undefined) {
  1030. Object.defineProperty(sprite, "yAnchorOffset", {
  1031. get: function get() {
  1032. if (sprite.anchor !== undefined) {
  1033. return sprite.height * sprite.anchor.y;
  1034. } else {
  1035. return 0;
  1036. }
  1037. },
  1038. enumerable: true, configurable: true
  1039. });
  1040. }
  1041. if (sprite.circular && sprite.radius === undefined) {
  1042. Object.defineProperty(sprite, "radius", {
  1043. get: function get() {
  1044. return sprite.width / 2;
  1045. },
  1046. enumerable: true, configurable: true
  1047. });
  1048. }
  1049. //Earlier code - not needed now.
  1050. /*
  1051. Object.defineProperties(sprite, {
  1052. "gx": {
  1053. get(){return sprite.getGlobalPosition().x},
  1054. enumerable: true, configurable: true
  1055. },
  1056. "gy": {
  1057. get(){return sprite.getGlobalPosition().y},
  1058. enumerable: true, configurable: true
  1059. },
  1060. "centerX": {
  1061. get(){return sprite.x + sprite.width / 2},
  1062. enumerable: true, configurable: true
  1063. },
  1064. "centerY": {
  1065. get(){return sprite.y + sprite.height / 2},
  1066. enumerable: true, configurable: true
  1067. },
  1068. "halfWidth": {
  1069. get(){return sprite.width / 2},
  1070. enumerable: true, configurable: true
  1071. },
  1072. "halfHeight": {
  1073. get(){return sprite.height / 2},
  1074. enumerable: true, configurable: true
  1075. },
  1076. "xAnchorOffset": {
  1077. get(){
  1078. if (sprite.anchor !== undefined) {
  1079. return sprite.height * sprite.anchor.x;
  1080. } else {
  1081. return 0;
  1082. }
  1083. },
  1084. enumerable: true, configurable: true
  1085. },
  1086. "yAnchorOffset": {
  1087. get(){
  1088. if (sprite.anchor !== undefined) {
  1089. return sprite.width * sprite.anchor.y;
  1090. } else {
  1091. return 0;
  1092. }
  1093. },
  1094. enumerable: true, configurable: true
  1095. }
  1096. });
  1097. */
  1098. }
  1099. //Add a Boolean `_bumpPropertiesAdded` property to the sprite to flag it
  1100. //as having these new properties
  1101. sprite._bumpPropertiesAdded = true;
  1102. }
  1103. /*
  1104. hitTestPoint
  1105. ------------
  1106. Use it to find out if a point is touching a circlular or rectangular sprite.
  1107. Parameters:
  1108. a. An object with `x` and `y` properties.
  1109. b. A sprite object with `x`, `y`, `centerX` and `centerY` properties.
  1110. If the sprite has a `radius` property, the function will interpret
  1111. the shape as a circle.
  1112. */
  1113. }, {
  1114. key: "hitTestPoint",
  1115. value: function hitTestPoint(point, sprite) {
  1116. //Add collision properties
  1117. if (!sprite._bumpPropertiesAdded) this.addCollisionProperties(sprite);
  1118. var shape = undefined,
  1119. left = undefined,
  1120. right = undefined,
  1121. top = undefined,
  1122. bottom = undefined,
  1123. vx = undefined,
  1124. vy = undefined,
  1125. magnitude = undefined,
  1126. hit = undefined;
  1127. //Find out if the sprite is rectangular or circular depending
  1128. //on whether it has a `radius` property
  1129. if (sprite.radius) {
  1130. shape = "circle";
  1131. } else {
  1132. shape = "rectangle";
  1133. }
  1134. //Rectangle
  1135. if (shape === "rectangle") {
  1136. //Get the position of the sprite's edges
  1137. left = sprite.x - sprite.xAnchorOffset;
  1138. right = sprite.x + sprite.width - sprite.xAnchorOffset;
  1139. top = sprite.y - sprite.yAnchorOffset;
  1140. bottom = sprite.y + sprite.height - sprite.yAnchorOffset;
  1141. //Find out if the point is intersecting the rectangle
  1142. hit = point.x > left && point.x < right && point.y > top && point.y < bottom;
  1143. }
  1144. //Circle
  1145. if (shape === "circle") {
  1146. //Find the distance between the point and the
  1147. //center of the circle
  1148. var _vx = point.x - sprite.x - sprite.width / 2 + sprite.xAnchorOffset,
  1149. _vy = point.y - sprite.y - sprite.height / 2 + sprite.yAnchorOffset,
  1150. _magnitude = Math.sqrt(_vx * _vx + _vy * _vy);
  1151. //The point is intersecting the circle if the magnitude
  1152. //(distance) is less than the circle's radius
  1153. hit = _magnitude < sprite.radius;
  1154. }
  1155. //`hit` will be either `true` or `false`
  1156. return hit;
  1157. }
  1158. /*
  1159. hitTestCircle
  1160. -------------
  1161. Use it to find out if two circular sprites are touching.
  1162. Parameters:
  1163. a. A sprite object with `centerX`, `centerY` and `radius` properties.
  1164. b. A sprite object with `centerX`, `centerY` and `radius`.
  1165. */
  1166. }, {
  1167. key: "hitTestCircle",
  1168. value: function hitTestCircle(c1, c2) {
  1169. var global = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2];
  1170. //Add collision properties
  1171. if (!c1._bumpPropertiesAdded) this.addCollisionProperties(c1);
  1172. if (!c2._bumpPropertiesAdded) this.addCollisionProperties(c2);
  1173. var vx = undefined,
  1174. vy = undefined,
  1175. magnitude = undefined,
  1176. combinedRadii = undefined,
  1177. hit = undefined;
  1178. //Calculate the vector between the circles’ center points
  1179. if (global) {
  1180. //Use global coordinates
  1181. vx = c2.gx + c2.width / 2 - c2.xAnchorOffset - (c1.gx + c1.width / 2 - c1.xAnchorOffset);
  1182. vy = c2.gy + c2.width / 2 - c2.yAnchorOffset - (c1.gy + c1.width / 2 - c1.yAnchorOffset);
  1183. } else {
  1184. //Use local coordinates
  1185. vx = c2.x + c2.width / 2 - c2.xAnchorOffset - (c1.x + c1.width / 2 - c1.xAnchorOffset);
  1186. vy = c2.y + c2.width / 2 - c2.yAnchorOffset - (c1.y + c1.width / 2 - c1.yAnchorOffset);
  1187. }
  1188. //Find the distance between the circles by calculating
  1189. //the vector's magnitude (how long the vector is)
  1190. magnitude = Math.sqrt(vx * vx + vy * vy);
  1191. //Add together the circles' total radii
  1192. combinedRadii = c1.radius + c2.radius;
  1193. //Set `hit` to `true` if the distance between the circles is
  1194. //less than their `combinedRadii`
  1195. hit = magnitude < combinedRadii;
  1196. //`hit` will be either `true` or `false`
  1197. return hit;
  1198. }
  1199. /*
  1200. circleCollision
  1201. ---------------
  1202. Use it to prevent a moving circular sprite from overlapping and optionally
  1203. bouncing off a non-moving circular sprite.
  1204. Parameters:
  1205. a. A sprite object with `x`, `y` `centerX`, `centerY` and `radius` properties.
  1206. b. A sprite object with `x`, `y` `centerX`, `centerY` and `radius` properties.
  1207. c. Optional: true or false to indicate whether or not the first sprite
  1208. should bounce off the second sprite.
  1209. The sprites can contain an optional mass property that should be greater than 1.
  1210. */
  1211. }, {
  1212. key: "circleCollision",
  1213. value: function circleCollision(c1, c2) {
  1214. var bounce = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2];
  1215. var global = arguments.length <= 3 || arguments[3] === undefined ? false : arguments[3];
  1216. //Add collision properties
  1217. if (!c1._bumpPropertiesAdded) this.addCollisionProperties(c1);
  1218. if (!c2._bumpPropertiesAdded) this.addCollisionProperties(c2);
  1219. var magnitude = undefined,
  1220. combinedRadii = undefined,
  1221. overlap = undefined,
  1222. vx = undefined,
  1223. vy = undefined,
  1224. dx = undefined,
  1225. dy = undefined,
  1226. s = {},
  1227. hit = false;
  1228. //Calculate the vector between the circles’ center points
  1229. if (global) {
  1230. //Use global coordinates
  1231. vx = c2.gx + c2.width / 2 - c2.xAnchorOffset - (c1.gx + c1.width / 2 - c1.xAnchorOffset);
  1232. vy = c2.gy + c2.width / 2 - c2.yAnchorOffset - (c1.gy + c1.width / 2 - c1.yAnchorOffset);
  1233. } else {
  1234. //Use local coordinates
  1235. vx = c2.x + c2.width / 2 - c2.xAnchorOffset - (c1.x + c1.width / 2 - c1.xAnchorOffset);
  1236. vy = c2.y + c2.width / 2 - c2.yAnchorOffset - (c1.y + c1.width / 2 - c1.yAnchorOffset);
  1237. }
  1238. //Find the distance between the circles by calculating
  1239. //the vector's magnitude (how long the vector is)
  1240. magnitude = Math.sqrt(vx * vx + vy * vy);
  1241. //Add together the circles' combined half-widths
  1242. combinedRadii = c1.radius + c2.radius;
  1243. //Figure out if there's a collision
  1244. if (magnitude < combinedRadii) {
  1245. //Yes, a collision is happening
  1246. hit = true;
  1247. //Find the amount of overlap between the circles
  1248. overlap = combinedRadii - magnitude;
  1249. //Add some "quantum padding". This adds a tiny amount of space
  1250. //between the circles to reduce their surface tension and make
  1251. //them more slippery. "0.3" is a good place to start but you might
  1252. //need to modify this slightly depending on the exact behaviour
  1253. //you want. Too little and the balls will feel sticky, too much
  1254. //and they could start to jitter if they're jammed together
  1255. var quantumPadding = 0.3;
  1256. overlap += quantumPadding;
  1257. //Normalize the vector
  1258. //These numbers tell us the direction of the collision
  1259. dx = vx / magnitude;
  1260. dy = vy / magnitude;
  1261. //Move circle 1 out of the collision by multiplying
  1262. //the overlap with the normalized vector and subtract it from
  1263. //circle 1's position
  1264. c1.x -= overlap * dx;
  1265. c1.y -= overlap * dy;
  1266. //Bounce
  1267. if (bounce) {
  1268. //Create a collision vector object, `s` to represent the bounce "surface".
  1269. //Find the bounce surface's x and y properties
  1270. //(This represents the normal of the distance vector between the circles)
  1271. s.x = vy;
  1272. s.y = -vx;
  1273. //Bounce c1 off the surface
  1274. this.bounceOffSurface(c1, s);
  1275. }
  1276. }
  1277. return hit;
  1278. }
  1279. /*
  1280. movingCircleCollision
  1281. ---------------------
  1282. Use it to make two moving circles bounce off each other.
  1283. Parameters:
  1284. a. A sprite object with `x`, `y` `centerX`, `centerY` and `radius` properties.
  1285. b. A sprite object with `x`, `y` `centerX`, `centerY` and `radius` properties.
  1286. The sprites can contain an optional mass property that should be greater than 1.
  1287. */
  1288. }, {
  1289. key: "movingCircleCollision",
  1290. value: function movingCircleCollision(c1, c2) {
  1291. var global = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2];
  1292. //Add collision properties
  1293. if (!c1._bumpPropertiesAdded) this.addCollisionProperties(c1);
  1294. if (!c2._bumpPropertiesAdded) this.addCollisionProperties(c2);
  1295. var combinedRadii = undefined,
  1296. overlap = undefined,
  1297. xSide = undefined,
  1298. ySide = undefined,
  1299. //`s` refers to the distance vector between the circles
  1300. s = {},
  1301. p1A = {},
  1302. p1B = {},
  1303. p2A = {},
  1304. p2B = {},
  1305. hit = false;
  1306. //Apply mass, if the circles have mass properties
  1307. c1.mass = c1.mass || 1;
  1308. c2.mass = c2.mass || 1;
  1309. //Calculate the vector between the circles’ center points
  1310. if (global) {
  1311. //Use global coordinates
  1312. s.vx = c2.gx + c2.radius - c2.xAnchorOffset - (c1.gx + c1.radius - c1.xAnchorOffset);
  1313. s.vy = c2.gy + c2.radius - c2.yAnchorOffset - (c1.gy + c1.radius - c1.yAnchorOffset);
  1314. } else {
  1315. //Use local coordinates
  1316. s.vx = c2.x + c2.radius - c2.xAnchorOffset - (c1.x + c1.radius - c1.xAnchorOffset);
  1317. s.vy = c2.y + c2.radius - c2.yAnchorOffset - (c1.y + c1.radius - c1.yAnchorOffset);
  1318. }
  1319. //Find the distance between the circles by calculating
  1320. //the vector's magnitude (how long the vector is)
  1321. s.magnitude = Math.sqrt(s.vx * s.vx + s.vy * s.vy);
  1322. //Add together the circles' combined half-widths
  1323. combinedRadii = c1.radius + c2.radius;
  1324. //Figure out if there's a collision
  1325. if (s.magnitude < combinedRadii) {
  1326. //Yes, a collision is happening
  1327. hit = true;
  1328. //Find the amount of overlap between the circles
  1329. overlap = combinedRadii - s.magnitude;
  1330. //Add some "quantum padding" to the overlap
  1331. overlap += 0.3;
  1332. //Normalize the vector.
  1333. //These numbers tell us the direction of the collision
  1334. s.dx = s.vx / s.magnitude;
  1335. s.dy = s.vy / s.magnitude;
  1336. //Find the collision vector.
  1337. //Divide it in half to share between the circles, and make it absolute
  1338. s.vxHalf = Math.abs(s.dx * overlap / 2);
  1339. s.vyHalf = Math.abs(s.dy * overlap / 2);
  1340. //Find the side that the collision is occurring on
  1341. c1.x > c2.x ? xSide = 1 : xSide = -1;
  1342. c1.y > c2.y ? ySide = 1 : ySide = -1;
  1343. //Move c1 out of the collision by multiplying
  1344. //the overlap with the normalized vector and adding it to
  1345. //the circles' positions
  1346. c1.x = c1.x + s.vxHalf * xSide;
  1347. c1.y = c1.y + s.vyHalf * ySide;
  1348. //Move c2 out of the collision
  1349. c2.x = c2.x + s.vxHalf * -xSide;
  1350. c2.y = c2.y + s.vyHalf * -ySide;
  1351. //1. Calculate the collision surface's properties
  1352. //Find the surface vector's left normal
  1353. s.lx = s.vy;
  1354. s.ly = -s.vx;
  1355. //2. Bounce c1 off the surface (s)
  1356. //Find the dot product between c1 and the surface
  1357. var dp1 = c1.vx * s.dx + c1.vy * s.dy;
  1358. //Project c1's velocity onto the collision surface
  1359. p1A.x = dp1 * s.dx;
  1360. p1A.y = dp1 * s.dy;
  1361. //Find the dot product of c1 and the surface's left normal (s.lx and s.ly)
  1362. var dp2 = c1.vx * (s.lx / s.magnitude) + c1.vy * (s.ly / s.magnitude);
  1363. //Project the c1's velocity onto the surface's left normal
  1364. p1B.x = dp2 * (s.lx / s.magnitude);
  1365. p1B.y = dp2 * (s.ly / s.magnitude);
  1366. //3. Bounce c2 off the surface (s)
  1367. //Find the dot product between c2 and the surface
  1368. var dp3 = c2.vx * s.dx + c2.vy * s.dy;
  1369. //Project c2's velocity onto the collision surface
  1370. p2A.x = dp3 * s.dx;
  1371. p2A.y = dp3 * s.dy;
  1372. //Find the dot product of c2 and the surface's left normal (s.lx and s.ly)
  1373. var dp4 = c2.vx * (s.lx / s.magnitude) + c2.vy * (s.ly / s.magnitude);
  1374. //Project c2's velocity onto the surface's left normal
  1375. p2B.x = dp4 * (s.lx / s.magnitude);
  1376. p2B.y = dp4 * (s.ly / s.magnitude);
  1377. //4. Calculate the bounce vectors
  1378. //Bounce c1
  1379. //using p1B and p2A
  1380. c1.bounce = {};
  1381. c1.bounce.x = p1B.x + p2A.x;
  1382. c1.bounce.y = p1B.y + p2A.y;
  1383. //Bounce c2
  1384. //using p1A and p2B
  1385. c2.bounce = {};
  1386. c2.bounce.x = p1A.x + p2B.x;
  1387. c2.bounce.y = p1A.y + p2B.y;
  1388. //Add the bounce vector to the circles' velocity
  1389. //and add mass if the circle has a mass property
  1390. c1.vx = c1.bounce.x / c1.mass;
  1391. c1.vy = c1.bounce.y / c1.mass;
  1392. c2.vx = c2.bounce.x / c2.mass;
  1393. c2.vy = c2.bounce.y / c2.mass;
  1394. }
  1395. return hit;
  1396. }
  1397. /*
  1398. multipleCircleCollision
  1399. -----------------------
  1400. Checks all the circles in an array for a collision against
  1401. all the other circles in an array, using `movingCircleCollision` (above)
  1402. */
  1403. }, {
  1404. key: "multipleCircleCollision",
  1405. value: function multipleCircleCollision(arrayOfCircles) {
  1406. var global = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1];
  1407. for (var i = 0; i < arrayOfCircles.length; i++) {
  1408. //The first circle to use in the collision check
  1409. var c1 = arrayOfCircles[i];
  1410. for (var j = i + 1; j < arrayOfCircles.length; j++) {
  1411. //The second circle to use in the collision check
  1412. var c2 = arrayOfCircles[j];
  1413. //Check for a collision and bounce the circles apart if
  1414. //they collide. Use an optional `mass` property on the sprite
  1415. //to affect the bounciness of each marble
  1416. this.movingCircleCollision(c1, c2, global);
  1417. }
  1418. }
  1419. }
  1420. /*
  1421. rectangleCollision
  1422. ------------------
  1423. Use it to prevent two rectangular sprites from overlapping.
  1424. Optionally, make the first rectangle bounce off the second rectangle.
  1425. Parameters:
  1426. a. A sprite object with `x`, `y` `centerX`, `centerY`, `halfWidth` and `halfHeight` properties.
  1427. b. A sprite object with `x`, `y` `centerX`, `centerY`, `halfWidth` and `halfHeight` properties.
  1428. c. Optional: true or false to indicate whether or not the first sprite
  1429. should bounce off the second sprite.
  1430. */
  1431. }, {
  1432. key: "rectangleCollision",
  1433. value: function rectangleCollision(r1, r2) {
  1434. var bounce = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2];
  1435. var global = arguments.length <= 3 || arguments[3] === undefined ? true : arguments[3];
  1436. //Add collision properties
  1437. if (!r1._bumpPropertiesAdded) this.addCollisionProperties(r1);
  1438. if (!r2._bumpPropertiesAdded) this.addCollisionProperties(r2);
  1439. var collision = undefined,
  1440. combinedHalfWidths = undefined,
  1441. combinedHalfHeights = undefined,
  1442. overlapX = undefined,
  1443. overlapY = undefined,
  1444. vx = undefined,
  1445. vy = undefined;
  1446. //Calculate the distance vector
  1447. if (global) {
  1448. vx = r1.gx + Math.abs(r1.halfWidth) - r1.xAnchorOffset - (r2.gx + Math.abs(r2.halfWidth) - r2.xAnchorOffset);
  1449. vy = r1.gy + Math.abs(r1.halfHeight) - r1.yAnchorOffset - (r2.gy + Math.abs(r2.halfHeight) - r2.yAnchorOffset);
  1450. } else {
  1451. //vx = r1.centerX - r2.centerX;
  1452. //vy = r1.centerY - r2.centerY;
  1453. vx = r1.x + Math.abs(r1.halfWidth) - r1.xAnchorOffset - (r2.x + Math.abs(r2.halfWidth) - r2.xAnchorOffset);
  1454. vy = r1.y + Math.abs(r1.halfHeight) - r1.yAnchorOffset - (r2.y + Math.abs(r2.halfHeight) - r2.yAnchorOffset);
  1455. }
  1456. //Figure out the combined half-widths and half-heights
  1457. combinedHalfWidths = Math.abs(r1.halfWidth) + Math.abs(r2.halfWidth);
  1458. combinedHalfHeights = Math.abs(r1.halfHeight) + Math.abs(r2.halfHeight);
  1459. //Check whether vx is less than the combined half widths
  1460. if (Math.abs(vx) < combinedHalfWidths) {
  1461. //A collision might be occurring!
  1462. //Check whether vy is less than the combined half heights
  1463. if (Math.abs(vy) < combinedHalfHeights) {
  1464. //A collision has occurred! This is good!
  1465. //Find out the size of the overlap on both the X and Y axes
  1466. overlapX = combinedHalfWidths - Math.abs(vx);
  1467. overlapY = combinedHalfHeights - Math.abs(vy);
  1468. //The collision has occurred on the axis with the
  1469. //*smallest* amount of overlap. Let's figure out which
  1470. //axis that is
  1471. if (overlapX >= overlapY) {
  1472. //The collision is happening on the X axis
  1473. //But on which side? vy can tell us
  1474. if (vy > 0) {
  1475. collision = "top";
  1476. //Move the rectangle out of the collision
  1477. r1.y = r1.y + overlapY;
  1478. } else {
  1479. collision = "bottom";
  1480. //Move the rectangle out of the collision
  1481. r1.y = r1.y - overlapY;
  1482. }
  1483. //Bounce
  1484. if (bounce) {
  1485. r1.vy *= -1;
  1486. /*Alternative
  1487. //Find the bounce surface's vx and vy properties
  1488. var s = {};
  1489. s.vx = r2.x - r2.x + r2.width;
  1490. s.vy = 0;
  1491. //Bounce r1 off the surface
  1492. //this.bounceOffSurface(r1, s);
  1493. */
  1494. }
  1495. } else {
  1496. //The collision is happening on the Y axis
  1497. //But on which side? vx can tell us
  1498. if (vx > 0) {
  1499. collision = "left";
  1500. //Move the rectangle out of the collision
  1501. r1.x = r1.x + overlapX;
  1502. } else {
  1503. collision = "right";
  1504. //Move the rectangle out of the collision
  1505. r1.x = r1.x - overlapX;
  1506. }
  1507. //Bounce
  1508. if (bounce) {
  1509. r1.vx *= -1;
  1510. /*Alternative
  1511. //Find the bounce surface's vx and vy properties
  1512. var s = {};
  1513. s.vx = 0;
  1514. s.vy = r2.y - r2.y + r2.height;
  1515. //Bounce r1 off the surface
  1516. this.bounceOffSurface(r1, s);
  1517. */
  1518. }
  1519. }
  1520. } else {
  1521. //No collision
  1522. }
  1523. } else {}
  1524. //No collision
  1525. //Return the collision string. it will be either "top", "right",
  1526. //"bottom", or "left" depending on which side of r1 is touching r2.
  1527. return collision;
  1528. }
  1529. /*
  1530. hitTestRectangle
  1531. ----------------
  1532. Use it to find out if two rectangular sprites are touching.
  1533. Parameters:
  1534. a. A sprite object with `centerX`, `centerY`, `halfWidth` and `halfHeight` properties.
  1535. b. A sprite object with `centerX`, `centerY`, `halfWidth` and `halfHeight` properties.
  1536. */
  1537. }, {
  1538. key: "hitTestRectangle",
  1539. value: function hitTestRectangle(r1, r2) {
  1540. var global = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2];
  1541. //Add collision properties
  1542. if (!r1._bumpPropertiesAdded) this.addCollisionProperties(r1);
  1543. if (!r2._bumpPropertiesAdded) this.addCollisionProperties(r2);
  1544. var hit = undefined,
  1545. combinedHalfWidths = undefined,
  1546. combinedHalfHeights = undefined,
  1547. vx = undefined,
  1548. vy = undefined;
  1549. //A variable to determine whether there's a collision
  1550. hit = false;
  1551. //Calculate the distance vector
  1552. if (global) {
  1553. vx = r1.gx + Math.abs(r1.halfWidth) - r1.xAnchorOffset - (r2.gx + Math.abs(r2.halfWidth) - r2.xAnchorOffset);
  1554. vy = r1.gy + Math.abs(r1.halfHeight) - r1.yAnchorOffset - (r2.gy + Math.abs(r2.halfHeight) - r2.yAnchorOffset);
  1555. } else {
  1556. vx = r1.x + Math.abs(r1.halfWidth) - r1.xAnchorOffset - (r2.x + Math.abs(r2.halfWidth) - r2.xAnchorOffset);
  1557. vy = r1.y + Math.abs(r1.halfHeight) - r1.yAnchorOffset - (r2.y + Math.abs(r2.halfHeight) - r2.yAnchorOffset);
  1558. }
  1559. //Figure out the combined half-widths and half-heights
  1560. combinedHalfWidths = Math.abs(r1.halfWidth) + Math.abs(r2.halfWidth);
  1561. combinedHalfHeights = Math.abs(r1.halfHeight) + Math.abs(r2.halfHeight);
  1562. //Check for a collision on the x axis
  1563. if (Math.abs(vx) < combinedHalfWidths) {
  1564. //A collision might be occuring. Check for a collision on the y axis
  1565. if (Math.abs(vy) < combinedHalfHeights) {
  1566. //There's definitely a collision happening
  1567. hit = true;
  1568. } else {
  1569. //There's no collision on the y axis
  1570. hit = false;
  1571. }
  1572. } else {
  1573. //There's no collision on the x axis
  1574. hit = false;
  1575. }
  1576. //`hit` will be either `true` or `false`
  1577. return hit;
  1578. }
  1579. /*
  1580. hitTestCircleRectangle
  1581. ----------------
  1582. Use it to find out if a circular shape is touching a rectangular shape
  1583. Parameters:
  1584. a. A sprite object with `centerX`, `centerY`, `halfWidth` and `halfHeight` properties.
  1585. b. A sprite object with `centerX`, `centerY`, `halfWidth` and `halfHeight` properties.
  1586. */
  1587. }, {
  1588. key: "hitTestCircleRectangle",
  1589. value: function hitTestCircleRectangle(c1, r1) {
  1590. var global = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2];
  1591. //Add collision properties
  1592. if (!r1._bumpPropertiesAdded) this.addCollisionProperties(r1);
  1593. if (!c1._bumpPropertiesAdded) this.addCollisionProperties(c1);
  1594. var region = undefined,
  1595. collision = undefined,
  1596. c1x = undefined,
  1597. c1y = undefined,
  1598. r1x = undefined,
  1599. r1y = undefined;
  1600. //Use either global or local coordinates
  1601. if (global) {
  1602. c1x = c1.gx;
  1603. c1y = c1.gy;
  1604. r1x = r1.gx;
  1605. r1y = r1.gy;
  1606. } else {
  1607. c1x = c1.x;
  1608. c1y = c1.y;
  1609. r1x = r1.x;
  1610. r1y = r1.y;
  1611. }
  1612. //Is the circle above the rectangle's top edge?
  1613. if (c1y - c1.yAnchorOffset < r1y - Math.abs(r1.halfHeight) - r1.yAnchorOffset) {
  1614. //If it is, we need to check whether it's in the
  1615. //top left, top center or top right
  1616. if (c1x - c1.xAnchorOffset < r1x - 1 - Math.abs(r1.halfWidth) - r1.xAnchorOffset) {
  1617. region = "topLeft";
  1618. } else if (c1x - c1.xAnchorOffset > r1x + 1 + Math.abs(r1.halfWidth) - r1.xAnchorOffset) {
  1619. region = "topRight";
  1620. } else {
  1621. region = "topMiddle";
  1622. }
  1623. }
  1624. //The circle isn't above the top edge, so it might be
  1625. //below the bottom edge
  1626. else if (c1y - c1.yAnchorOffset > r1y + Math.abs(r1.halfHeight) - r1.yAnchorOffset) {
  1627. //If it is, we need to check whether it's in the bottom left,
  1628. //bottom center, or bottom right
  1629. if (c1x - c1.xAnchorOffset < r1x - 1 - Math.abs(r1.halfWidth) - r1.xAnchorOffset) {
  1630. region = "bottomLeft";
  1631. } else if (c1x - c1.xAnchorOffset > r1x + 1 + Math.abs(r1.halfWidth) - r1.xAnchorOffset) {
  1632. region = "bottomRight";
  1633. } else {
  1634. region = "bottomMiddle";
  1635. }
  1636. }
  1637. //The circle isn't above the top edge or below the bottom edge,
  1638. //so it must be on the left or right side
  1639. else {
  1640. if (c1x - c1.xAnchorOffset < r1x - Math.abs(r1.halfWidth) - r1.xAnchorOffset) {
  1641. region = "leftMiddle";
  1642. } else {
  1643. region = "rightMiddle";
  1644. }
  1645. }
  1646. //Is this the circle touching the flat sides
  1647. //of the rectangle?
  1648. if (region === "topMiddle" || region === "bottomMiddle" || region === "leftMiddle" || region === "rightMiddle") {
  1649. //Yes, it is, so do a standard rectangle vs. rectangle collision test
  1650. collision = this.hitTestRectangle(c1, r1, global);
  1651. }
  1652. //The circle is touching one of the corners, so do a
  1653. //circle vs. point collision test
  1654. else {
  1655. var point = {};
  1656. switch (region) {
  1657. case "topLeft":
  1658. point.x = r1x - r1.xAnchorOffset;
  1659. point.y = r1y - r1.yAnchorOffset;
  1660. break;
  1661. case "topRight":
  1662. point.x = r1x + r1.width - r1.xAnchorOffset;
  1663. point.y = r1y - r1.yAnchorOffset;
  1664. break;
  1665. case "bottomLeft":
  1666. point.x = r1x - r1.xAnchorOffset;
  1667. point.y = r1y + r1.height - r1.yAnchorOffset;
  1668. break;
  1669. case "bottomRight":
  1670. point.x = r1x + r1.width - r1.xAnchorOffset;
  1671. point.y = r1y + r1.height - r1.yAnchorOffset;
  1672. }
  1673. //Check for a collision between the circle and the point
  1674. collision = this.hitTestCirclePoint(c1, point, global);
  1675. }
  1676. //Return the result of the collision.
  1677. //The return value will be `undefined` if there's no collision
  1678. if (collision) {
  1679. return region;
  1680. } else {
  1681. return collision;
  1682. }
  1683. }
  1684. /*
  1685. hitTestCirclePoint
  1686. ------------------
  1687. Use it to find out if a circular shape is touching a point
  1688. Parameters:
  1689. a. A sprite object with `centerX`, `centerY`, and `radius` properties.
  1690. b. A point object with `x` and `y` properties.
  1691. */
  1692. }, {
  1693. key: "hitTestCirclePoint",
  1694. value: function hitTestCirclePoint(c1, point) {
  1695. var global = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2];
  1696. //Add collision properties
  1697. if (!c1._bumpPropertiesAdded) this.addCollisionProperties(c1);
  1698. //A point is just a circle with a diameter of
  1699. //1 pixel, so we can cheat. All we need to do is an ordinary circle vs. circle
  1700. //Collision test. Just supply the point with the properties
  1701. //it needs
  1702. point.diameter = 1;
  1703. point.width = point.diameter;
  1704. point.radius = 0.5;
  1705. point.centerX = point.x;
  1706. point.centerY = point.y;
  1707. point.gx = point.x;
  1708. point.gy = point.y;
  1709. point.xAnchorOffset = 0;
  1710. point.yAnchorOffset = 0;
  1711. point._bumpPropertiesAdded = true;
  1712. return this.hitTestCircle(c1, point, global);
  1713. }
  1714. /*
  1715. circleRectangleCollision
  1716. ------------------------
  1717. Use it to bounce a circular shape off a rectangular shape
  1718. Parameters:
  1719. a. A sprite object with `centerX`, `centerY`, `halfWidth` and `halfHeight` properties.
  1720. b. A sprite object with `centerX`, `centerY`, `halfWidth` and `halfHeight` properties.
  1721. */
  1722. }, {
  1723. key: "circleRectangleCollision",
  1724. value: function circleRectangleCollision(c1, r1) {
  1725. var bounce = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2];
  1726. var global = arguments.length <= 3 || arguments[3] === undefined ? false : arguments[3];
  1727. //Add collision properties
  1728. if (!r1._bumpPropertiesAdded) this.addCollisionProperties(r1);
  1729. if (!c1._bumpPropertiesAdded) this.addCollisionProperties(c1);
  1730. var region = undefined,
  1731. collision = undefined,
  1732. c1x = undefined,
  1733. c1y = undefined,
  1734. r1x = undefined,
  1735. r1y = undefined;
  1736. //Use either the global or local coordinates
  1737. if (global) {
  1738. c1x = c1.gx;
  1739. c1y = c1.gy;
  1740. r1x = r1.gx;
  1741. r1y = r1.gy;
  1742. } else {
  1743. c1x = c1.x;
  1744. c1y = c1.y;
  1745. r1x = r1.x;
  1746. r1y = r1.y;
  1747. }
  1748. //Is the circle above the rectangle's top edge?
  1749. if (c1y - c1.yAnchorOffset < r1y - Math.abs(r1.halfHeight) - r1.yAnchorOffset) {
  1750. //If it is, we need to check whether it's in the
  1751. //top left, top center or top right
  1752. if (c1x - c1.xAnchorOffset < r1x - 1 - Math.abs(r1.halfWidth) - r1.xAnchorOffset) {
  1753. region = "topLeft";
  1754. } else if (c1x - c1.xAnchorOffset > r1x + 1 + Math.abs(r1.halfWidth) - r1.xAnchorOffset) {
  1755. region = "topRight";
  1756. } else {
  1757. region = "topMiddle";
  1758. }
  1759. }
  1760. //The circle isn't above the top edge, so it might be
  1761. //below the bottom edge
  1762. else if (c1y - c1.yAnchorOffset > r1y + Math.abs(r1.halfHeight) - r1.yAnchorOffset) {
  1763. //If it is, we need to check whether it's in the bottom left,
  1764. //bottom center, or bottom right
  1765. if (c1x - c1.xAnchorOffset < r1x - 1 - Math.abs(r1.halfWidth) - r1.xAnchorOffset) {
  1766. region = "bottomLeft";
  1767. } else if (c1x - c1.xAnchorOffset > r1x + 1 + Math.abs(r1.halfWidth) - r1.xAnchorOffset) {
  1768. region = "bottomRight";
  1769. } else {
  1770. region = "bottomMiddle";
  1771. }
  1772. }
  1773. //The circle isn't above the top edge or below the bottom edge,
  1774. //so it must be on the left or right side
  1775. else {
  1776. if (c1x - c1.xAnchorOffset < r1x - Math.abs(r1.halfWidth) - r1.xAnchorOffset) {
  1777. region = "leftMiddle";
  1778. } else {
  1779. region = "rightMiddle";
  1780. }
  1781. }
  1782. //Is this the circle touching the flat sides
  1783. //of the rectangle?
  1784. if (region === "topMiddle" || region === "bottomMiddle" || region === "leftMiddle" || region === "rightMiddle") {
  1785. //Yes, it is, so do a standard rectangle vs. rectangle collision test
  1786. collision = this.rectangleCollision(c1, r1, bounce, global);
  1787. }
  1788. //The circle is touching one of the corners, so do a
  1789. //circle vs. point collision test
  1790. else {
  1791. var point = {};
  1792. switch (region) {
  1793. case "topLeft":
  1794. point.x = r1x - r1.xAnchorOffset;
  1795. point.y = r1y - r1.yAnchorOffset;
  1796. break;
  1797. case "topRight":
  1798. point.x = r1x + r1.width - r1.xAnchorOffset;
  1799. point.y = r1y - r1.yAnchorOffset;
  1800. break;
  1801. case "bottomLeft":
  1802. point.x = r1x - r1.xAnchorOffset;
  1803. point.y = r1y + r1.height - r1.yAnchorOffset;
  1804. break;
  1805. case "bottomRight":
  1806. point.x = r1x + r1.width - r1.xAnchorOffset;
  1807. point.y = r1y + r1.height - r1.yAnchorOffset;
  1808. }
  1809. //Check for a collision between the circle and the point
  1810. collision = this.circlePointCollision(c1, point, bounce, global);
  1811. }
  1812. if (collision) {
  1813. return region;
  1814. } else {
  1815. return collision;
  1816. }
  1817. }
  1818. /*
  1819. circlePointCollision
  1820. --------------------
  1821. Use it to boucnce a circle off a point.
  1822. Parameters:
  1823. a. A sprite object with `centerX`, `centerY`, and `radius` properties.
  1824. b. A point object with `x` and `y` properties.
  1825. */
  1826. }, {
  1827. key: "circlePointCollision",
  1828. value: function circlePointCollision(c1, point) {
  1829. var bounce = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2];
  1830. var global = arguments.length <= 3 || arguments[3] === undefined ? false : arguments[3];
  1831. //Add collision properties
  1832. if (!c1._bumpPropertiesAdded) this.addCollisionProperties(c1);
  1833. //A point is just a circle with a diameter of
  1834. //1 pixel, so we can cheat. All we need to do is an ordinary circle vs. circle
  1835. //Collision test. Just supply the point with the properties
  1836. //it needs
  1837. point.diameter = 1;
  1838. point.width = point.diameter;
  1839. point.radius = 0.5;
  1840. point.centerX = point.x;
  1841. point.centerY = point.y;
  1842. point.gx = point.x;
  1843. point.gy = point.y;
  1844. point.xAnchorOffset = 0;
  1845. point.yAnchorOffset = 0;
  1846. point._bumpPropertiesAdded = true;
  1847. return this.circleCollision(c1, point, bounce, global);
  1848. }
  1849. /*
  1850. bounceOffSurface
  1851. ----------------
  1852. Use this to bounce an object off another object.
  1853. Parameters:
  1854. a. An object with `v.x` and `v.y` properties. This represents the object that is colliding
  1855. with a surface.
  1856. b. An object with `x` and `y` properties. This represents the surface that the object
  1857. is colliding into.
  1858. The first object can optionally have a mass property that's greater than 1. The mass will
  1859. be used to dampen the bounce effect.
  1860. */
  1861. }, {
  1862. key: "bounceOffSurface",
  1863. value: function bounceOffSurface(o, s) {
  1864. //Add collision properties
  1865. if (!o._bumpPropertiesAdded) this.addCollisionProperties(o);
  1866. var dp1 = undefined,
  1867. dp2 = undefined,
  1868. p1 = {},
  1869. p2 = {},
  1870. bounce = {},
  1871. mass = o.mass || 1;
  1872. //1. Calculate the collision surface's properties
  1873. //Find the surface vector's left normal
  1874. s.lx = s.y;
  1875. s.ly = -s.x;
  1876. //Find its magnitude
  1877. s.magnitude = Math.sqrt(s.x * s.x + s.y * s.y);
  1878. //Find its normalized values
  1879. s.dx = s.x / s.magnitude;
  1880. s.dy = s.y / s.magnitude;
  1881. //2. Bounce the object (o) off the surface (s)
  1882. //Find the dot product between the object and the surface
  1883. dp1 = o.vx * s.dx + o.vy * s.dy;
  1884. //Project the object's velocity onto the collision surface
  1885. p1.vx = dp1 * s.dx;
  1886. p1.vy = dp1 * s.dy;
  1887. //Find the dot product of the object and the surface's left normal (s.lx and s.ly)
  1888. dp2 = o.vx * (s.lx / s.magnitude) + o.vy * (s.ly / s.magnitude);
  1889. //Project the object's velocity onto the surface's left normal
  1890. p2.vx = dp2 * (s.lx / s.magnitude);
  1891. p2.vy = dp2 * (s.ly / s.magnitude);
  1892. //Reverse the projection on the surface's left normal
  1893. p2.vx *= -1;
  1894. p2.vy *= -1;
  1895. //Add up the projections to create a new bounce vector
  1896. bounce.x = p1.vx + p2.vx;
  1897. bounce.y = p1.vy + p2.vy;
  1898. //Assign the bounce vector to the object's velocity
  1899. //with optional mass to dampen the effect
  1900. o.vx = bounce.x / mass;
  1901. o.vy = bounce.y / mass;
  1902. }
  1903. /*
  1904. contain
  1905. -------
  1906. `contain` can be used to contain a sprite with `x` and
  1907. `y` properties inside a rectangular area.
  1908. The `contain` function takes four arguments: a sprite with `x` and `y`
  1909. properties, an object literal with `x`, `y`, `width` and `height` properties. The
  1910. third argument is a Boolean (true/false) value that determines if the sprite
  1911. should bounce when it hits the edge of the container. The fourth argument
  1912. is an extra user-defined callback function that you can call when the
  1913. sprite hits the container
  1914. ```js
  1915. contain(anySprite, {x: 0, y: 0, width: 512, height: 512}, true, callbackFunction);
  1916. ```
  1917. The code above will contain the sprite's position inside the 512 by
  1918. 512 pixel area defined by the object. If the sprite hits the edges of
  1919. the container, it will bounce. The `callBackFunction` will run if
  1920. there's a collision.
  1921. An additional feature of the `contain` method is that if the sprite
  1922. has a `mass` property, it will be used to dampen the sprite's bounce
  1923. in a natural looking way.
  1924. If the sprite bumps into any of the containing object's boundaries,
  1925. the `contain` function will return a value that tells you which side
  1926. the sprite bumped into: “left”, “top”, “right” or “bottom”. Here's how
  1927. you could keep the sprite contained and also find out which boundary
  1928. it hit:
  1929. ```js
  1930. //Contain the sprite and find the collision value
  1931. let collision = contain(anySprite, {x: 0, y: 0, width: 512, height: 512});
  1932. //If there's a collision, display the boundary that the collision happened on
  1933. if(collision) {
  1934. if collision.has("left") console.log("The sprite hit the left");
  1935. if collision.has("top") console.log("The sprite hit the top");
  1936. if collision.has("right") console.log("The sprite hit the right");
  1937. if collision.has("bottom") console.log("The sprite hit the bottom");
  1938. }
  1939. ```
  1940. If the sprite doesn't hit a boundary, the value of
  1941. `collision` will be `undefined`.
  1942. */
  1943. /*
  1944. contain(sprite, container, bounce = false, extra = undefined) {
  1945. //Helper methods that compensate for any possible shift the the
  1946. //sprites' anchor points
  1947. let nudgeAnchor = (o, value, axis) => {
  1948. if (o.anchor !== undefined) {
  1949. if (o.anchor[axis] !== 0) {
  1950. return value * ((1 - o.anchor[axis]) - o.anchor[axis]);
  1951. } else {
  1952. return value;
  1953. }
  1954. } else {
  1955. return value;
  1956. }
  1957. };
  1958. let compensateForAnchor = (o, value, axis) => {
  1959. if (o.anchor !== undefined) {
  1960. if (o.anchor[axis] !== 0) {
  1961. return value * o.anchor[axis];
  1962. } else {
  1963. return 0;
  1964. }
  1965. } else {
  1966. return 0;
  1967. }
  1968. };
  1969. let compensateForAnchors = (a, b, property1, property2) => {
  1970. return compensateForAnchor(a, a[property1], property2) + compensateForAnchor(b, b[property1], property2)
  1971. };
  1972. //Create a set called `collision` to keep track of the
  1973. //boundaries with which the sprite is colliding
  1974. let collision = new Set();
  1975. //Left
  1976. if (sprite.x - compensateForAnchor(sprite, sprite.width, "x") < container.x - sprite.parent.gx - compensateForAnchor(container, container.width, "x")) {
  1977. //Bounce the sprite if `bounce` is true
  1978. if (bounce) sprite.vx *= -1;
  1979. //If the sprite has `mass`, let the mass
  1980. //affect the sprite's velocity
  1981. if(sprite.mass) sprite.vx /= sprite.mass;
  1982. //Keep the sprite inside the container
  1983. sprite.x = container.x - sprite.parent.gx + compensateForAnchor(sprite, sprite.width, "x") - compensateForAnchor(container, container.width, "x");
  1984. //Add "left" to the collision set
  1985. collision.add("left");
  1986. }
  1987. //Top
  1988. if (sprite.y - compensateForAnchor(sprite, sprite.height, "y") < container.y - sprite.parent.gy - compensateForAnchor(container, container.height, "y")) {
  1989. if (bounce) sprite.vy *= -1;
  1990. if(sprite.mass) sprite.vy /= sprite.mass;
  1991. sprite.y = container.x - sprite.parent.gy + compensateForAnchor(sprite, sprite.height, "y") - compensateForAnchor(container, container.height, "y");
  1992. collision.add("top");
  1993. }
  1994. //Right
  1995. if (sprite.x - compensateForAnchor(sprite, sprite.width, "x") + sprite.width > container.width - compensateForAnchor(container, container.width, "x")) {
  1996. if (bounce) sprite.vx *= -1;
  1997. if(sprite.mass) sprite.vx /= sprite.mass;
  1998. sprite.x = container.width - sprite.width + compensateForAnchor(sprite, sprite.width, "x") - compensateForAnchor(container, container.width, "x");
  1999. collision.add("right");
  2000. }
  2001. //Bottom
  2002. if (sprite.y - compensateForAnchor(sprite, sprite.height, "y") + sprite.height > container.height - compensateForAnchor(container, container.height, "y")) {
  2003. if (bounce) sprite.vy *= -1;
  2004. if(sprite.mass) sprite.vy /= sprite.mass;
  2005. sprite.y = container.height - sprite.height + compensateForAnchor(sprite, sprite.height, "y") - compensateForAnchor(container, container.height, "y");
  2006. collision.add("bottom");
  2007. }
  2008. //If there were no collisions, set `collision` to `undefined`
  2009. if (collision.size === 0) collision = undefined;
  2010. //The `extra` function runs if there was a collision
  2011. //and `extra` has been defined
  2012. if (collision && extra) extra(collision);
  2013. //Return the `collision` value
  2014. return collision;
  2015. }
  2016. */
  2017. }, {
  2018. key: "contain",
  2019. value: function contain(sprite, container) {
  2020. var bounce = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2];
  2021. var extra = arguments.length <= 3 || arguments[3] === undefined ? undefined : arguments[3];
  2022. //Add collision properties
  2023. if (!sprite._bumpPropertiesAdded) this.addCollisionProperties(sprite);
  2024. //Give the container x and y anchor offset values, if it doesn't
  2025. //have any
  2026. if (container.xAnchorOffset === undefined) container.xAnchorOffset = 0;
  2027. if (container.yAnchorOffset === undefined) container.yAnchorOffset = 0;
  2028. if (sprite.parent.gx === undefined) sprite.parent.gx = 0;
  2029. if (sprite.parent.gy === undefined) sprite.parent.gy = 0;
  2030. //Create a Set called `collision` to keep track of the
  2031. //boundaries with which the sprite is colliding
  2032. var collision = new Set();
  2033. //Left
  2034. if (sprite.x - sprite.xAnchorOffset < container.x - sprite.parent.gx - container.xAnchorOffset) {
  2035. //Bounce the sprite if `bounce` is true
  2036. if (bounce) sprite.vx *= -1;
  2037. //If the sprite has `mass`, let the mass
  2038. //affect the sprite's velocity
  2039. if (sprite.mass) sprite.vx /= sprite.mass;
  2040. //Reposition the sprite inside the container
  2041. sprite.x = container.x - sprite.parent.gx - container.xAnchorOffset + sprite.xAnchorOffset;
  2042. //Make a record of the side which the container hit
  2043. collision.add("left");
  2044. }
  2045. //Top
  2046. if (sprite.y - sprite.yAnchorOffset < container.y - sprite.parent.gy - container.yAnchorOffset) {
  2047. if (bounce) sprite.vy *= -1;
  2048. if (sprite.mass) sprite.vy /= sprite.mass;
  2049. sprite.y = container.y - sprite.parent.gy - container.yAnchorOffset + sprite.yAnchorOffset;;
  2050. collision.add("top");
  2051. }
  2052. //Right
  2053. if (sprite.x - sprite.xAnchorOffset + sprite.width > container.width - container.xAnchorOffset) {
  2054. if (bounce) sprite.vx *= -1;
  2055. if (sprite.mass) sprite.vx /= sprite.mass;
  2056. sprite.x = container.width - sprite.width - container.xAnchorOffset + sprite.xAnchorOffset;
  2057. collision.add("right");
  2058. }
  2059. //Bottom
  2060. if (sprite.y - sprite.yAnchorOffset + sprite.height > container.height - container.yAnchorOffset) {
  2061. if (bounce) sprite.vy *= -1;
  2062. if (sprite.mass) sprite.vy /= sprite.mass;
  2063. sprite.y = container.height - sprite.height - container.yAnchorOffset + sprite.yAnchorOffset;
  2064. collision.add("bottom");
  2065. }
  2066. //If there were no collisions, set `collision` to `undefined`
  2067. if (collision.size === 0) collision = undefined;
  2068. //The `extra` function runs if there was a collision
  2069. //and `extra` has been defined
  2070. if (collision && extra) extra(collision);
  2071. //Return the `collision` value
  2072. return collision;
  2073. }
  2074. //`outsideBounds` checks whether a sprite is outide the boundary of
  2075. //another object. It returns an object called `collision`. `collision` will be `undefined` if there's no
  2076. //collision. But if there is a collision, `collision` will be
  2077. //returned as a Set containg strings that tell you which boundary
  2078. //side was crossed: "left", "right", "top" or "bottom"
  2079. }, {
  2080. key: "outsideBounds",
  2081. value: function outsideBounds(s, bounds, extra) {
  2082. var x = bounds.x,
  2083. y = bounds.y,
  2084. width = bounds.width,
  2085. height = bounds.height;
  2086. //The `collision` object is used to store which
  2087. //side of the containing rectangle the sprite hits
  2088. var collision = new Set();
  2089. //Left
  2090. if (s.x < x - s.width) {
  2091. collision.add("left");
  2092. }
  2093. //Top
  2094. if (s.y < y - s.height) {
  2095. collision.add("top");
  2096. }
  2097. //Right
  2098. if (s.x > width + s.width) {
  2099. collision.add("right");
  2100. }
  2101. //Bottom
  2102. if (s.y > height + s.height) {
  2103. collision.add("bottom");
  2104. }
  2105. //If there were no collisions, set `collision` to `undefined`
  2106. if (collision.size === 0) collision = undefined;
  2107. //The `extra` function runs if there was a collision
  2108. //and `extra` has been defined
  2109. if (collision && extra) extra(collision);
  2110. //Return the `collision` object
  2111. return collision;
  2112. }
  2113. /*
  2114. _getCenter
  2115. ----------
  2116. A utility that finds the center point of the sprite. If it's anchor point is the
  2117. sprite's top left corner, then the center is calculated from that point.
  2118. If the anchor point has been shifted, then the anchor x/y point is used as the sprite's center
  2119. */
  2120. }, {
  2121. key: "_getCenter",
  2122. value: function _getCenter(o, dimension, axis) {
  2123. if (o.anchor !== undefined) {
  2124. if (o.anchor[axis] !== 0) {
  2125. return 0;
  2126. } else {
  2127. //console.log(o.anchor[axis])
  2128. return dimension / 2;
  2129. }
  2130. } else {
  2131. return dimension;
  2132. }
  2133. }
  2134. /*
  2135. hit
  2136. ---
  2137. A convenient universal collision function to test for collisions
  2138. between rectangles, circles, and points.
  2139. */
  2140. }, {
  2141. key: "hit",
  2142. value: function hit(a, b) {
  2143. var react = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2];
  2144. var bounce = arguments.length <= 3 || arguments[3] === undefined ? false : arguments[3];
  2145. var global = arguments[4];
  2146. var extra = arguments.length <= 5 || arguments[5] === undefined ? undefined : arguments[5];
  2147. //Local references to bump's collision methods
  2148. var hitTestPoint = this.hitTestPoint.bind(this),
  2149. hitTestRectangle = this.hitTestRectangle.bind(this),
  2150. hitTestCircle = this.hitTestCircle.bind(this),
  2151. movingCircleCollision = this.movingCircleCollision.bind(this),
  2152. circleCollision = this.circleCollision.bind(this),
  2153. hitTestCircleRectangle = this.hitTestCircleRectangle.bind(this),
  2154. rectangleCollision = this.rectangleCollision.bind(this),
  2155. circleRectangleCollision = this.circleRectangleCollision.bind(this);
  2156. var collision = undefined,
  2157. aIsASprite = a.parent !== undefined,
  2158. bIsASprite = b.parent !== undefined;
  2159. //Check to make sure one of the arguments isn't an array
  2160. if (aIsASprite && b instanceof Array || bIsASprite && a instanceof Array) {
  2161. //If it is, check for a collision between a sprite and an array
  2162. spriteVsArray();
  2163. } else {
  2164. //If one of the arguments isn't an array, find out what type of
  2165. //collision check to run
  2166. collision = findCollisionType(a, b);
  2167. if (collision && extra) extra(collision);
  2168. }
  2169. //Return the result of the collision.
  2170. //It will be `undefined` if there's no collision and `true` if
  2171. //there is a collision. `rectangleCollision` sets `collsision` to
  2172. //"top", "bottom", "left" or "right" depeneding on which side the
  2173. //collision is occuring on
  2174. return collision;
  2175. function findCollisionType(a, b) {
  2176. //Are `a` and `b` both sprites?
  2177. //(We have to check again if this function was called from
  2178. //`spriteVsArray`)
  2179. var aIsASprite = a.parent !== undefined;
  2180. var bIsASprite = b.parent !== undefined;
  2181. if (aIsASprite && bIsASprite) {
  2182. //Yes, but what kind of sprites?
  2183. if (a.diameter && b.diameter) {
  2184. //They're circles
  2185. return circleVsCircle(a, b);
  2186. } else if (a.diameter && !b.diameter) {
  2187. //The first one is a circle and the second is a rectangle
  2188. return circleVsRectangle(a, b);
  2189. } else {
  2190. //They're rectangles
  2191. return rectangleVsRectangle(a, b);
  2192. }
  2193. }
  2194. //They're not both sprites, so what are they?
  2195. //Is `a` not a sprite and does it have x and y properties?
  2196. else if (bIsASprite && !(a.x === undefined) && !(a.y === undefined)) {
  2197. //Yes, so this is a point vs. sprite collision test
  2198. return hitTestPoint(a, b);
  2199. } else {
  2200. //The user is trying to test some incompatible objects
  2201. throw new Error("I'm sorry, " + a + " and " + b + " cannot be use together in a collision test.'");
  2202. }
  2203. }
  2204. function spriteVsArray() {
  2205. //If `a` happens to be the array, flip it around so that it becomes `b`
  2206. if (a instanceof Array) {
  2207. var _ref = [_b, _a];
  2208. var _a = _ref[0];
  2209. var _b = _ref[1];
  2210. }
  2211. //Loop through the array in reverse
  2212. for (var i = b.length - 1; i >= 0; i--) {
  2213. var sprite = b[i];
  2214. collision = findCollisionType(a, sprite);
  2215. if (collision && extra) extra(collision, sprite);
  2216. }
  2217. }
  2218. function circleVsCircle(a, b) {
  2219. //If the circles shouldn't react to the collision,
  2220. //just test to see if they're touching
  2221. if (!react) {
  2222. return hitTestCircle(a, b);
  2223. }
  2224. //Yes, the circles should react to the collision
  2225. else {
  2226. //Are they both moving?
  2227. if (a.vx + a.vy !== 0 && b.vx + b.vy !== 0) {
  2228. //Yes, they are both moving
  2229. //(moving circle collisions always bounce apart so there's
  2230. //no need for the third, `bounce`, argument)
  2231. return movingCircleCollision(a, b, global);
  2232. } else {
  2233. //No, they're not both moving
  2234. return circleCollision(a, b, bounce, global);
  2235. }
  2236. }
  2237. }
  2238. function rectangleVsRectangle(a, b) {
  2239. //If the rectangles shouldn't react to the collision, just
  2240. //test to see if they're touching
  2241. if (!react) {
  2242. return hitTestRectangle(a, b, global);
  2243. } else {
  2244. return rectangleCollision(a, b, bounce, global);
  2245. }
  2246. }
  2247. function circleVsRectangle(a, b) {
  2248. //If the rectangles shouldn't react to the collision, just
  2249. //test to see if they're touching
  2250. if (!react) {
  2251. return hitTestCircleRectangle(a, b, global);
  2252. } else {
  2253. return circleRectangleCollision(a, b, bounce, global);
  2254. }
  2255. }
  2256. }
  2257. }]);
  2258. return Bump;
  2259. })();
  2260. //# sourceMappingURL=bump.js.map"use strict";
  2261. var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
  2262. function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
  2263. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  2264. var Charm = (function () {
  2265. function Charm() {
  2266. var _this = this;
  2267. var renderingEngine = arguments.length <= 0 || arguments[0] === undefined ? PIXI : arguments[0];
  2268. _classCallCheck(this, Charm);
  2269. if (renderingEngine === undefined) throw new Error("Please assign a rendering engine in the constructor before using charm.js");
  2270. //Find out which rendering engine is being used (the default is Pixi)
  2271. this.renderer = "";
  2272. //If the `renderingEngine` is Pixi, set up Pixi object aliases
  2273. if (renderingEngine.ParticleContainer && renderingEngine.Sprite) {
  2274. this.renderer = "pixi";
  2275. }
  2276. //An array to store the global tweens
  2277. this.globalTweens = [];
  2278. //An object that stores all the easing formulas
  2279. this.easingFormulas = {
  2280. //Linear
  2281. linear: function linear(x) {
  2282. return x;
  2283. },
  2284. //Smoothstep
  2285. smoothstep: function smoothstep(x) {
  2286. return x * x * (3 - 2 * x);
  2287. },
  2288. smoothstepSquared: function smoothstepSquared(x) {
  2289. return Math.pow(x * x * (3 - 2 * x), 2);
  2290. },
  2291. smoothstepCubed: function smoothstepCubed(x) {
  2292. return Math.pow(x * x * (3 - 2 * x), 3);
  2293. },
  2294. //Acceleration
  2295. acceleration: function acceleration(x) {
  2296. return x * x;
  2297. },
  2298. accelerationCubed: function accelerationCubed(x) {
  2299. return Math.pow(x * x, 3);
  2300. },
  2301. //Deceleration
  2302. deceleration: function deceleration(x) {
  2303. return 1 - Math.pow(1 - x, 2);
  2304. },
  2305. decelerationCubed: function decelerationCubed(x) {
  2306. return 1 - Math.pow(1 - x, 3);
  2307. },
  2308. //Sine
  2309. sine: function sine(x) {
  2310. return Math.sin(x * Math.PI / 2);
  2311. },
  2312. sineSquared: function sineSquared(x) {
  2313. return Math.pow(Math.sin(x * Math.PI / 2), 2);
  2314. },
  2315. sineCubed: function sineCubed(x) {
  2316. return Math.pow(Math.sin(x * Math.PI / 2), 2);
  2317. },
  2318. inverseSine: function inverseSine(x) {
  2319. return 1 - Math.sin((1 - x) * Math.PI / 2);
  2320. },
  2321. inverseSineSquared: function inverseSineSquared(x) {
  2322. return 1 - Math.pow(Math.sin((1 - x) * Math.PI / 2), 2);
  2323. },
  2324. inverseSineCubed: function inverseSineCubed(x) {
  2325. return 1 - Math.pow(Math.sin((1 - x) * Math.PI / 2), 3);
  2326. },
  2327. //Spline
  2328. spline: function spline(t, p0, p1, p2, p3) {
  2329. return 0.5 * (2 * p1 + (-p0 + p2) * t + (2 * p0 - 5 * p1 + 4 * p2 - p3) * t * t + (-p0 + 3 * p1 - 3 * p2 + p3) * t * t * t);
  2330. },
  2331. //Bezier curve
  2332. cubicBezier: function cubicBezier(t, a, b, c, d) {
  2333. var t2 = t * t;
  2334. var t3 = t2 * t;
  2335. return a + (-a * 3 + t * (3 * a - a * t)) * t + (3 * b + t * (-6 * b + b * 3 * t)) * t + (c * 3 - c * 3 * t) * t2 + d * t3;
  2336. }
  2337. };
  2338. //Add `scaleX` and `scaleY` properties to Pixi sprites
  2339. this._addScaleProperties = function (sprite) {
  2340. if (_this.renderer === "pixi") {
  2341. if (!("scaleX" in sprite) && "scale" in sprite && "x" in sprite.scale) {
  2342. Object.defineProperty(sprite, "scaleX", {
  2343. get: function get() {
  2344. return sprite.scale.x;
  2345. },
  2346. set: function set(value) {
  2347. sprite.scale.x = value;
  2348. }
  2349. });
  2350. }
  2351. if (!("scaleY" in sprite) && "scale" in sprite && "y" in sprite.scale) {
  2352. Object.defineProperty(sprite, "scaleY", {
  2353. get: function get() {
  2354. return sprite.scale.y;
  2355. },
  2356. set: function set(value) {
  2357. sprite.scale.y = value;
  2358. }
  2359. });
  2360. }
  2361. }
  2362. };
  2363. }
  2364. //The low level `tweenProperty` function is used as the foundation
  2365. //for the the higher level tween methods.
  2366. _createClass(Charm, [{
  2367. key: "tweenProperty",
  2368. value: function tweenProperty(sprite, //Sprite object
  2369. property, //String property
  2370. startValue, //Tween start value
  2371. endValue, //Tween end value
  2372. totalFrames) //Delay in frames before repeating
  2373. {
  2374. var type = arguments.length <= 5 || arguments[5] === undefined ? "smoothstep" : arguments[5];
  2375. var _this2 = this;
  2376. var yoyo = arguments.length <= 6 || arguments[6] === undefined ? false : arguments[6];
  2377. var delayBeforeRepeat = arguments.length <= 7 || arguments[7] === undefined ? 0 : arguments[7];
  2378. //Create the tween object
  2379. var o = {};
  2380. //If the tween is a bounce type (a spline), set the
  2381. //start and end magnitude values
  2382. var typeArray = type.split(" ");
  2383. if (typeArray[0] === "bounce") {
  2384. o.startMagnitude = parseInt(typeArray[1]);
  2385. o.endMagnitude = parseInt(typeArray[2]);
  2386. }
  2387. //Use `o.start` to make a new tween using the current
  2388. //end point values
  2389. o.start = function (startValue, endValue) {
  2390. //Clone the start and end values so that any possible references to sprite
  2391. //properties are converted to ordinary numbers
  2392. o.startValue = JSON.parse(JSON.stringify(startValue));
  2393. o.endValue = JSON.parse(JSON.stringify(endValue));
  2394. o.playing = true;
  2395. o.totalFrames = totalFrames;
  2396. o.frameCounter = 0;
  2397. //Add the tween to the global `tweens` array. The `tweens` array is
  2398. //updated on each frame
  2399. _this2.globalTweens.push(o);
  2400. };
  2401. //Call `o.start` to start the tween
  2402. o.start(startValue, endValue);
  2403. //The `update` method will be called on each frame by the game loop.
  2404. //This is what makes the tween move
  2405. o.update = function () {
  2406. var time = undefined,
  2407. curvedTime = undefined;
  2408. if (o.playing) {
  2409. //If the elapsed frames are less than the total frames,
  2410. //use the tweening formulas to move the sprite
  2411. if (o.frameCounter < o.totalFrames) {
  2412. //Find the normalized value
  2413. var normalizedTime = o.frameCounter / o.totalFrames;
  2414. //Select the correct easing function from the
  2415. //`ease` object’s library of easing functions
  2416. //If it's not a spline, use one of the ordinary easing functions
  2417. if (typeArray[0] !== "bounce") {
  2418. curvedTime = _this2.easingFormulas[type](normalizedTime);
  2419. }
  2420. //If it's a spline, use the `spline` function and apply the
  2421. //2 additional `type` array values as the spline's start and
  2422. //end points
  2423. else {
  2424. curvedTime = _this2.easingFormulas.spline(normalizedTime, o.startMagnitude, 0, 1, o.endMagnitude);
  2425. }
  2426. //Interpolate the sprite's property based on the curve
  2427. sprite[property] = o.endValue * curvedTime + o.startValue * (1 - curvedTime);
  2428. o.frameCounter += 1;
  2429. }
  2430. //When the tween has finished playing, run the end tasks
  2431. else {
  2432. sprite[property] = o.endValue;
  2433. o.end();
  2434. }
  2435. }
  2436. };
  2437. //The `end` method will be called when the tween is finished
  2438. o.end = function () {
  2439. //Set `playing` to `false`
  2440. o.playing = false;
  2441. //Call the tween's `onComplete` method, if it's been assigned
  2442. if (o.onComplete) o.onComplete();
  2443. //Remove the tween from the `tweens` array
  2444. _this2.globalTweens.splice(_this2.globalTweens.indexOf(o), 1);
  2445. //If the tween's `yoyo` property is `true`, create a new tween
  2446. //using the same values, but use the current tween's `startValue`
  2447. //as the next tween's `endValue`
  2448. if (yoyo) {
  2449. _this2.wait(delayBeforeRepeat).then(function () {
  2450. o.start(o.endValue, o.startValue);
  2451. });
  2452. }
  2453. };
  2454. //Pause and play methods
  2455. o.play = function () {
  2456. return o.playing = true;
  2457. };
  2458. o.pause = function () {
  2459. return o.playing = false;
  2460. };
  2461. //Return the tween object
  2462. return o;
  2463. }
  2464. //`makeTween` is a general low-level method for making complex tweens
  2465. //out of multiple `tweenProperty` functions. Its one argument,
  2466. //`tweensToAdd` is an array containing multiple `tweenProperty` calls
  2467. }, {
  2468. key: "makeTween",
  2469. value: function makeTween(tweensToAdd) {
  2470. var _this3 = this;
  2471. //Create an object to manage the tweens
  2472. var o = {};
  2473. //Create a `tweens` array to store the new tweens
  2474. o.tweens = [];
  2475. //Make a new tween for each array
  2476. tweensToAdd.forEach(function (tweenPropertyArguments) {
  2477. //Use the tween property arguments to make a new tween
  2478. var newTween = _this3.tweenProperty.apply(_this3, _toConsumableArray(tweenPropertyArguments));
  2479. //Push the new tween into this object's internal `tweens` array
  2480. o.tweens.push(newTween);
  2481. });
  2482. //Add a counter to keep track of the
  2483. //number of tweens that have completed their actions
  2484. var completionCounter = 0;
  2485. //`o.completed` will be called each time one of the tweens
  2486. //finishes
  2487. o.completed = function () {
  2488. //Add 1 to the `completionCounter`
  2489. completionCounter += 1;
  2490. //If all tweens have finished, call the user-defined `onComplete`
  2491. //method, if it's been assigned. Reset the `completionCounter`
  2492. if (completionCounter === o.tweens.length) {
  2493. if (o.onComplete) o.onComplete();
  2494. completionCounter = 0;
  2495. }
  2496. };
  2497. //Add `onComplete` methods to all tweens
  2498. o.tweens.forEach(function (tween) {
  2499. tween.onComplete = function () {
  2500. return o.completed();
  2501. };
  2502. });
  2503. //Add pause and play methods to control all the tweens
  2504. o.pause = function () {
  2505. o.tweens.forEach(function (tween) {
  2506. tween.playing = false;
  2507. });
  2508. };
  2509. o.play = function () {
  2510. o.tweens.forEach(function (tween) {
  2511. tween.playing = true;
  2512. });
  2513. };
  2514. //Return the tween object
  2515. return o;
  2516. }
  2517. /* High level tween methods */
  2518. //1. Simple tweens
  2519. //`fadeOut`
  2520. }, {
  2521. key: "fadeOut",
  2522. value: function fadeOut(sprite) {
  2523. var frames = arguments.length <= 1 || arguments[1] === undefined ? 60 : arguments[1];
  2524. return this.tweenProperty(sprite, "alpha", sprite.alpha, 0, frames, "sine");
  2525. }
  2526. //`fadeIn`
  2527. }, {
  2528. key: "fadeIn",
  2529. value: function fadeIn(sprite) {
  2530. var frames = arguments.length <= 1 || arguments[1] === undefined ? 60 : arguments[1];
  2531. return this.tweenProperty(sprite, "alpha", sprite.alpha, 1, frames, "sine");
  2532. }
  2533. //`pulse`
  2534. //Fades the sprite in and out at a steady rate.
  2535. //Set the `minAlpha` to something greater than 0 if you
  2536. //don't want the sprite to fade away completely
  2537. }, {
  2538. key: "pulse",
  2539. value: function pulse(sprite) {
  2540. var frames = arguments.length <= 1 || arguments[1] === undefined ? 60 : arguments[1];
  2541. var minAlpha = arguments.length <= 2 || arguments[2] === undefined ? 0 : arguments[2];
  2542. return this.tweenProperty(sprite, "alpha", sprite.alpha, minAlpha, frames, "smoothstep", true);
  2543. }
  2544. //2. Complex tweens
  2545. }, {
  2546. key: "slide",
  2547. value: function slide(sprite, endX, endY) {
  2548. var frames = arguments.length <= 3 || arguments[3] === undefined ? 60 : arguments[3];
  2549. var type = arguments.length <= 4 || arguments[4] === undefined ? "smoothstep" : arguments[4];
  2550. var yoyo = arguments.length <= 5 || arguments[5] === undefined ? false : arguments[5];
  2551. var delayBeforeRepeat = arguments.length <= 6 || arguments[6] === undefined ? 0 : arguments[6];
  2552. return this.makeTween([
  2553. //Create the x axis tween
  2554. [sprite, "x", sprite.x, endX, frames, type, yoyo, delayBeforeRepeat],
  2555. //Create the y axis tween
  2556. [sprite, "y", sprite.y, endY, frames, type, yoyo, delayBeforeRepeat]]);
  2557. }
  2558. }, {
  2559. key: "breathe",
  2560. value: function breathe(sprite) {
  2561. var endScaleX = arguments.length <= 1 || arguments[1] === undefined ? 0.8 : arguments[1];
  2562. var endScaleY = arguments.length <= 2 || arguments[2] === undefined ? 0.8 : arguments[2];
  2563. var frames = arguments.length <= 3 || arguments[3] === undefined ? 60 : arguments[3];
  2564. var yoyo = arguments.length <= 4 || arguments[4] === undefined ? true : arguments[4];
  2565. var delayBeforeRepeat = arguments.length <= 5 || arguments[5] === undefined ? 0 : arguments[5];
  2566. //Add `scaleX` and `scaleY` properties to Pixi sprites
  2567. this._addScaleProperties(sprite);
  2568. return this.makeTween([
  2569. //Create the scaleX tween
  2570. [sprite, "scaleX", sprite.scaleX, endScaleX, frames, "smoothstepSquared", yoyo, delayBeforeRepeat],
  2571. //Create the scaleY tween
  2572. [sprite, "scaleY", sprite.scaleY, endScaleY, frames, "smoothstepSquared", yoyo, delayBeforeRepeat]]);
  2573. }
  2574. }, {
  2575. key: "scale",
  2576. value: function scale(sprite) {
  2577. var endScaleX = arguments.length <= 1 || arguments[1] === undefined ? 0.5 : arguments[1];
  2578. var endScaleY = arguments.length <= 2 || arguments[2] === undefined ? 0.5 : arguments[2];
  2579. var frames = arguments.length <= 3 || arguments[3] === undefined ? 60 : arguments[3];
  2580. //Add `scaleX` and `scaleY` properties to Pixi sprites
  2581. this._addScaleProperties(sprite);
  2582. return this.makeTween([
  2583. //Create the scaleX tween
  2584. [sprite, "scaleX", sprite.scaleX, endScaleX, frames, "smoothstep", false],
  2585. //Create the scaleY tween
  2586. [sprite, "scaleY", sprite.scaleY, endScaleY, frames, "smoothstep", false]]);
  2587. }
  2588. }, {
  2589. key: "strobe",
  2590. value: function strobe(sprite) {
  2591. var scaleFactor = arguments.length <= 1 || arguments[1] === undefined ? 1.3 : arguments[1];
  2592. var startMagnitude = arguments.length <= 2 || arguments[2] === undefined ? 10 : arguments[2];
  2593. var endMagnitude = arguments.length <= 3 || arguments[3] === undefined ? 20 : arguments[3];
  2594. var frames = arguments.length <= 4 || arguments[4] === undefined ? 10 : arguments[4];
  2595. var yoyo = arguments.length <= 5 || arguments[5] === undefined ? true : arguments[5];
  2596. var delayBeforeRepeat = arguments.length <= 6 || arguments[6] === undefined ? 0 : arguments[6];
  2597. var bounce = "bounce " + startMagnitude + " " + endMagnitude;
  2598. //Add `scaleX` and `scaleY` properties to Pixi sprites
  2599. this._addScaleProperties(sprite);
  2600. return this.makeTween([
  2601. //Create the scaleX tween
  2602. [sprite, "scaleX", sprite.scaleX, scaleFactor, frames, bounce, yoyo, delayBeforeRepeat],
  2603. //Create the scaleY tween
  2604. [sprite, "scaleY", sprite.scaleY, scaleFactor, frames, bounce, yoyo, delayBeforeRepeat]]);
  2605. }
  2606. }, {
  2607. key: "wobble",
  2608. value: function wobble(sprite) {
  2609. var scaleFactorX = arguments.length <= 1 || arguments[1] === undefined ? 1.2 : arguments[1];
  2610. var scaleFactorY = arguments.length <= 2 || arguments[2] === undefined ? 1.2 : arguments[2];
  2611. var frames = arguments.length <= 3 || arguments[3] === undefined ? 10 : arguments[3];
  2612. var xStartMagnitude = arguments.length <= 4 || arguments[4] === undefined ? 10 : arguments[4];
  2613. var xEndMagnitude = arguments.length <= 5 || arguments[5] === undefined ? 10 : arguments[5];
  2614. var yStartMagnitude = arguments.length <= 6 || arguments[6] === undefined ? -10 : arguments[6];
  2615. var yEndMagnitude = arguments.length <= 7 || arguments[7] === undefined ? -10 : arguments[7];
  2616. var friction = arguments.length <= 8 || arguments[8] === undefined ? 0.98 : arguments[8];
  2617. var _this4 = this;
  2618. var yoyo = arguments.length <= 9 || arguments[9] === undefined ? true : arguments[9];
  2619. var delayBeforeRepeat = arguments.length <= 10 || arguments[10] === undefined ? 0 : arguments[10];
  2620. var bounceX = "bounce " + xStartMagnitude + " " + xEndMagnitude;
  2621. var bounceY = "bounce " + yStartMagnitude + " " + yEndMagnitude;
  2622. //Add `scaleX` and `scaleY` properties to Pixi sprites
  2623. this._addScaleProperties(sprite);
  2624. var o = this.makeTween([
  2625. //Create the scaleX tween
  2626. [sprite, "scaleX", sprite.scaleX, scaleFactorX, frames, bounceX, yoyo, delayBeforeRepeat],
  2627. //Create the scaleY tween
  2628. [sprite, "scaleY", sprite.scaleY, scaleFactorY, frames, bounceY, yoyo, delayBeforeRepeat]]);
  2629. //Add some friction to the `endValue` at the end of each tween
  2630. o.tweens.forEach(function (tween) {
  2631. tween.onComplete = function () {
  2632. //Add friction if the `endValue` is greater than 1
  2633. if (tween.endValue > 1) {
  2634. tween.endValue *= friction;
  2635. //Set the `endValue` to 1 when the effect is finished and
  2636. //remove the tween from the global `tweens` array
  2637. if (tween.endValue <= 1) {
  2638. tween.endValue = 1;
  2639. _this4.removeTween(tween);
  2640. }
  2641. }
  2642. };
  2643. });
  2644. return o;
  2645. }
  2646. //3. Motion path tweens
  2647. }, {
  2648. key: "followCurve",
  2649. value: function followCurve(sprite, pointsArray, totalFrames) {
  2650. var type = arguments.length <= 3 || arguments[3] === undefined ? "smoothstep" : arguments[3];
  2651. var _this5 = this;
  2652. var yoyo = arguments.length <= 4 || arguments[4] === undefined ? false : arguments[4];
  2653. var delayBeforeRepeat = arguments.length <= 5 || arguments[5] === undefined ? 0 : arguments[5];
  2654. //Create the tween object
  2655. var o = {};
  2656. //If the tween is a bounce type (a spline), set the
  2657. //start and end magnitude values
  2658. var typeArray = type.split(" ");
  2659. if (typeArray[0] === "bounce") {
  2660. o.startMagnitude = parseInt(typeArray[1]);
  2661. o.endMagnitude = parseInt(typeArray[2]);
  2662. }
  2663. //Use `tween.start` to make a new tween using the current
  2664. //end point values
  2665. o.start = function (pointsArray) {
  2666. o.playing = true;
  2667. o.totalFrames = totalFrames;
  2668. o.frameCounter = 0;
  2669. //Clone the points array
  2670. o.pointsArray = JSON.parse(JSON.stringify(pointsArray));
  2671. //Add the tween to the `globalTweens` array. The `globalTweens` array is
  2672. //updated on each frame
  2673. _this5.globalTweens.push(o);
  2674. };
  2675. //Call `tween.start` to start the first tween
  2676. o.start(pointsArray);
  2677. //The `update` method will be called on each frame by the game loop.
  2678. //This is what makes the tween move
  2679. o.update = function () {
  2680. var normalizedTime = undefined,
  2681. curvedTime = undefined,
  2682. p = o.pointsArray;
  2683. if (o.playing) {
  2684. //If the elapsed frames are less than the total frames,
  2685. //use the tweening formulas to move the sprite
  2686. if (o.frameCounter < o.totalFrames) {
  2687. //Find the normalized value
  2688. normalizedTime = o.frameCounter / o.totalFrames;
  2689. //Select the correct easing function
  2690. //If it's not a spline, use one of the ordinary tween
  2691. //functions
  2692. if (typeArray[0] !== "bounce") {
  2693. curvedTime = _this5.easingFormulas[type](normalizedTime);
  2694. }
  2695. //If it's a spline, use the `spline` function and apply the
  2696. //2 additional `type` array values as the spline's start and
  2697. //end points
  2698. else {
  2699. //curve = tweenFunction.spline(n, type[1], 0, 1, type[2]);
  2700. curvedTime = _this5.easingFormulas.spline(normalizedTime, o.startMagnitude, 0, 1, o.endMagnitude);
  2701. }
  2702. //Apply the Bezier curve to the sprite's position
  2703. sprite.x = _this5.easingFormulas.cubicBezier(curvedTime, p[0][0], p[1][0], p[2][0], p[3][0]);
  2704. sprite.y = _this5.easingFormulas.cubicBezier(curvedTime, p[0][1], p[1][1], p[2][1], p[3][1]);
  2705. //Add one to the `elapsedFrames`
  2706. o.frameCounter += 1;
  2707. }
  2708. //When the tween has finished playing, run the end tasks
  2709. else {
  2710. //sprite[property] = o.endValue;
  2711. o.end();
  2712. }
  2713. }
  2714. };
  2715. //The `end` method will be called when the tween is finished
  2716. o.end = function () {
  2717. //Set `playing` to `false`
  2718. o.playing = false;
  2719. //Call the tween's `onComplete` method, if it's been
  2720. //assigned
  2721. if (o.onComplete) o.onComplete();
  2722. //Remove the tween from the global `tweens` array
  2723. _this5.globalTweens.splice(_this5.globalTweens.indexOf(o), 1);
  2724. //If the tween's `yoyo` property is `true`, reverse the array and
  2725. //use it to create a new tween
  2726. if (yoyo) {
  2727. _this5.wait(delayBeforeRepeat).then(function () {
  2728. o.pointsArray = o.pointsArray.reverse();
  2729. o.start(o.pointsArray);
  2730. });
  2731. }
  2732. };
  2733. //Pause and play methods
  2734. o.pause = function () {
  2735. o.playing = false;
  2736. };
  2737. o.play = function () {
  2738. o.playing = true;
  2739. };
  2740. //Return the tween object
  2741. return o;
  2742. }
  2743. }, {
  2744. key: "walkPath",
  2745. value: function walkPath(sprite, //The sprite
  2746. originalPathArray) //Delay, in milliseconds, between sections
  2747. {
  2748. var totalFrames = arguments.length <= 2 || arguments[2] === undefined ? 300 : arguments[2];
  2749. var type = arguments.length <= 3 || arguments[3] === undefined ? "smoothstep" : arguments[3];
  2750. var loop = arguments.length <= 4 || arguments[4] === undefined ? false : arguments[4];
  2751. var _this6 = this;
  2752. var yoyo = arguments.length <= 5 || arguments[5] === undefined ? false : arguments[5];
  2753. var delayBetweenSections = arguments.length <= 6 || arguments[6] === undefined ? 0 : arguments[6];
  2754. //Clone the path array so that any possible references to sprite
  2755. //properties are converted into ordinary numbers
  2756. var pathArray = JSON.parse(JSON.stringify(originalPathArray));
  2757. //Figure out the duration, in frames, of each path section by
  2758. //dividing the `totalFrames` by the length of the `pathArray`
  2759. var frames = totalFrames / pathArray.length;
  2760. //Set the current point to 0, which will be the first waypoint
  2761. var currentPoint = 0;
  2762. //The `makePath` function creates a single tween between two points and
  2763. //then schedules the next path to be made after it
  2764. var makePath = function makePath(currentPoint) {
  2765. //Use the `makeTween` function to tween the sprite's
  2766. //x and y position
  2767. var tween = _this6.makeTween([
  2768. //Create the x axis tween between the first x value in the
  2769. //current point and the x value in the following point
  2770. [sprite, "x", pathArray[currentPoint][0], pathArray[currentPoint + 1][0], frames, type],
  2771. //Create the y axis tween in the same way
  2772. [sprite, "y", pathArray[currentPoint][1], pathArray[currentPoint + 1][1], frames, type]]);
  2773. //When the tween is complete, advance the `currentPoint` by one.
  2774. //Add an optional delay between path segments, and then make the
  2775. //next connecting path
  2776. tween.onComplete = function () {
  2777. //Advance to the next point
  2778. currentPoint += 1;
  2779. //If the sprite hasn't reached the end of the
  2780. //path, tween the sprite to the next point
  2781. if (currentPoint < pathArray.length - 1) {
  2782. _this6.wait(delayBetweenSections).then(function () {
  2783. tween = makePath(currentPoint);
  2784. });
  2785. }
  2786. //If we've reached the end of the path, optionally
  2787. //loop and yoyo it
  2788. else {
  2789. //Reverse the path if `loop` is `true`
  2790. if (loop) {
  2791. //Reverse the array if `yoyo` is `true`
  2792. if (yoyo) pathArray.reverse();
  2793. //Optionally wait before restarting
  2794. _this6.wait(delayBetweenSections).then(function () {
  2795. //Reset the `currentPoint` to 0 so that we can
  2796. //restart at the first point
  2797. currentPoint = 0;
  2798. //Set the sprite to the first point
  2799. sprite.x = pathArray[0][0];
  2800. sprite.y = pathArray[0][1];
  2801. //Make the first new path
  2802. tween = makePath(currentPoint);
  2803. //... and so it continues!
  2804. });
  2805. }
  2806. }
  2807. };
  2808. //Return the path tween to the main function
  2809. return tween;
  2810. };
  2811. //Make the first path using the internal `makePath` function (below)
  2812. var tween = makePath(currentPoint);
  2813. //Pass the tween back to the main program
  2814. return tween;
  2815. }
  2816. }, {
  2817. key: "walkCurve",
  2818. value: function walkCurve(sprite, //The sprite
  2819. pathArray) //Delay, in milliseconds, between sections
  2820. {
  2821. var totalFrames = arguments.length <= 2 || arguments[2] === undefined ? 300 : arguments[2];
  2822. var type = arguments.length <= 3 || arguments[3] === undefined ? "smoothstep" : arguments[3];
  2823. var loop = arguments.length <= 4 || arguments[4] === undefined ? false : arguments[4];
  2824. var _this7 = this;
  2825. var yoyo = arguments.length <= 5 || arguments[5] === undefined ? false : arguments[5];
  2826. var delayBeforeContinue = arguments.length <= 6 || arguments[6] === undefined ? 0 : arguments[6];
  2827. //Divide the `totalFrames` into sections for each part of the path
  2828. var frames = totalFrames / pathArray.length;
  2829. //Set the current curve to 0, which will be the first one
  2830. var currentCurve = 0;
  2831. //The `makePath` function
  2832. var makePath = function makePath(currentCurve) {
  2833. //Use the custom `followCurve` function to make
  2834. //a sprite follow a curve
  2835. var tween = _this7.followCurve(sprite, pathArray[currentCurve], frames, type);
  2836. //When the tween is complete, advance the `currentCurve` by one.
  2837. //Add an optional delay between path segments, and then make the
  2838. //next path
  2839. tween.onComplete = function () {
  2840. currentCurve += 1;
  2841. if (currentCurve < pathArray.length) {
  2842. _this7.wait(delayBeforeContinue).then(function () {
  2843. tween = makePath(currentCurve);
  2844. });
  2845. }
  2846. //If we've reached the end of the path, optionally
  2847. //loop and reverse it
  2848. else {
  2849. if (loop) {
  2850. if (yoyo) {
  2851. //Reverse order of the curves in the `pathArray`
  2852. pathArray.reverse();
  2853. //Reverse the order of the points in each curve
  2854. pathArray.forEach(function (curveArray) {
  2855. return curveArray.reverse();
  2856. });
  2857. }
  2858. //After an optional delay, reset the sprite to the
  2859. //beginning of the path and make the next new path
  2860. _this7.wait(delayBeforeContinue).then(function () {
  2861. currentCurve = 0;
  2862. sprite.x = pathArray[0][0];
  2863. sprite.y = pathArray[0][1];
  2864. tween = makePath(currentCurve);
  2865. });
  2866. }
  2867. }
  2868. };
  2869. //Return the path tween to the main function
  2870. return tween;
  2871. };
  2872. //Make the first path
  2873. var tween = makePath(currentCurve);
  2874. //Pass the tween back to the main program
  2875. return tween;
  2876. }
  2877. //4. Utilities
  2878. /*
  2879. The `wait` method lets you set up a timed sequence of events
  2880. wait(1000)
  2881. .then(() => console.log("One"))
  2882. .then(() => wait(1000))
  2883. .then(() => console.log("Two"))
  2884. .then(() => wait(1000))
  2885. .then(() => console.log("Three"))
  2886. */
  2887. }, {
  2888. key: "wait",
  2889. value: function wait() {
  2890. var duration = arguments.length <= 0 || arguments[0] === undefined ? 0 : arguments[0];
  2891. return new Promise(function (resolve, reject) {
  2892. setTimeout(resolve, duration);
  2893. });
  2894. }
  2895. //A utility to remove tweens from the game
  2896. }, {
  2897. key: "removeTween",
  2898. value: function removeTween(tweenObject) {
  2899. var _this8 = this;
  2900. //Remove the tween if `tweenObject` doesn't have any nested
  2901. //tween objects
  2902. if (!tweenObject.tweens) {
  2903. tweenObject.pause();
  2904. //array.splice(-1,1) will always remove last elemnt of array, so this
  2905. //extra check prevents that (Thank you, MCumic10! https://github.com/kittykatattack/charm/issues/5)
  2906. if (this.globalTweens.indexOf(tweenObject) != -1) {
  2907. this.globalTweens.splice(this.globalTweens.indexOf(tweenObject), 1);
  2908. }
  2909. //Otherwise, remove the nested tween objects
  2910. } else {
  2911. tweenObject.pause();
  2912. tweenObject.tweens.forEach(function (element) {
  2913. _this8.globalTweens.splice(_this8.globalTweens.indexOf(element), 1);
  2914. });
  2915. }
  2916. }
  2917. }, {
  2918. key: "update",
  2919. value: function update() {
  2920. //Update all the tween objects in the `globalTweens` array
  2921. if (this.globalTweens.length > 0) {
  2922. for (var i = this.globalTweens.length - 1; i >= 0; i--) {
  2923. var tween = this.globalTweens[i];
  2924. if (tween) tween.update();
  2925. }
  2926. }
  2927. }
  2928. }]);
  2929. return Charm;
  2930. })();
  2931. //# sourceMappingURL=charm.js.map"use strict";
  2932. var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
  2933. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  2934. var Tink = (function () {
  2935. function Tink(PIXI, element) {
  2936. var scale = arguments.length <= 2 || arguments[2] === undefined ? 1 : arguments[2];
  2937. _classCallCheck(this, Tink);
  2938. //Add element and scale properties
  2939. this.element = element;
  2940. this._scale = scale;
  2941. //An array to store all the draggable sprites
  2942. this.draggableSprites = [];
  2943. //An array to store all the pointer objects
  2944. //(there will usually just be one)
  2945. this.pointers = [];
  2946. //An array to store all the buttons and button-like
  2947. //interactive sprites
  2948. this.buttons = [];
  2949. //A local PIXI reference
  2950. this.PIXI = PIXI;
  2951. //Aliases for Pixi objects
  2952. this.TextureCache = this.PIXI.utils.TextureCache;
  2953. //Note: change MovieClip to AnimatedSprite for Pixi v4
  2954. this.AnimatedSprite = this.PIXI.extras.MovieClip;
  2955. this.Texture = this.PIXI.Texture;
  2956. }
  2957. _createClass(Tink, [{
  2958. key: "makeDraggable",
  2959. //`makeDraggable` lets you make a drag-and-drop sprite by pushing it
  2960. //into the `draggableSprites` array
  2961. value: function makeDraggable() {
  2962. var _this = this;
  2963. for (var _len = arguments.length, sprites = Array(_len), _key = 0; _key < _len; _key++) {
  2964. sprites[_key] = arguments[_key];
  2965. }
  2966. //If the first argument isn't an array of sprites...
  2967. if (!(sprites[0] instanceof Array)) {
  2968. sprites.forEach(function (sprite) {
  2969. _this.draggableSprites.push(sprite);
  2970. //If the sprite's `draggable` property hasn't already been defined by
  2971. //another library, like Hexi, define it
  2972. if (sprite.draggable === undefined) {
  2973. sprite.draggable = true;
  2974. sprite._localDraggableAllocation = true;
  2975. }
  2976. });
  2977. }
  2978. //If the first argument is an array of sprites...
  2979. else {
  2980. var spritesArray = sprites[0];
  2981. if (spritesArray.length > 0) {
  2982. for (var i = spritesArray.length - 1; i >= 0; i--) {
  2983. var sprite = spritesArray[i];
  2984. this.draggableSprites.push(sprite);
  2985. //If the sprite's `draggable` property hasn't already been defined by
  2986. //another library, like Hexi, define it
  2987. if (sprite.draggable === undefined) {
  2988. sprite.draggable = true;
  2989. sprite._localDraggableAllocation = true;
  2990. }
  2991. }
  2992. }
  2993. }
  2994. }
  2995. //`makeUndraggable` removes the sprite from the `draggableSprites`
  2996. //array
  2997. }, {
  2998. key: "makeUndraggable",
  2999. value: function makeUndraggable() {
  3000. var _this2 = this;
  3001. for (var _len2 = arguments.length, sprites = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
  3002. sprites[_key2] = arguments[_key2];
  3003. }
  3004. //If the first argument isn't an array of sprites...
  3005. if (!(sprites[0] instanceof Array)) {
  3006. sprites.forEach(function (sprite) {
  3007. _this2.draggableSprites.splice(_this2.draggableSprites.indexOf(sprite), 1);
  3008. if (sprite._localDraggableAllocation === true) sprite.draggable = false;
  3009. });
  3010. }
  3011. //If the first argument is an array of sprites
  3012. else {
  3013. var spritesArray = sprites[0];
  3014. if (spritesArray.length > 0) {
  3015. for (var i = spritesArray.length - 1; i >= 0; i--) {
  3016. var sprite = spritesArray[i];
  3017. this.draggableSprites.splice(this.draggableSprites.indexOf(sprite), 1);
  3018. if (sprite._localDraggableAllocation === true) sprite.draggable = false;
  3019. }
  3020. }
  3021. }
  3022. }
  3023. }, {
  3024. key: "makePointer",
  3025. value: function makePointer() {
  3026. var element = arguments.length <= 0 || arguments[0] === undefined ? this.element : arguments[0];
  3027. var scale = arguments.length <= 1 || arguments[1] === undefined ? this.scale : arguments[1];
  3028. //Get a reference to Tink's global `draggableSprites` array
  3029. var draggableSprites = this.draggableSprites;
  3030. //Get a reference to Tink's `addGlobalPositionProperties` method
  3031. var addGlobalPositionProperties = this.addGlobalPositionProperties;
  3032. //The pointer object will be returned by this function
  3033. var pointer = {
  3034. element: element,
  3035. _scale: scale,
  3036. //Private x and y properties
  3037. _x: 0,
  3038. _y: 0,
  3039. //Width and height
  3040. width: 1,
  3041. height: 1,
  3042. //The public x and y properties are divided by the scale. If the
  3043. //HTML element that the pointer is sensitive to (like the canvas)
  3044. //is scaled up or down, you can change the `scale` value to
  3045. //correct the pointer's position values
  3046. get x() {
  3047. return this._x / this.scale;
  3048. },
  3049. get y() {
  3050. return this._y / this.scale;
  3051. },
  3052. //Add `centerX` and `centerY` getters so that we
  3053. //can use the pointer's coordinates with easing
  3054. //and collision functions
  3055. get centerX() {
  3056. return this.x;
  3057. },
  3058. get centerY() {
  3059. return this.y;
  3060. },
  3061. //`position` returns an object with x and y properties that
  3062. //contain the pointer's position
  3063. get position() {
  3064. return {
  3065. x: this.x,
  3066. y: this.y
  3067. };
  3068. },
  3069. get scale() {
  3070. return this._scale;
  3071. },
  3072. set scale(value) {
  3073. this._scale = value;
  3074. },
  3075. //Add a `cursor` getter/setter to change the pointer's cursor
  3076. //style. Values can be "pointer" (for a hand icon) or "auto" for
  3077. //an ordinary arrow icon.
  3078. get cursor() {
  3079. return this.element.style.cursor;
  3080. },
  3081. set cursor(value) {
  3082. this.element.style.cursor = value;
  3083. },
  3084. //Booleans to track the pointer state
  3085. isDown: false,
  3086. isUp: true,
  3087. tapped: false,
  3088. //Properties to help measure the time between up and down states
  3089. downTime: 0,
  3090. elapsedTime: 0,
  3091. //Optional `press`,`release` and `tap` methods
  3092. press: undefined,
  3093. release: undefined,
  3094. tap: undefined,
  3095. //A `dragSprite` property to help with drag and drop
  3096. dragSprite: null,
  3097. //The drag offsets to help drag sprites
  3098. dragOffsetX: 0,
  3099. dragOffsetY: 0,
  3100. //A property to check whether or not the pointer
  3101. //is visible
  3102. _visible: true,
  3103. get visible() {
  3104. return this._visible;
  3105. },
  3106. set visible(value) {
  3107. if (value === true) {
  3108. this.cursor = "auto";
  3109. } else {
  3110. this.cursor = "none";
  3111. }
  3112. this._visible = value;
  3113. },
  3114. //The pointer's mouse `moveHandler`
  3115. moveHandler: function moveHandler(event) {
  3116. //Get the element that's firing the event
  3117. var element = event.target;
  3118. //Find the pointer’s x and y position (for mouse).
  3119. //Subtract the element's top and left offset from the browser window
  3120. this._x = event.pageX - element.offsetLeft;
  3121. this._y = event.pageY - element.offsetTop;
  3122. //Prevent the event's default behavior
  3123. event.preventDefault();
  3124. },
  3125. //The pointer's `touchmoveHandler`
  3126. touchmoveHandler: function touchmoveHandler(event) {
  3127. var element = event.target;
  3128. //Find the touch point's x and y position
  3129. this._x = event.targetTouches[0].pageX - element.offsetLeft;
  3130. this._y = event.targetTouches[0].pageY - element.offsetTop;
  3131. event.preventDefault();
  3132. },
  3133. //The pointer's `downHandler`
  3134. downHandler: function downHandler(event) {
  3135. //Set the down states
  3136. this.isDown = true;
  3137. this.isUp = false;
  3138. this.tapped = false;
  3139. //Capture the current time
  3140. this.downTime = Date.now();
  3141. //Call the `press` method if it's been assigned
  3142. if (this.press) this.press();
  3143. event.preventDefault();
  3144. },
  3145. //The pointer's `touchstartHandler`
  3146. touchstartHandler: function touchstartHandler(event) {
  3147. var element = event.target;
  3148. //Find the touch point's x and y position
  3149. this._x = event.targetTouches[0].pageX - element.offsetLeft;
  3150. this._y = event.targetTouches[0].pageY - element.offsetTop;
  3151. //Set the down states
  3152. this.isDown = true;
  3153. this.isUp = false;
  3154. this.tapped = false;
  3155. //Capture the current time
  3156. this.downTime = Date.now();
  3157. //Call the `press` method if it's been assigned
  3158. if (this.press) this.press();
  3159. event.preventDefault();
  3160. },
  3161. //The pointer's `upHandler`
  3162. upHandler: function upHandler(event) {
  3163. //Figure out how much time the pointer has been down
  3164. this.elapsedTime = Math.abs(this.downTime - Date.now());
  3165. //If it's less than 200 milliseconds, it must be a tap or click
  3166. if (this.elapsedTime <= 200 && this.tapped === false) {
  3167. this.tapped = true;
  3168. //Call the `tap` method if it's been assigned
  3169. if (this.tap) this.tap();
  3170. }
  3171. this.isUp = true;
  3172. this.isDown = false;
  3173. //Call the `release` method if it's been assigned
  3174. if (this.release) this.release();
  3175. //`event.preventDefault();` needs to be disabled to prevent <input> range sliders
  3176. //from getting trapped in Firefox (and possibly Safari)
  3177. //event.preventDefault();
  3178. },
  3179. //The pointer's `touchendHandler`
  3180. touchendHandler: function touchendHandler(event) {
  3181. //Figure out how much time the pointer has been down
  3182. this.elapsedTime = Math.abs(this.downTime - Date.now());
  3183. //If it's less than 200 milliseconds, it must be a tap or click
  3184. if (this.elapsedTime <= 200 && this.tapped === false) {
  3185. this.tapped = true;
  3186. //Call the `tap` method if it's been assigned
  3187. if (this.tap) this.tap();
  3188. }
  3189. this.isUp = true;
  3190. this.isDown = false;
  3191. //Call the `release` method if it's been assigned
  3192. if (this.release) this.release();
  3193. //event.preventDefault();
  3194. },
  3195. //`hitTestSprite` figures out if the pointer is touching a sprite
  3196. hitTestSprite: function hitTestSprite(sprite) {
  3197. //Add global `gx` and `gy` properties to the sprite if they
  3198. //don't already exist
  3199. addGlobalPositionProperties(sprite);
  3200. //The `hit` variable will become `true` if the pointer is
  3201. //touching the sprite and remain `false` if it isn't
  3202. var hit = false;
  3203. //Find out the sprite's offset from its anchor point
  3204. var xAnchorOffset = undefined,
  3205. yAnchorOffset = undefined;
  3206. if (sprite.anchor !== undefined) {
  3207. xAnchorOffset = sprite.width * sprite.anchor.x;
  3208. yAnchorOffset = sprite.height * sprite.anchor.y;
  3209. } else {
  3210. xAnchorOffset = 0;
  3211. yAnchorOffset = 0;
  3212. }
  3213. //Is the sprite rectangular?
  3214. if (!sprite.circular) {
  3215. //Get the position of the sprite's edges using global
  3216. //coordinates
  3217. var left = sprite.gx - xAnchorOffset,
  3218. right = sprite.gx + sprite.width - xAnchorOffset,
  3219. top = sprite.gy - yAnchorOffset,
  3220. bottom = sprite.gy + sprite.height - yAnchorOffset;
  3221. //Find out if the pointer is intersecting the rectangle.
  3222. //`hit` will become `true` if the pointer is inside the
  3223. //sprite's area
  3224. hit = this.x > left && this.x < right && this.y > top && this.y < bottom;
  3225. }
  3226. //Is the sprite circular?
  3227. else {
  3228. //Find the distance between the pointer and the
  3229. //center of the circle
  3230. var vx = this.x - (sprite.gx + sprite.width / 2 - xAnchorOffset),
  3231. vy = this.y - (sprite.gy + sprite.width / 2 - yAnchorOffset),
  3232. distance = Math.sqrt(vx * vx + vy * vy);
  3233. //The pointer is intersecting the circle if the
  3234. //distance is less than the circle's radius
  3235. hit = distance < sprite.width / 2;
  3236. }
  3237. //Check the value of `hit`
  3238. return hit;
  3239. }
  3240. };
  3241. //Bind the events to the handlers
  3242. //Mouse events
  3243. element.addEventListener("mousemove", pointer.moveHandler.bind(pointer), false);
  3244. element.addEventListener("mousedown", pointer.downHandler.bind(pointer), false);
  3245. //Add the `mouseup` event to the `window` to
  3246. //catch a mouse button release outside of the canvas area
  3247. window.addEventListener("mouseup", pointer.upHandler.bind(pointer), false);
  3248. //Touch events
  3249. element.addEventListener("touchmove", pointer.touchmoveHandler.bind(pointer), false);
  3250. element.addEventListener("touchstart", pointer.touchstartHandler.bind(pointer), false);
  3251. //Add the `touchend` event to the `window` object to
  3252. //catch a mouse button release outside of the canvas area
  3253. window.addEventListener("touchend", pointer.touchendHandler.bind(pointer), false);
  3254. //Disable the default pan and zoom actions on the `canvas`
  3255. element.style.touchAction = "none";
  3256. //Add the pointer to Tink's global `pointers` array
  3257. this.pointers.push(pointer);
  3258. //Return the pointer
  3259. return pointer;
  3260. }
  3261. //Many of Tink's objects, like pointers, use collision
  3262. //detection using the sprites' global x and y positions. To make
  3263. //this easier, new `gx` and `gy` properties are added to sprites
  3264. //that reference Pixi sprites' `getGlobalPosition()` values.
  3265. }, {
  3266. key: "addGlobalPositionProperties",
  3267. value: function addGlobalPositionProperties(sprite) {
  3268. if (sprite.gx === undefined) {
  3269. Object.defineProperty(sprite, "gx", {
  3270. get: function get() {
  3271. return sprite.getGlobalPosition().x;
  3272. }
  3273. });
  3274. }
  3275. if (sprite.gy === undefined) {
  3276. Object.defineProperty(sprite, "gy", {
  3277. get: function get() {
  3278. return sprite.getGlobalPosition().y;
  3279. }
  3280. });
  3281. }
  3282. }
  3283. //A method that implments drag-and-drop functionality
  3284. //for each pointer
  3285. }, {
  3286. key: "updateDragAndDrop",
  3287. value: function updateDragAndDrop(draggableSprites) {
  3288. //Create a pointer if one doesn't already exist
  3289. if (this.pointers.length === 0) {
  3290. this.makePointer(this.element, this.scale);
  3291. }
  3292. //Loop through all the pointers in Tink's global `pointers` array
  3293. //(there will usually just be one, but you never know)
  3294. this.pointers.forEach(function (pointer) {
  3295. //Check whether the pointer is pressed down
  3296. if (pointer.isDown) {
  3297. //You need to capture the co-ordinates at which the pointer was
  3298. //pressed down and find out if it's touching a sprite
  3299. //Only run pointer.code if the pointer isn't already dragging
  3300. //sprite
  3301. if (pointer.dragSprite === null) {
  3302. //Loop through the `draggableSprites` in reverse to start searching at the bottom of the stack
  3303. for (var i = draggableSprites.length - 1; i > -1; i--) {
  3304. //Get a reference to the current sprite
  3305. var sprite = draggableSprites[i];
  3306. //Check for a collision with the pointer using `hitTestSprite`
  3307. if (pointer.hitTestSprite(sprite) && sprite.draggable) {
  3308. //Calculate the difference between the pointer's
  3309. //position and the sprite's position
  3310. pointer.dragOffsetX = pointer.x - sprite.gx;
  3311. pointer.dragOffsetY = pointer.y - sprite.gy;
  3312. //Set the sprite as the pointer's `dragSprite` property
  3313. pointer.dragSprite = sprite;
  3314. //The next two lines re-order the `sprites` array so that the
  3315. //selected sprite is displayed above all the others.
  3316. //First, splice the sprite out of its current position in
  3317. //its parent's `children` array
  3318. var children = sprite.parent.children;
  3319. children.splice(children.indexOf(sprite), 1);
  3320. //Next, push the `dragSprite` to the end of its `children` array so that it's
  3321. //displayed last, above all the other sprites
  3322. children.push(sprite);
  3323. //Reorganize the `draggableSpites` array in the same way
  3324. draggableSprites.splice(draggableSprites.indexOf(sprite), 1);
  3325. draggableSprites.push(sprite);
  3326. //Break the loop, because we only need to drag the topmost sprite
  3327. break;
  3328. }
  3329. }
  3330. }
  3331. //If the pointer is down and it has a `dragSprite`, make the sprite follow the pointer's
  3332. //position, with the calculated offset
  3333. else {
  3334. pointer.dragSprite.x = pointer.x - pointer.dragOffsetX;
  3335. pointer.dragSprite.y = pointer.y - pointer.dragOffsetY;
  3336. }
  3337. }
  3338. //If the pointer is up, drop the `dragSprite` by setting it to `null`
  3339. if (pointer.isUp) {
  3340. pointer.dragSprite = null;
  3341. }
  3342. //Change the mouse arrow pointer to a hand if it's over a
  3343. //draggable sprite
  3344. draggableSprites.some(function (sprite) {
  3345. if (pointer.hitTestSprite(sprite) && sprite.draggable) {
  3346. if (pointer.visible) pointer.cursor = "pointer";
  3347. return true;
  3348. } else {
  3349. if (pointer.visible) pointer.cursor = "auto";
  3350. return false;
  3351. }
  3352. });
  3353. });
  3354. }
  3355. }, {
  3356. key: "makeInteractive",
  3357. value: function makeInteractive(o) {
  3358. //The `press`,`release`, `over`, `out` and `tap` methods. They're `undefined`
  3359. //for now, but they can be defined in the game program
  3360. o.press = o.press || undefined;
  3361. o.release = o.release || undefined;
  3362. o.over = o.over || undefined;
  3363. o.out = o.out || undefined;
  3364. o.tap = o.tap || undefined;
  3365. //The `state` property tells you the button's
  3366. //current state. Set its initial state to "up"
  3367. o.state = "up";
  3368. //The `action` property tells you whether its being pressed or
  3369. //released
  3370. o.action = "";
  3371. //The `pressed` and `hoverOver` Booleans are mainly for internal
  3372. //use in this code to help figure out the correct state.
  3373. //`pressed` is a Boolean that helps track whether or not
  3374. //the sprite has been pressed down
  3375. o.pressed = false;
  3376. //`hoverOver` is a Boolean which checks whether the pointer
  3377. //has hovered over the sprite
  3378. o.hoverOver = false;
  3379. //tinkType is a string that will be set to "button" if the
  3380. //user creates an object using the `button` function
  3381. o.tinkType = "";
  3382. //Set `enabled` to true to allow for interactivity
  3383. //Set `enabled` to false to disable interactivity
  3384. o.enabled = true;
  3385. //Add the sprite to the global `buttons` array so that it can
  3386. //be updated each frame in the `updateButtons method
  3387. this.buttons.push(o);
  3388. }
  3389. //The `updateButtons` method will be called each frame
  3390. //inside the game loop. It updates all the button-like sprites
  3391. }, {
  3392. key: "updateButtons",
  3393. value: function updateButtons() {
  3394. var _this3 = this;
  3395. //Create a pointer if one doesn't already exist
  3396. if (this.pointers.length === 0) {
  3397. this.makePointer(this.element, this.scale);
  3398. }
  3399. //Loop through all of Tink's pointers (there will usually
  3400. //just be one)
  3401. this.pointers.forEach(function (pointer) {
  3402. pointer.shouldBeHand = false;
  3403. //Loop through all the button-like sprites that were created
  3404. //using the `makeInteractive` method
  3405. _this3.buttons.forEach(function (o) {
  3406. //Only do this if the interactive object is enabled
  3407. if (o.enabled) {
  3408. //Figure out if the pointer is touching the sprite
  3409. var hit = pointer.hitTestSprite(o);
  3410. //1. Figure out the current state
  3411. if (pointer.isUp) {
  3412. //Up state
  3413. o.state = "up";
  3414. //Show the first image state frame, if this is a `Button` sprite
  3415. if (o.tinkType === "button") o.gotoAndStop(0);
  3416. }
  3417. //If the pointer is touching the sprite, figure out
  3418. //if the over or down state should be displayed
  3419. if (hit) {
  3420. //Over state
  3421. o.state = "over";
  3422. //Show the second image state frame if this sprite has
  3423. //3 frames and it's a `Button` sprite
  3424. if (o.totalFrames && o.totalFrames === 3 && o.tinkType === "button") {
  3425. o.gotoAndStop(1);
  3426. }
  3427. //Down state
  3428. if (pointer.isDown) {
  3429. o.state = "down";
  3430. //Show the third frame if this sprite is a `Button` sprite and it
  3431. //has only three frames, or show the second frame if it
  3432. //only has two frames
  3433. if (o.tinkType === "button") {
  3434. if (o.totalFrames === 3) {
  3435. o.gotoAndStop(2);
  3436. } else {
  3437. o.gotoAndStop(1);
  3438. }
  3439. }
  3440. }
  3441. //Flag this pointer to be changed to a hand
  3442. pointer.shouldBeHand = true;
  3443. //if (pointer.visible) pointer.cursor = "pointer";
  3444. // } else {
  3445. // //Turn the pointer to an ordinary arrow icon if the
  3446. // //pointer isn't touching a sprite
  3447. // if (pointer.visible) pointer.cursor = "auto";
  3448. //Change the pointer icon to a hand
  3449. if (pointer.visible) pointer.cursor = "pointer";
  3450. } else {
  3451. //Turn the pointer to an ordinary arrow icon if the
  3452. //pointer isn't touching a sprite
  3453. if (pointer.visible) pointer.cursor = "auto";
  3454. }
  3455. //Perform the correct interactive action
  3456. //a. Run the `press` method if the sprite state is "down" and
  3457. //the sprite hasn't already been pressed
  3458. if (o.state === "down") {
  3459. if (!o.pressed) {
  3460. if (o.press) o.press();
  3461. o.pressed = true;
  3462. o.action = "pressed";
  3463. }
  3464. }
  3465. //b. Run the `release` method if the sprite state is "over" and
  3466. //the sprite has been pressed
  3467. if (o.state === "over") {
  3468. if (o.pressed) {
  3469. if (o.release) o.release();
  3470. o.pressed = false;
  3471. o.action = "released";
  3472. //If the pointer was tapped and the user assigned a `tap`
  3473. //method, call the `tap` method
  3474. if (pointer.tapped && o.tap) o.tap();
  3475. }
  3476. //Run the `over` method if it has been assigned
  3477. if (!o.hoverOver) {
  3478. if (o.over) o.over();
  3479. o.hoverOver = true;
  3480. }
  3481. }
  3482. //c. Check whether the pointer has been released outside
  3483. //the sprite's area. If the button state is "up" and it's
  3484. //already been pressed, then run the `release` method.
  3485. if (o.state === "up") {
  3486. if (o.pressed) {
  3487. if (o.release) o.release();
  3488. o.pressed = false;
  3489. o.action = "released";
  3490. }
  3491. //Run the `out` method if it has been assigned
  3492. if (o.hoverOver) {
  3493. if (o.out) o.out();
  3494. o.hoverOver = false;
  3495. }
  3496. }
  3497. }
  3498. });
  3499. if (pointer.shouldBeHand) {
  3500. pointer.cursor = "pointer";
  3501. } else {
  3502. pointer.cursor = "auto";
  3503. }
  3504. });
  3505. }
  3506. //A function that creates a sprite with 3 frames that
  3507. //represent the button states: up, over and down
  3508. }, {
  3509. key: "button",
  3510. value: function button(source) {
  3511. var x = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1];
  3512. var y = arguments.length <= 2 || arguments[2] === undefined ? 0 : arguments[2];
  3513. //The sprite object that will be returned
  3514. var o = undefined;
  3515. //Is it an array of frame ids or textures?
  3516. if (typeof source[0] === "string") {
  3517. //They're strings, but are they pre-existing texture or
  3518. //paths to image files?
  3519. //Check to see if the first element matches a texture in the
  3520. //cache
  3521. if (this.TextureCache[source[0]]) {
  3522. //It does, so it's an array of frame ids
  3523. o = this.AnimatedSprite.fromFrames(source);
  3524. } else {
  3525. //It's not already in the cache, so let's load it
  3526. o = this.AnimatedSprite.fromImages(source);
  3527. }
  3528. }
  3529. //If the `source` isn't an array of strings, check whether
  3530. //it's an array of textures
  3531. else if (source[0] instanceof this.Texture) {
  3532. //Yes, it's an array of textures.
  3533. //Use them to make a AnimatedSprite o
  3534. o = new this.AnimatedSprite(source);
  3535. }
  3536. //Add interactive properties to the button
  3537. this.makeInteractive(o);
  3538. //Set the `tinkType` to "button"
  3539. o.tinkType = "button";
  3540. //Position the button
  3541. o.x = x;
  3542. o.y = y;
  3543. //Return the new button sprite
  3544. return o;
  3545. }
  3546. //Run the `udpate` function in your game loop
  3547. //to update all of Tink's interactive objects
  3548. }, {
  3549. key: "update",
  3550. value: function update() {
  3551. //Update the drag and drop system
  3552. if (this.draggableSprites.length !== 0) this.updateDragAndDrop(this.draggableSprites);
  3553. //Update the buttons and button-like interactive sprites
  3554. if (this.buttons.length !== 0) this.updateButtons();
  3555. }
  3556. /*
  3557. `keyboard` is a method that listens for and captures keyboard events. It's really
  3558. just a convenient wrapper function for HTML `keyup` and `keydown` events so that you can keep your application code clutter-free and easier to write and read.
  3559. Here's how to use the `keyboard` method. Create a new keyboard object like this:
  3560. ```js
  3561. let keyObject = keyboard(asciiKeyCodeNumber);
  3562. ```
  3563. It's one argument is the ASCII key code number of the keyboard key
  3564. that you want to listen for. [Here's a list of ASCII key codes you can
  3565. use](http://www.asciitable.com).
  3566. Then assign `press` and `release` methods to the keyboard object like this:
  3567. ```js
  3568. keyObject.press = () => {
  3569. //key object pressed
  3570. };
  3571. keyObject.release = () => {
  3572. //key object released
  3573. };
  3574. ```
  3575. Keyboard objects also have `isDown` and `isUp` Boolean properties that you can use to check the state of each key.
  3576. */
  3577. }, {
  3578. key: "keyboard",
  3579. value: function keyboard(keyCode) {
  3580. var key = {};
  3581. key.code = keyCode;
  3582. key.isDown = false;
  3583. key.isUp = true;
  3584. key.press = undefined;
  3585. key.release = undefined;
  3586. //The `downHandler`
  3587. key.downHandler = function (event) {
  3588. if (event.keyCode === key.code) {
  3589. if (key.isUp && key.press) key.press();
  3590. key.isDown = true;
  3591. key.isUp = false;
  3592. }
  3593. event.preventDefault();
  3594. };
  3595. //The `upHandler`
  3596. key.upHandler = function (event) {
  3597. if (event.keyCode === key.code) {
  3598. if (key.isDown && key.release) key.release();
  3599. key.isDown = false;
  3600. key.isUp = true;
  3601. }
  3602. event.preventDefault();
  3603. };
  3604. //Attach event listeners
  3605. window.addEventListener("keydown", key.downHandler.bind(key), false);
  3606. window.addEventListener("keyup", key.upHandler.bind(key), false);
  3607. //Return the key object
  3608. return key;
  3609. }
  3610. //`arrowControl` is a convenience method for updating a sprite's velocity
  3611. //for 4-way movement using the arrow directional keys. Supply it
  3612. //with the sprite you want to control and the speed per frame, in
  3613. //pixels, that you want to update the sprite's velocity
  3614. }, {
  3615. key: "arrowControl",
  3616. value: function arrowControl(sprite, speed) {
  3617. if (speed === undefined) {
  3618. throw new Error("Please supply the arrowControl method with the speed at which you want the sprite to move");
  3619. }
  3620. var upArrow = this.keyboard(38),
  3621. rightArrow = this.keyboard(39),
  3622. downArrow = this.keyboard(40),
  3623. leftArrow = this.keyboard(37);
  3624. //Assign key `press` methods
  3625. leftArrow.press = function () {
  3626. //Change the sprite's velocity when the key is pressed
  3627. sprite.vx = -speed;
  3628. sprite.vy = 0;
  3629. };
  3630. leftArrow.release = function () {
  3631. //If the left arrow has been released, and the right arrow isn't down,
  3632. //and the sprite isn't moving vertically:
  3633. //Stop the sprite
  3634. if (!rightArrow.isDown && sprite.vy === 0) {
  3635. sprite.vx = 0;
  3636. }
  3637. };
  3638. upArrow.press = function () {
  3639. sprite.vy = -speed;
  3640. sprite.vx = 0;
  3641. };
  3642. upArrow.release = function () {
  3643. if (!downArrow.isDown && sprite.vx === 0) {
  3644. sprite.vy = 0;
  3645. }
  3646. };
  3647. rightArrow.press = function () {
  3648. sprite.vx = speed;
  3649. sprite.vy = 0;
  3650. };
  3651. rightArrow.release = function () {
  3652. if (!leftArrow.isDown && sprite.vy === 0) {
  3653. sprite.vx = 0;
  3654. }
  3655. };
  3656. downArrow.press = function () {
  3657. sprite.vy = speed;
  3658. sprite.vx = 0;
  3659. };
  3660. downArrow.release = function () {
  3661. if (!upArrow.isDown && sprite.vx === 0) {
  3662. sprite.vy = 0;
  3663. }
  3664. };
  3665. }
  3666. }, {
  3667. key: "scale",
  3668. get: function get() {
  3669. return this._scale;
  3670. },
  3671. set: function set(value) {
  3672. this._scale = value;
  3673. //Update scale values for all pointers
  3674. this.pointers.forEach(function (pointer) {
  3675. return pointer.scale = value;
  3676. });
  3677. }
  3678. }]);
  3679. return Tink;
  3680. })();
  3681. //# sourceMappingURL=tink.js.map"use strict";
  3682. var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
  3683. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  3684. var Dust = (function () {
  3685. function Dust() {
  3686. var renderingEngine = arguments.length <= 0 || arguments[0] === undefined ? PIXI : arguments[0];
  3687. _classCallCheck(this, Dust);
  3688. if (renderingEngine === undefined) throw new Error("Please assign a rendering engine in the constructor before using pixiDust.js");
  3689. //Find out which rendering engine is being used (the default is Pixi)
  3690. this.renderer = "";
  3691. //If the `renderingEngine` is Pixi, set up Pixi object aliases
  3692. if (renderingEngine.ParticleContainer) {
  3693. this.Container = renderingEngine.Container;
  3694. this.renderer = "pixi";
  3695. }
  3696. //The `particles` array stores all the particles you make
  3697. this.globalParticles = [];
  3698. }
  3699. //Random number functions
  3700. _createClass(Dust, [{
  3701. key: "randomFloat",
  3702. value: function randomFloat(min, max) {
  3703. return min + Math.random() * (max - min);
  3704. }
  3705. }, {
  3706. key: "randomInt",
  3707. value: function randomInt(min, max) {
  3708. return Math.floor(Math.random() * (max - min + 1)) + min;
  3709. }
  3710. //Use the create function to create new particle effects
  3711. }, {
  3712. key: "create",
  3713. value: function create() {
  3714. var x = arguments.length <= 0 || arguments[0] === undefined ? 0 : arguments[0];
  3715. var y = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1];
  3716. var spriteFunction = arguments.length <= 2 || arguments[2] === undefined ? function () {
  3717. return console.log("Sprite creation function");
  3718. } : arguments[2];
  3719. var container = arguments.length <= 3 || arguments[3] === undefined ? function () {
  3720. return new _this.Container();
  3721. } : arguments[3];
  3722. var numberOfParticles = arguments.length <= 4 || arguments[4] === undefined ? 20 : arguments[4];
  3723. var gravity = arguments.length <= 5 || arguments[5] === undefined ? 0 : arguments[5];
  3724. var randomSpacing = arguments.length <= 6 || arguments[6] === undefined ? true : arguments[6];
  3725. var minAngle = arguments.length <= 7 || arguments[7] === undefined ? 0 : arguments[7];
  3726. var maxAngle = arguments.length <= 8 || arguments[8] === undefined ? 6.28 : arguments[8];
  3727. var minSize = arguments.length <= 9 || arguments[9] === undefined ? 4 : arguments[9];
  3728. var maxSize = arguments.length <= 10 || arguments[10] === undefined ? 16 : arguments[10];
  3729. var minSpeed = arguments.length <= 11 || arguments[11] === undefined ? 0.3 : arguments[11];
  3730. var maxSpeed = arguments.length <= 12 || arguments[12] === undefined ? 3 : arguments[12];
  3731. var minScaleSpeed = arguments.length <= 13 || arguments[13] === undefined ? 0.01 : arguments[13];
  3732. var maxScaleSpeed = arguments.length <= 14 || arguments[14] === undefined ? 0.05 : arguments[14];
  3733. var minAlphaSpeed = arguments.length <= 15 || arguments[15] === undefined ? 0.02 : arguments[15];
  3734. var maxAlphaSpeed = arguments.length <= 16 || arguments[16] === undefined ? 0.02 : arguments[16];
  3735. var _this = this;
  3736. var minRotationSpeed = arguments.length <= 17 || arguments[17] === undefined ? 0.01 : arguments[17];
  3737. var maxRotationSpeed = arguments.length <= 18 || arguments[18] === undefined ? 0.03 : arguments[18];
  3738. //An array to store the curent batch of particles
  3739. var particles = [];
  3740. //Add the current `particles` array to the `globalParticles` array
  3741. this.globalParticles.push(particles);
  3742. //An array to store the angles
  3743. var angles = [];
  3744. //A variable to store the current particle's angle
  3745. var angle = undefined;
  3746. //Figure out by how many radians each particle should be separated
  3747. var spacing = (maxAngle - minAngle) / (numberOfParticles - 1);
  3748. //Create an angle value for each particle and push that //value into the `angles` array
  3749. for (var i = 0; i < numberOfParticles; i++) {
  3750. //If `randomSpacing` is `true`, give the particle any angle
  3751. //value between `minAngle` and `maxAngle`
  3752. if (randomSpacing) {
  3753. angle = this.randomFloat(minAngle, maxAngle);
  3754. angles.push(angle);
  3755. }
  3756. //If `randomSpacing` is `false`, space each particle evenly,
  3757. //starting with the `minAngle` and ending with the `maxAngle`
  3758. else {
  3759. if (angle === undefined) angle = minAngle;
  3760. angles.push(angle);
  3761. angle += spacing;
  3762. }
  3763. }
  3764. //A function to make particles
  3765. var makeParticle = function makeParticle(angle) {
  3766. //Create the particle using the supplied sprite function
  3767. var particle = spriteFunction();
  3768. //Display a random frame if the particle has more than 1 frame
  3769. if (particle.totalFrames > 0) {
  3770. particle.gotoAndStop(_this.randomInt(0, particle.totalFrames - 1));
  3771. }
  3772. //Set a random width and height
  3773. var size = _this.randomInt(minSize, maxSize);
  3774. particle.width = size;
  3775. particle.height = size;
  3776. //Set the particle's `anchor` to its center
  3777. particle.anchor.set(0.5, 0.5);
  3778. //Set the x and y position
  3779. particle.x = x;
  3780. particle.y = y;
  3781. //Set a random speed to change the scale, alpha and rotation
  3782. particle.scaleSpeed = _this.randomFloat(minScaleSpeed, maxScaleSpeed);
  3783. particle.alphaSpeed = _this.randomFloat(minAlphaSpeed, maxAlphaSpeed);
  3784. particle.rotationSpeed = _this.randomFloat(minRotationSpeed, maxRotationSpeed);
  3785. //Set a random velocity at which the particle should move
  3786. var speed = _this.randomFloat(minSpeed, maxSpeed);
  3787. particle.vx = speed * Math.cos(angle);
  3788. particle.vy = speed * Math.sin(angle);
  3789. //Push the particle into the `particles` array.
  3790. //The `particles` array needs to be updated by the game loop each frame particles.push(particle);
  3791. particles.push(particle);
  3792. //Add the particle to its parent container
  3793. container.addChild(particle);
  3794. //The particle's `updateParticle` method is called on each frame of the
  3795. //game loop
  3796. particle.updateParticle = function () {
  3797. //Add gravity
  3798. particle.vy += gravity;
  3799. //Move the particle
  3800. particle.x += particle.vx;
  3801. particle.y += particle.vy;
  3802. //Change the particle's `scale`
  3803. if (particle.scale.x - particle.scaleSpeed > 0) {
  3804. particle.scale.x -= particle.scaleSpeed;
  3805. }
  3806. if (particle.scale.y - particle.scaleSpeed > 0) {
  3807. particle.scale.y -= particle.scaleSpeed;
  3808. }
  3809. //Change the particle's rotation
  3810. particle.rotation += particle.rotationSpeed;
  3811. //Change the particle's `alpha`
  3812. particle.alpha -= particle.alphaSpeed;
  3813. //Remove the particle if its `alpha` reaches zero
  3814. if (particle.alpha <= 0) {
  3815. container.removeChild(particle);
  3816. particles.splice(particles.indexOf(particle), 1);
  3817. }
  3818. };
  3819. };
  3820. //Make a particle for each angle
  3821. angles.forEach(function (angle) {
  3822. return makeParticle(angle);
  3823. });
  3824. //Return the `particles` array back to the main program
  3825. return particles;
  3826. }
  3827. //A particle emitter
  3828. }, {
  3829. key: "emitter",
  3830. value: function emitter(interval, particleFunction) {
  3831. var emitterObject = {},
  3832. timerInterval = undefined;
  3833. emitterObject.playing = false;
  3834. function play() {
  3835. if (!emitterObject.playing) {
  3836. particleFunction();
  3837. timerInterval = setInterval(emitParticle.bind(this), interval);
  3838. emitterObject.playing = true;
  3839. }
  3840. }
  3841. function stop() {
  3842. if (emitterObject.playing) {
  3843. clearInterval(timerInterval);
  3844. emitterObject.playing = false;
  3845. }
  3846. }
  3847. function emitParticle() {
  3848. particleFunction();
  3849. }
  3850. emitterObject.play = play;
  3851. emitterObject.stop = stop;
  3852. return emitterObject;
  3853. }
  3854. //A function to update the particles in the game loop
  3855. }, {
  3856. key: "update",
  3857. value: function update() {
  3858. //Check so see if the `globalParticles` array contains any
  3859. //sub-arrays
  3860. if (this.globalParticles.length > 0) {
  3861. //If it does, Loop through the particle arrays in reverse
  3862. for (var i = this.globalParticles.length - 1; i >= 0; i--) {
  3863. //Get the current particle sub-array
  3864. var particles = this.globalParticles[i];
  3865. //Loop through the `particles` sub-array and update the
  3866. //all the particle sprites that it contains
  3867. if (particles.length > 0) {
  3868. for (var j = particles.length - 1; j >= 0; j--) {
  3869. var particle = particles[j];
  3870. particle.updateParticle();
  3871. }
  3872. }
  3873. //Remove the particle array from the `globalParticles` array if doesn't
  3874. //contain any more sprites
  3875. else {
  3876. this.globalParticles.splice(this.globalParticles.indexOf(particles), 1);
  3877. }
  3878. }
  3879. }
  3880. }
  3881. }]);
  3882. return Dust;
  3883. })();
  3884. //# sourceMappingURL=dust.js.map"use strict";
  3885. var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
  3886. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  3887. var SpriteUtilities = (function () {
  3888. function SpriteUtilities() {
  3889. var renderingEngine = arguments.length <= 0 || arguments[0] === undefined ? PIXI : arguments[0];
  3890. _classCallCheck(this, SpriteUtilities);
  3891. if (renderingEngine === undefined) throw new Error("Please supply a reference to PIXI in the SpriteUtilities constructor before using spriteUtilities.js");
  3892. //Find out which rendering engine is being used (the default is Pixi)
  3893. this.renderer = "";
  3894. //If the `renderingEngine` is Pixi, set up Pixi object aliases
  3895. if (renderingEngine.ParticleContainer && renderingEngine.Sprite) {
  3896. this.renderer = "pixi";
  3897. this.Container = renderingEngine.Container;
  3898. this.ParticleContainer = renderingEngine.ParticleContainer;
  3899. this.TextureCache = renderingEngine.utils.TextureCache;
  3900. this.Texture = renderingEngine.Texture;
  3901. this.Rectangle = renderingEngine.Rectangle;
  3902. this.MovieClip = renderingEngine.extras.MovieClip;
  3903. this.BitmapText = renderingEngine.extras.BitmapText;
  3904. this.Sprite = renderingEngine.Sprite;
  3905. this.TilingSprite = renderingEngine.extras.TilingSprite;
  3906. this.Graphics = renderingEngine.Graphics;
  3907. this.Text = renderingEngine.Text;
  3908. //An array to store all the shaking sprites
  3909. this.shakingSprites = [];
  3910. }
  3911. }
  3912. _createClass(SpriteUtilities, [{
  3913. key: "update",
  3914. value: function update() {
  3915. if (this.shakingSprites.length > 0) {
  3916. for (var i = this.shakingSprites.length - 1; i >= 0; i--) {
  3917. var shakingSprite = this.shakingSprites[i];
  3918. if (shakingSprite.updateShake) shakingSprite.updateShake();
  3919. }
  3920. }
  3921. }
  3922. }, {
  3923. key: "sprite",
  3924. value: function sprite(source) {
  3925. var x = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1];
  3926. var y = arguments.length <= 2 || arguments[2] === undefined ? 0 : arguments[2];
  3927. var tiling = arguments.length <= 3 || arguments[3] === undefined ? false : arguments[3];
  3928. var width = arguments[4];
  3929. var height = arguments[5];
  3930. var o = undefined,
  3931. texture = undefined;
  3932. //Create a sprite if the `source` is a string
  3933. if (typeof source === "string") {
  3934. //Access the texture in the cache if it's there
  3935. if (this.TextureCache[source]) {
  3936. texture = this.TextureCache[source];
  3937. }
  3938. //If it's not is the cache, load it from the source file
  3939. else {
  3940. texture = this.Texture.fromImage(source);
  3941. }
  3942. //If the texture was created, make the o
  3943. if (texture) {
  3944. //If `tiling` is `false`, make a regular `Sprite`
  3945. if (!tiling) {
  3946. o = new this.Sprite(texture);
  3947. }
  3948. //If `tiling` is `true` make a `TilingSprite`
  3949. else {
  3950. o = new this.TilingSprite(texture, width, height);
  3951. }
  3952. }
  3953. //But if the source still can't be found, alert the user
  3954. else {
  3955. throw new Error(source + " cannot be found");
  3956. }
  3957. }
  3958. //Create a o if the `source` is a texture
  3959. else if (source instanceof this.Texture) {
  3960. if (!tiling) {
  3961. o = new this.Sprite(source);
  3962. } else {
  3963. o = new this.TilingSprite(source, width, height);
  3964. }
  3965. }
  3966. //Create a `MovieClip` o if the `source` is an array
  3967. else if (source instanceof Array) {
  3968. //Is it an array of frame ids or textures?
  3969. if (typeof source[0] === "string") {
  3970. //They're strings, but are they pre-existing texture or
  3971. //paths to image files?
  3972. //Check to see if the first element matches a texture in the
  3973. //cache
  3974. if (this.TextureCache[source[0]]) {
  3975. //It does, so it's an array of frame ids
  3976. o = this.MovieClip.fromFrames(source);
  3977. } else {
  3978. //It's not already in the cache, so let's load it
  3979. o = this.MovieClip.fromImages(source);
  3980. }
  3981. }
  3982. //If the `source` isn't an array of strings, check whether
  3983. //it's an array of textures
  3984. else if (source[0] instanceof this.Texture) {
  3985. //Yes, it's an array of textures.
  3986. //Use them to make a MovieClip o
  3987. o = new this.MovieClip(source);
  3988. }
  3989. }
  3990. //If the sprite was successfully created, intialize it
  3991. if (o) {
  3992. //Position the sprite
  3993. o.x = x;
  3994. o.y = y;
  3995. //Set optional width and height
  3996. if (width) o.width = width;
  3997. if (height) o.height = height;
  3998. //If the sprite is a MovieClip, add a state player so that
  3999. //it's easier to control
  4000. if (o instanceof this.MovieClip) this.addStatePlayer(o);
  4001. //Assign the sprite
  4002. return o;
  4003. }
  4004. }
  4005. }, {
  4006. key: "addStatePlayer",
  4007. value: function addStatePlayer(sprite) {
  4008. var frameCounter = 0,
  4009. numberOfFrames = 0,
  4010. startFrame = 0,
  4011. endFrame = 0,
  4012. timerInterval = undefined;
  4013. //The `show` function (to display static states)
  4014. function show(frameNumber) {
  4015. //Reset any possible previous animations
  4016. reset();
  4017. //Find the new state on the sprite
  4018. sprite.gotoAndStop(frameNumber);
  4019. }
  4020. //The `stop` function stops the animation at the current frame
  4021. function stopAnimation() {
  4022. reset();
  4023. sprite.gotoAndStop(sprite.currentFrame);
  4024. }
  4025. //The `playSequence` function, to play a sequence of frames
  4026. function playAnimation(sequenceArray) {
  4027. //Reset any possible previous animations
  4028. reset();
  4029. //Figure out how many frames there are in the range
  4030. if (!sequenceArray) {
  4031. startFrame = 0;
  4032. endFrame = sprite.totalFrames - 1;
  4033. } else {
  4034. startFrame = sequenceArray[0];
  4035. endFrame = sequenceArray[1];
  4036. }
  4037. //Calculate the number of frames
  4038. numberOfFrames = endFrame - startFrame;
  4039. //Compensate for two edge cases:
  4040. //1. If the `startFrame` happens to be `0`
  4041. /*
  4042. if (startFrame === 0) {
  4043. numberOfFrames += 1;
  4044. frameCounter += 1;
  4045. }
  4046. */
  4047. //2. If only a two-frame sequence was provided
  4048. /*
  4049. if(numberOfFrames === 1) {
  4050. numberOfFrames = 2;
  4051. frameCounter += 1;
  4052. }
  4053. */
  4054. //Calculate the frame rate. Set the default fps to 12
  4055. if (!sprite.fps) sprite.fps = 12;
  4056. var frameRate = 1000 / sprite.fps;
  4057. //Set the sprite to the starting frame
  4058. sprite.gotoAndStop(startFrame);
  4059. //Set the `frameCounter` to the first frame
  4060. frameCounter = 1;
  4061. //If the state isn't already `playing`, start it
  4062. if (!sprite.animating) {
  4063. timerInterval = setInterval(advanceFrame.bind(this), frameRate);
  4064. sprite.animating = true;
  4065. }
  4066. }
  4067. //`advanceFrame` is called by `setInterval` to display the next frame
  4068. //in the sequence based on the `frameRate`. When the frame sequence
  4069. //reaches the end, it will either stop or loop
  4070. function advanceFrame() {
  4071. //Advance the frame if `frameCounter` is less than
  4072. //the state's total frames
  4073. if (frameCounter < numberOfFrames + 1) {
  4074. //Advance the frame
  4075. sprite.gotoAndStop(sprite.currentFrame + 1);
  4076. //Update the frame counter
  4077. frameCounter += 1;
  4078. //If we've reached the last frame and `loop`
  4079. //is `true`, then start from the first frame again
  4080. } else {
  4081. if (sprite.loop) {
  4082. sprite.gotoAndStop(startFrame);
  4083. frameCounter = 1;
  4084. }
  4085. }
  4086. }
  4087. function reset() {
  4088. //Reset `sprite.playing` to `false`, set the `frameCounter` to 0, //and clear the `timerInterval`
  4089. if (timerInterval !== undefined && sprite.animating === true) {
  4090. sprite.animating = false;
  4091. frameCounter = 0;
  4092. startFrame = 0;
  4093. endFrame = 0;
  4094. numberOfFrames = 0;
  4095. clearInterval(timerInterval);
  4096. }
  4097. }
  4098. //Add the `show`, `play`, `stop`, and `playSequence` methods to the sprite
  4099. sprite.show = show;
  4100. sprite.stopAnimation = stopAnimation;
  4101. sprite.playAnimation = playAnimation;
  4102. }
  4103. //`tilingSpirte` lets you quickly create Pixi tiling sprites
  4104. }, {
  4105. key: "tilingSprite",
  4106. value: function tilingSprite(source, width, height, x, y) {
  4107. if (width === undefined) {
  4108. throw new Error("Please define a width as your second argument for the tiling sprite");
  4109. }
  4110. if (height === undefined) {
  4111. throw new Error("Please define a height as your third argument for the tiling sprite");
  4112. }
  4113. var o = this.sprite(source, x, y, true, width, height);
  4114. //Add `tileX`, `tileY`, `tileScaleX` and `tileScaleY` properties
  4115. Object.defineProperties(o, {
  4116. "tileX": {
  4117. get: function get() {
  4118. return o.tilePosition.x;
  4119. },
  4120. set: function set(value) {
  4121. o.tilePosition.x = value;
  4122. },
  4123. enumerable: true, configurable: true
  4124. },
  4125. "tileY": {
  4126. get: function get() {
  4127. return o.tilePosition.y;
  4128. },
  4129. set: function set(value) {
  4130. o.tilePosition.y = value;
  4131. },
  4132. enumerable: true, configurable: true
  4133. },
  4134. "tileScaleX": {
  4135. get: function get() {
  4136. return o.tileScale.x;
  4137. },
  4138. set: function set(value) {
  4139. o.tileScale.x = value;
  4140. },
  4141. enumerable: true, configurable: true
  4142. },
  4143. "tileScaleY": {
  4144. get: function get() {
  4145. return o.tileScale.y;
  4146. },
  4147. set: function set(value) {
  4148. o.tileScale.y = value;
  4149. },
  4150. enumerable: true, configurable: true
  4151. }
  4152. });
  4153. return o;
  4154. }
  4155. }, {
  4156. key: "filmstrip",
  4157. value: function filmstrip(texture, frameWidth, frameHeight) {
  4158. var spacing = arguments.length <= 3 || arguments[3] === undefined ? 0 : arguments[3];
  4159. //An array to store the x/y positions of the frames
  4160. var positions = [];
  4161. //Find the width and height of the texture
  4162. var textureWidth = this.TextureCache[texture].width,
  4163. textureHeight = this.TextureCache[texture].height;
  4164. //Find out how many columns and rows there are
  4165. var columns = textureWidth / frameWidth,
  4166. rows = textureHeight / frameHeight;
  4167. //Find the total number of frames
  4168. var numberOfFrames = columns * rows;
  4169. for (var i = 0; i < numberOfFrames; i++) {
  4170. //Find the correct row and column for each frame
  4171. //and figure out its x and y position
  4172. var x = i % columns * frameWidth,
  4173. y = Math.floor(i / columns) * frameHeight;
  4174. //Compensate for any optional spacing (padding) around the tiles if
  4175. //there is any. This bit of code accumlates the spacing offsets from the
  4176. //left side of the tileset and adds them to the current tile's position
  4177. if (spacing > 0) {
  4178. x += spacing + spacing * i % columns;
  4179. y += spacing + spacing * Math.floor(i / columns);
  4180. }
  4181. //Add the x and y value of each frame to the `positions` array
  4182. positions.push([x, y]);
  4183. }
  4184. //Return the frames
  4185. return this.frames(texture, positions, frameWidth, frameHeight);
  4186. }
  4187. //Make a texture from a frame in another texture or image
  4188. }, {
  4189. key: "frame",
  4190. value: function frame(source, x, y, width, height) {
  4191. var texture = undefined,
  4192. imageFrame = undefined;
  4193. //If the source is a string, it's either a texture in the
  4194. //cache or an image file
  4195. if (typeof source === "string") {
  4196. if (this.TextureCache[source]) {
  4197. texture = new this.Texture(this.TextureCache[source]);
  4198. }
  4199. }
  4200. //If the `source` is a texture, use it
  4201. else if (source instanceof this.Texture) {
  4202. texture = new this.Texture(source);
  4203. }
  4204. if (!texture) {
  4205. throw new Error("Please load the " + source + " texture into the cache.");
  4206. } else {
  4207. //Make a rectangle the size of the sub-image
  4208. imageFrame = new this.Rectangle(x, y, width, height);
  4209. texture.frame = imageFrame;
  4210. return texture;
  4211. }
  4212. }
  4213. //Make an array of textures from a 2D array of frame x and y coordinates in
  4214. //texture
  4215. }, {
  4216. key: "frames",
  4217. value: function frames(source, coordinates, frameWidth, frameHeight) {
  4218. var _this = this;
  4219. var baseTexture = undefined,
  4220. textures = undefined;
  4221. //If the source is a string, it's either a texture in the
  4222. //cache or an image file
  4223. if (typeof source === "string") {
  4224. if (this.TextureCache[source]) {
  4225. baseTexture = new this.Texture(this.TextureCache[source]);
  4226. }
  4227. }
  4228. //If the `source` is a texture, use it
  4229. else if (source instanceof this.Texture) {
  4230. baseTexture = new this.Texture(source);
  4231. }
  4232. if (!baseTexture) {
  4233. throw new Error("Please load the " + source + " texture into the cache.");
  4234. } else {
  4235. var _textures = coordinates.map(function (position) {
  4236. var x = position[0],
  4237. y = position[1];
  4238. var imageFrame = new _this.Rectangle(x, y, frameWidth, frameHeight);
  4239. var frameTexture = new _this.Texture(baseTexture);
  4240. frameTexture.frame = imageFrame;
  4241. return frameTexture;
  4242. });
  4243. return _textures;
  4244. }
  4245. }
  4246. }, {
  4247. key: "frameSeries",
  4248. value: function frameSeries() {
  4249. var startNumber = arguments.length <= 0 || arguments[0] === undefined ? 0 : arguments[0];
  4250. var endNumber = arguments.length <= 1 || arguments[1] === undefined ? 1 : arguments[1];
  4251. var baseName = arguments.length <= 2 || arguments[2] === undefined ? "" : arguments[2];
  4252. var extension = arguments.length <= 3 || arguments[3] === undefined ? "" : arguments[3];
  4253. //Create an array to store the frame names
  4254. var frames = [];
  4255. for (var i = startNumber; i < endNumber + 1; i++) {
  4256. var frame = this.TextureCache["" + (baseName + i + extension)];
  4257. frames.push(frame);
  4258. }
  4259. return frames;
  4260. }
  4261. /* Text creation */
  4262. //The`text` method is a quick way to create a Pixi Text sprite
  4263. }, {
  4264. key: "text",
  4265. value: function text() {
  4266. var content = arguments.length <= 0 || arguments[0] === undefined ? "message" : arguments[0];
  4267. var font = arguments.length <= 1 || arguments[1] === undefined ? "16px sans" : arguments[1];
  4268. var fillStyle = arguments.length <= 2 || arguments[2] === undefined ? "red" : arguments[2];
  4269. var x = arguments.length <= 3 || arguments[3] === undefined ? 0 : arguments[3];
  4270. var y = arguments.length <= 4 || arguments[4] === undefined ? 0 : arguments[4];
  4271. //Create a Pixi Sprite object
  4272. var message = new this.Text(content, { font: font, fill: fillStyle });
  4273. message.x = x;
  4274. message.y = y;
  4275. //Add a `_text` property with a getter/setter
  4276. message._content = content;
  4277. Object.defineProperty(message, "content", {
  4278. get: function get() {
  4279. return this._content;
  4280. },
  4281. set: function set(value) {
  4282. this._content = value;
  4283. this.text = value;
  4284. },
  4285. enumerable: true, configurable: true
  4286. });
  4287. //Return the text object
  4288. return message;
  4289. }
  4290. //The`bitmapText` method lets you create bitmap text
  4291. }, {
  4292. key: "bitmapText",
  4293. value: function bitmapText() {
  4294. var content = arguments.length <= 0 || arguments[0] === undefined ? "message" : arguments[0];
  4295. var font = arguments[1];
  4296. var align = arguments[2];
  4297. var tint = arguments[3];
  4298. var x = arguments.length <= 4 || arguments[4] === undefined ? 0 : arguments[4];
  4299. var y = arguments.length <= 5 || arguments[5] === undefined ? 0 : arguments[5];
  4300. //Create a Pixi Sprite object
  4301. var message = new this.BitmapText(content, { font: font, align: align, tint: tint });
  4302. message.x = x;
  4303. message.y = y;
  4304. //Add a `_text` property with a getter/setter
  4305. message._content = content;
  4306. Object.defineProperty(message, "content", {
  4307. get: function get() {
  4308. return this._content;
  4309. },
  4310. set: function set(value) {
  4311. this._content = value;
  4312. this.text = value;
  4313. },
  4314. enumerable: true, configurable: true
  4315. });
  4316. //Return the text object
  4317. return message;
  4318. }
  4319. /* Shapes and lines */
  4320. //Rectangle
  4321. }, {
  4322. key: "rectangle",
  4323. value: function rectangle() {
  4324. var width = arguments.length <= 0 || arguments[0] === undefined ? 32 : arguments[0];
  4325. var height = arguments.length <= 1 || arguments[1] === undefined ? 32 : arguments[1];
  4326. var fillStyle = arguments.length <= 2 || arguments[2] === undefined ? 0xFF3300 : arguments[2];
  4327. var strokeStyle = arguments.length <= 3 || arguments[3] === undefined ? 0x0033CC : arguments[3];
  4328. var lineWidth = arguments.length <= 4 || arguments[4] === undefined ? 0 : arguments[4];
  4329. var x = arguments.length <= 5 || arguments[5] === undefined ? 0 : arguments[5];
  4330. var y = arguments.length <= 6 || arguments[6] === undefined ? 0 : arguments[6];
  4331. var o = new this.Graphics();
  4332. o._sprite = undefined;
  4333. o._width = width;
  4334. o._height = height;
  4335. o._fillStyle = this.color(fillStyle);
  4336. o._strokeStyle = this.color(strokeStyle);
  4337. o._lineWidth = lineWidth;
  4338. //Draw the rectangle
  4339. var draw = function draw(width, height, fillStyle, strokeStyle, lineWidth) {
  4340. o.clear();
  4341. o.beginFill(fillStyle);
  4342. if (lineWidth > 0) {
  4343. o.lineStyle(lineWidth, strokeStyle, 1);
  4344. }
  4345. o.drawRect(0, 0, width, height);
  4346. o.endFill();
  4347. };
  4348. //Draw the line and capture the sprite that the `draw` function
  4349. //returns
  4350. draw(o._width, o._height, o._fillStyle, o._strokeStyle, o._lineWidth);
  4351. //Generate a texture from the rectangle
  4352. var texture = o.generateTexture();
  4353. //Use the texture to create a sprite
  4354. var sprite = new this.Sprite(texture);
  4355. //Position the sprite
  4356. sprite.x = x;
  4357. sprite.y = y;
  4358. //Add getters and setters to the sprite
  4359. var self = this;
  4360. Object.defineProperties(sprite, {
  4361. "fillStyle": {
  4362. get: function get() {
  4363. return o._fillStyle;
  4364. },
  4365. set: function set(value) {
  4366. o._fillStyle = self.color(value);
  4367. //Draw the new rectangle
  4368. draw(o._width, o._height, o._fillStyle, o._strokeStyle, o._lineWidth, o._x, o._y);
  4369. //Generate a new texture and set it as the sprite's texture
  4370. var texture = o.generateTexture();
  4371. o._sprite.texture = texture;
  4372. },
  4373. enumerable: true, configurable: true
  4374. },
  4375. "strokeStyle": {
  4376. get: function get() {
  4377. return o._strokeStyle;
  4378. },
  4379. set: function set(value) {
  4380. o._strokeStyle = self.color(value);
  4381. //Draw the new rectangle
  4382. draw(o._width, o._height, o._fillStyle, o._strokeStyle, o._lineWidth, o._x, o._y);
  4383. //Generate a new texture and set it as the sprite's texture
  4384. var texture = o.generateTexture();
  4385. o._sprite.texture = texture;
  4386. },
  4387. enumerable: true, configurable: true
  4388. },
  4389. "lineWidth": {
  4390. get: function get() {
  4391. return o._lineWidth;
  4392. },
  4393. set: function set(value) {
  4394. o._lineWidth = value;
  4395. //Draw the new rectangle
  4396. draw(o._width, o._height, o._fillStyle, o._strokeStyle, o._lineWidth, o._x, o._y);
  4397. //Generate a new texture and set it as the sprite's texture
  4398. var texture = o.generateTexture();
  4399. o._sprite.texture = texture;
  4400. },
  4401. enumerable: true, configurable: true
  4402. }
  4403. });
  4404. //Get a local reference to the sprite so that we can
  4405. //change the rectangle properties later using the getters/setters
  4406. o._sprite = sprite;
  4407. //Return the sprite
  4408. return sprite;
  4409. }
  4410. //Circle
  4411. }, {
  4412. key: "circle",
  4413. value: function circle() {
  4414. var diameter = arguments.length <= 0 || arguments[0] === undefined ? 32 : arguments[0];
  4415. var fillStyle = arguments.length <= 1 || arguments[1] === undefined ? 0xFF3300 : arguments[1];
  4416. var strokeStyle = arguments.length <= 2 || arguments[2] === undefined ? 0x0033CC : arguments[2];
  4417. var lineWidth = arguments.length <= 3 || arguments[3] === undefined ? 0 : arguments[3];
  4418. var x = arguments.length <= 4 || arguments[4] === undefined ? 0 : arguments[4];
  4419. var y = arguments.length <= 5 || arguments[5] === undefined ? 0 : arguments[5];
  4420. var o = new this.Graphics();
  4421. o._diameter = diameter;
  4422. o._fillStyle = this.color(fillStyle);
  4423. o._strokeStyle = this.color(strokeStyle);
  4424. o._lineWidth = lineWidth;
  4425. //Draw the circle
  4426. var draw = function draw(diameter, fillStyle, strokeStyle, lineWidth) {
  4427. o.clear();
  4428. o.beginFill(fillStyle);
  4429. if (lineWidth > 0) {
  4430. o.lineStyle(lineWidth, strokeStyle, 1);
  4431. }
  4432. o.drawCircle(0, 0, diameter / 2);
  4433. o.endFill();
  4434. };
  4435. //Draw the cirlce
  4436. draw(o._diameter, o._fillStyle, o._strokeStyle, o._lineWidth);
  4437. //Generate a texture from the rectangle
  4438. var texture = o.generateTexture();
  4439. //Use the texture to create a sprite
  4440. var sprite = new this.Sprite(texture);
  4441. //Position the sprite
  4442. sprite.x = x;
  4443. sprite.y = y;
  4444. //Add getters and setters to the sprite
  4445. var self = this;
  4446. Object.defineProperties(sprite, {
  4447. "fillStyle": {
  4448. get: function get() {
  4449. return o._fillStyle;
  4450. },
  4451. set: function set(value) {
  4452. o._fillStyle = self.color(value);
  4453. //Draw the cirlce
  4454. draw(o._diameter, o._fillStyle, o._strokeStyle, o._lineWidth);
  4455. //Generate a new texture and set it as the sprite's texture
  4456. var texture = o.generateTexture();
  4457. o._sprite.texture = texture;
  4458. },
  4459. enumerable: true, configurable: true
  4460. },
  4461. "strokeStyle": {
  4462. get: function get() {
  4463. return o._strokeStyle;
  4464. },
  4465. set: function set(value) {
  4466. o._strokeStyle = self.color(value);
  4467. //Draw the cirlce
  4468. draw(o._diameter, o._fillStyle, o._strokeStyle, o._lineWidth);
  4469. //Generate a new texture and set it as the sprite's texture
  4470. var texture = o.generateTexture();
  4471. o._sprite.texture = texture;
  4472. },
  4473. enumerable: true, configurable: true
  4474. },
  4475. "diameter": {
  4476. get: function get() {
  4477. return o._diameter;
  4478. },
  4479. set: function set(value) {
  4480. o._lineWidth = 10;
  4481. //Draw the cirlce
  4482. draw(o._diameter, o._fillStyle, o._strokeStyle, o._lineWidth);
  4483. //Generate a new texture and set it as the sprite's texture
  4484. var texture = o.generateTexture();
  4485. o._sprite.texture = texture;
  4486. },
  4487. enumerable: true, configurable: true
  4488. },
  4489. "radius": {
  4490. get: function get() {
  4491. return o._diameter / 2;
  4492. },
  4493. set: function set(value) {
  4494. //Draw the cirlce
  4495. draw(value * 2, o._fillStyle, o._strokeStyle, o._lineWidth);
  4496. //Generate a new texture and set it as the sprite's texture
  4497. var texture = o.generateTexture();
  4498. o._sprite.texture = texture;
  4499. },
  4500. enumerable: true, configurable: true
  4501. }
  4502. });
  4503. //Get a local reference to the sprite so that we can
  4504. //change the circle properties later using the getters/setters
  4505. o._sprite = sprite;
  4506. //Return the sprite
  4507. return sprite;
  4508. }
  4509. //Line
  4510. }, {
  4511. key: "line",
  4512. value: function line() {
  4513. var strokeStyle = arguments.length <= 0 || arguments[0] === undefined ? 0x000000 : arguments[0];
  4514. var lineWidth = arguments.length <= 1 || arguments[1] === undefined ? 1 : arguments[1];
  4515. var ax = arguments.length <= 2 || arguments[2] === undefined ? 0 : arguments[2];
  4516. var ay = arguments.length <= 3 || arguments[3] === undefined ? 0 : arguments[3];
  4517. var bx = arguments.length <= 4 || arguments[4] === undefined ? 32 : arguments[4];
  4518. var by = arguments.length <= 5 || arguments[5] === undefined ? 32 : arguments[5];
  4519. //Create the line object
  4520. var o = new this.Graphics();
  4521. //Private properties
  4522. o._strokeStyle = this.color(strokeStyle);
  4523. o._width = lineWidth;
  4524. o._ax = ax;
  4525. o._ay = ay;
  4526. o._bx = bx;
  4527. o._by = by;
  4528. //A helper function that draws the line
  4529. var draw = function draw(strokeStyle, lineWidth, ax, ay, bx, by) {
  4530. o.clear();
  4531. o.lineStyle(lineWidth, strokeStyle, 1);
  4532. o.moveTo(ax, ay);
  4533. o.lineTo(bx, by);
  4534. };
  4535. //Draw the line
  4536. draw(o._strokeStyle, o._width, o._ax, o._ay, o._bx, o._by);
  4537. //Define getters and setters that redefine the line's start and
  4538. //end points and re-draws it if they change
  4539. var self = this;
  4540. Object.defineProperties(o, {
  4541. "ax": {
  4542. get: function get() {
  4543. return o._ax;
  4544. },
  4545. set: function set(value) {
  4546. o._ax = value;
  4547. draw(o._strokeStyle, o._width, o._ax, o._ay, o._bx, o._by);
  4548. },
  4549. enumerable: true, configurable: true
  4550. },
  4551. "ay": {
  4552. get: function get() {
  4553. return o._ay;
  4554. },
  4555. set: function set(value) {
  4556. o._ay = value;
  4557. draw(o._strokeStyle, o._width, o._ax, o._ay, o._bx, o._by);
  4558. },
  4559. enumerable: true, configurable: true
  4560. },
  4561. "bx": {
  4562. get: function get() {
  4563. return o._bx;
  4564. },
  4565. set: function set(value) {
  4566. o._bx = value;
  4567. draw(o._strokeStyle, o._width, o._ax, o._ay, o._bx, o._by);
  4568. },
  4569. enumerable: true, configurable: true
  4570. },
  4571. "by": {
  4572. get: function get() {
  4573. return o._by;
  4574. },
  4575. set: function set(value) {
  4576. o._by = value;
  4577. draw(o._strokeStyle, o._width, o._ax, o._ay, o._bx, o._by);
  4578. },
  4579. enumerable: true, configurable: true
  4580. },
  4581. "strokeStyle": {
  4582. get: function get() {
  4583. return o._strokeStyle;
  4584. },
  4585. set: function set(value) {
  4586. o._strokeStyle = self.color(value);
  4587. //Draw the line
  4588. draw(o._strokeStyle, o._width, o._ax, o._ay, o._bx, o._by);
  4589. },
  4590. enumerable: true, configurable: true
  4591. },
  4592. "width": {
  4593. get: function get() {
  4594. return o._width;
  4595. },
  4596. set: function set(value) {
  4597. o._width = value;
  4598. //Draw the line
  4599. draw(o._strokeStyle, o._width, o._ax, o._ay, o._bx, o._by);
  4600. },
  4601. enumerable: true, configurable: true
  4602. }
  4603. });
  4604. //Return the line
  4605. return o;
  4606. }
  4607. /* Compound sprites */
  4608. //Use `grid` to create a grid of sprites
  4609. }, {
  4610. key: "grid",
  4611. value: function grid() {
  4612. var columns = arguments.length <= 0 || arguments[0] === undefined ? 0 : arguments[0];
  4613. var rows = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1];
  4614. var cellWidth = arguments.length <= 2 || arguments[2] === undefined ? 32 : arguments[2];
  4615. var cellHeight = arguments.length <= 3 || arguments[3] === undefined ? 32 : arguments[3];
  4616. var centerCell = arguments.length <= 4 || arguments[4] === undefined ? false : arguments[4];
  4617. var xOffset = arguments.length <= 5 || arguments[5] === undefined ? 0 : arguments[5];
  4618. var yOffset = arguments.length <= 6 || arguments[6] === undefined ? 0 : arguments[6];
  4619. var makeSprite = arguments.length <= 7 || arguments[7] === undefined ? undefined : arguments[7];
  4620. var extra = arguments.length <= 8 || arguments[8] === undefined ? undefined : arguments[8];
  4621. //Create an empty group called `container`. This `container`
  4622. //group is what the function returns back to the main program.
  4623. //All the sprites in the grid cells will be added
  4624. //as children to this container
  4625. var container = new this.Container();
  4626. //The `create` method plots the grid
  4627. var createGrid = function createGrid() {
  4628. //Figure out the number of cells in the grid
  4629. var length = columns * rows;
  4630. //Create a sprite for each cell
  4631. for (var i = 0; i < length; i++) {
  4632. //Figure out the sprite's x/y placement in the grid
  4633. var x = i % columns * cellWidth,
  4634. y = Math.floor(i / columns) * cellHeight;
  4635. //Use the `makeSprite` function supplied in the constructor
  4636. //to make a sprite for the grid cell
  4637. var sprite = makeSprite();
  4638. //Add the sprite to the `container`
  4639. container.addChild(sprite);
  4640. //Should the sprite be centered in the cell?
  4641. //No, it shouldn't be centered
  4642. if (!centerCell) {
  4643. sprite.x = x + xOffset;
  4644. sprite.y = y + yOffset;
  4645. }
  4646. //Yes, it should be centered
  4647. else {
  4648. sprite.x = x + cellWidth / 2 - sprite.width / 2 + xOffset;
  4649. sprite.y = y + cellHeight / 2 - sprite.width / 2 + yOffset;
  4650. }
  4651. //Run any optional extra code. This calls the
  4652. //`extra` function supplied by the constructor
  4653. if (extra) extra(sprite);
  4654. }
  4655. };
  4656. //Run the `createGrid` method
  4657. createGrid();
  4658. //Return the `container` group back to the main program
  4659. return container;
  4660. }
  4661. //Use `shoot` to create bullet sprites
  4662. }, {
  4663. key: "shoot",
  4664. value: function shoot(shooter, angle, x, y, container, bulletSpeed, bulletArray, bulletSprite) {
  4665. //Make a new sprite using the user-supplied `bulletSprite` function
  4666. var bullet = bulletSprite();
  4667. //Set the bullet's anchor point to its center
  4668. bullet.anchor.set(0.5, 0.5);
  4669. //Temporarily add the bullet to the shooter
  4670. //so that we can position it relative to the
  4671. //shooter's position
  4672. shooter.addChild(bullet);
  4673. bullet.x = x;
  4674. bullet.y = y;
  4675. //Find the bullet's global coordinates so that we can use
  4676. //them to position the bullet on the new parent container
  4677. var tempGx = bullet.getGlobalPosition().x,
  4678. tempGy = bullet.getGlobalPosition().y;
  4679. //Add the bullet to the new parent container using
  4680. //the new global coordinates
  4681. container.addChild(bullet);
  4682. bullet.x = tempGx;
  4683. bullet.y = tempGy;
  4684. //Set the bullet's velocity
  4685. bullet.vx = Math.cos(angle) * bulletSpeed;
  4686. bullet.vy = Math.sin(angle) * bulletSpeed;
  4687. //Push the bullet into the `bulletArray`
  4688. bulletArray.push(bullet);
  4689. }
  4690. /*
  4691. grid
  4692. ----
  4693. Helps you to automatically create a grid of sprites. `grid` returns a
  4694. `group` sprite object that contains a sprite for every cell in the
  4695. grid. You can define the rows and columns in the grid, whether or
  4696. not the sprites should be centered inside each cell, or what their offset from the
  4697. top left corner of each cell should be. Supply a function that
  4698. returns the sprite that you want to make for each cell. You can
  4699. supply an optional final function that runs any extra code after
  4700. each sprite has been created. Here's the format for creating a grid:
  4701. gridGroup = grid(
  4702. //Set the grid's properties
  4703. columns, rows, cellWidth, cellHeight,
  4704. areSpirtesCentered?, xOffset, yOffset,
  4705. //A function that returns a sprite
  4706. () => g.circle(16, "blue"),
  4707. //An optional final function that runs some extra code
  4708. () => console.log("extra!")
  4709. );
  4710. */
  4711. }, {
  4712. key: "grid",
  4713. value: function grid() {
  4714. var columns = arguments.length <= 0 || arguments[0] === undefined ? 0 : arguments[0];
  4715. var rows = arguments.length <= 1 || arguments[1] === undefined ? 0 : arguments[1];
  4716. var cellWidth = arguments.length <= 2 || arguments[2] === undefined ? 32 : arguments[2];
  4717. var cellHeight = arguments.length <= 3 || arguments[3] === undefined ? 32 : arguments[3];
  4718. var centerCell = arguments.length <= 4 || arguments[4] === undefined ? false : arguments[4];
  4719. var xOffset = arguments.length <= 5 || arguments[5] === undefined ? 0 : arguments[5];
  4720. var yOffset = arguments.length <= 6 || arguments[6] === undefined ? 0 : arguments[6];
  4721. var makeSprite = arguments.length <= 7 || arguments[7] === undefined ? undefined : arguments[7];
  4722. var extra = arguments.length <= 8 || arguments[8] === undefined ? undefined : arguments[8];
  4723. //Create an empty group called `container`. This `container`
  4724. //group is what the function returns back to the main program.
  4725. //All the sprites in the grid cells will be added
  4726. //as children to this container
  4727. var container = this.group();
  4728. //The `create` method plots the grid
  4729. var createGrid = function createGrid() {
  4730. //Figure out the number of cells in the grid
  4731. var length = columns * rows;
  4732. //Create a sprite for each cell
  4733. for (var i = 0; i < length; i++) {
  4734. //Figure out the sprite's x/y placement in the grid
  4735. var x = i % columns * cellWidth,
  4736. y = Math.floor(i / columns) * cellHeight;
  4737. //Use the `makeSprite` function supplied in the constructor
  4738. //to make a sprite for the grid cell
  4739. var sprite = makeSprite();
  4740. //Add the sprite to the `container`
  4741. container.addChild(sprite);
  4742. //Should the sprite be centered in the cell?
  4743. //No, it shouldn't be centered
  4744. if (!centerCell) {
  4745. sprite.x = x + xOffset;
  4746. sprite.y = y + yOffset;
  4747. }
  4748. //Yes, it should be centered
  4749. else {
  4750. sprite.x = x + cellWidth / 2 - sprite.halfWidth + xOffset;
  4751. sprite.y = y + cellHeight / 2 - sprite.halfHeight + yOffset;
  4752. }
  4753. //Run any optional extra code. This calls the
  4754. //`extra` function supplied by the constructor
  4755. if (extra) extra(sprite);
  4756. }
  4757. };
  4758. //Run the `createGrid` method
  4759. createGrid();
  4760. //Return the `container` group back to the main program
  4761. return container;
  4762. }
  4763. /*
  4764. shake
  4765. -----
  4766. Used to create a shaking effect, like a screen shake
  4767. */
  4768. }, {
  4769. key: "shake",
  4770. value: function shake(sprite) {
  4771. var magnitude = arguments.length <= 1 || arguments[1] === undefined ? 16 : arguments[1];
  4772. var angular = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2];
  4773. //Get a reference to this current object so that
  4774. //it's easy to maintain scope in the nested sub-functions
  4775. var self = this;
  4776. //A counter to count the number of shakes
  4777. var counter = 1;
  4778. //The total number of shakes (there will be 1 shake per frame)
  4779. var numberOfShakes = 10;
  4780. //Capture the sprite's position and angle so you can
  4781. //restore them after the shaking has finished
  4782. var startX = sprite.x,
  4783. startY = sprite.y,
  4784. startAngle = sprite.rotation;
  4785. //Divide the magnitude into 10 units so that you can
  4786. //reduce the amount of shake by 10 percent each frame
  4787. var magnitudeUnit = magnitude / numberOfShakes;
  4788. //The `randomInt` helper function
  4789. var randomInt = function randomInt(min, max) {
  4790. return Math.floor(Math.random() * (max - min + 1)) + min;
  4791. };
  4792. //Add the sprite to the `shakingSprites` array if it
  4793. //isn't already there
  4794. if (self.shakingSprites.indexOf(sprite) === -1) {
  4795. self.shakingSprites.push(sprite);
  4796. //Add an `updateShake` method to the sprite.
  4797. //The `updateShake` method will be called each frame
  4798. //in the game loop. The shake effect type can be either
  4799. //up and down (x/y shaking) or angular (rotational shaking).
  4800. sprite.updateShake = function () {
  4801. if (angular) {
  4802. angularShake();
  4803. } else {
  4804. upAndDownShake();
  4805. }
  4806. };
  4807. }
  4808. //The `upAndDownShake` function
  4809. function upAndDownShake() {
  4810. //Shake the sprite while the `counter` is less than
  4811. //the `numberOfShakes`
  4812. if (counter < numberOfShakes) {
  4813. //Reset the sprite's position at the start of each shake
  4814. sprite.x = startX;
  4815. sprite.y = startY;
  4816. //Reduce the magnitude
  4817. magnitude -= magnitudeUnit;
  4818. //Randomly change the sprite's position
  4819. sprite.x += randomInt(-magnitude, magnitude);
  4820. sprite.y += randomInt(-magnitude, magnitude);
  4821. //Add 1 to the counter
  4822. counter += 1;
  4823. }
  4824. //When the shaking is finished, restore the sprite to its original
  4825. //position and remove it from the `shakingSprites` array
  4826. if (counter >= numberOfShakes) {
  4827. sprite.x = startX;
  4828. sprite.y = startY;
  4829. self.shakingSprites.splice(self.shakingSprites.indexOf(sprite), 1);
  4830. }
  4831. }
  4832. //The `angularShake` function
  4833. //First set the initial tilt angle to the right (+1)
  4834. var tiltAngle = 1;
  4835. function angularShake() {
  4836. if (counter < numberOfShakes) {
  4837. //Reset the sprite's rotation
  4838. sprite.rotation = startAngle;
  4839. //Reduce the magnitude
  4840. magnitude -= magnitudeUnit;
  4841. //Rotate the sprite left or right, depending on the direction,
  4842. //by an amount in radians that matches the magnitude
  4843. sprite.rotation = magnitude * tiltAngle;
  4844. counter += 1;
  4845. //Reverse the tilt angle so that the sprite is tilted
  4846. //in the opposite direction for the next shake
  4847. tiltAngle *= -1;
  4848. }
  4849. //When the shaking is finished, reset the sprite's angle and
  4850. //remove it from the `shakingSprites` array
  4851. if (counter >= numberOfShakes) {
  4852. sprite.rotation = startAngle;
  4853. self.shakingSprites.splice(self.shakingSprites.indexOf(sprite), 1);
  4854. }
  4855. }
  4856. }
  4857. /*
  4858. _getCenter
  4859. ----------
  4860. A utility that finds the center point of the sprite. If it's anchor point is the
  4861. sprite's top left corner, then the center is calculated from that point.
  4862. If the anchor point has been shifted, then the anchor x/y point is used as the sprite's center
  4863. */
  4864. }, {
  4865. key: "_getCenter",
  4866. value: function _getCenter(o, dimension, axis) {
  4867. if (o.anchor !== undefined) {
  4868. if (o.anchor[axis] !== 0) {
  4869. return 0;
  4870. } else {
  4871. return dimension / 2;
  4872. }
  4873. } else {
  4874. return dimension;
  4875. }
  4876. }
  4877. /* Groups */
  4878. //Group sprites into a container
  4879. }, {
  4880. key: "group",
  4881. value: function group() {
  4882. var container = new this.Container();
  4883. for (var _len = arguments.length, sprites = Array(_len), _key = 0; _key < _len; _key++) {
  4884. sprites[_key] = arguments[_key];
  4885. }
  4886. sprites.forEach(function (sprite) {
  4887. container.addChild(sprite);
  4888. });
  4889. return container;
  4890. }
  4891. //Use the `batch` method to create a ParticleContainer
  4892. }, {
  4893. key: "batch",
  4894. value: function batch() {
  4895. var size = arguments.length <= 0 || arguments[0] === undefined ? 15000 : arguments[0];
  4896. var options = arguments.length <= 1 || arguments[1] === undefined ? { rotation: true, alpha: true, scale: true, uvs: true } : arguments[1];
  4897. var o = new this.ParticleContainer(size, options);
  4898. return o;
  4899. }
  4900. //`remove` is a global convenience method that will
  4901. //remove any sprite, or an argument list of sprites, from its parent.
  4902. }, {
  4903. key: "remove",
  4904. value: function remove() {
  4905. for (var _len2 = arguments.length, sprites = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
  4906. sprites[_key2] = arguments[_key2];
  4907. }
  4908. //Remove sprites that's aren't in an array
  4909. if (!(sprites[0] instanceof Array)) {
  4910. if (sprites.length > 1) {
  4911. sprites.forEach(function (sprite) {
  4912. sprite.parent.removeChild(sprite);
  4913. });
  4914. } else {
  4915. sprites[0].parent.removeChild(sprites[0]);
  4916. }
  4917. }
  4918. //Remove sprites in an array of sprites
  4919. else {
  4920. var spritesArray = sprites[0];
  4921. if (spritesArray.length > 0) {
  4922. for (var i = spritesArray.length - 1; i >= 0; i--) {
  4923. var sprite = spritesArray[i];
  4924. sprite.parent.removeChild(sprite);
  4925. spritesArray.splice(spritesArray.indexOf(sprite), 1);
  4926. }
  4927. }
  4928. }
  4929. }
  4930. /* Color conversion */
  4931. //From: http://stackoverflow.com/questions/1573053/javascript-function-to-convert-color-names-to-hex-codes
  4932. //Utilities to convert HTML color string names to hexadecimal codes
  4933. }, {
  4934. key: "colorToRGBA",
  4935. value: function colorToRGBA(color) {
  4936. // Returns the color as an array of [r, g, b, a] -- all range from 0 - 255
  4937. // color must be a valid canvas fillStyle. This will cover most anything
  4938. // you'd want to use.
  4939. // Examples:
  4940. // colorToRGBA('red') # [255, 0, 0, 255]
  4941. // colorToRGBA('#f00') # [255, 0, 0, 255]
  4942. var cvs, ctx;
  4943. cvs = document.createElement('canvas');
  4944. cvs.height = 1;
  4945. cvs.width = 1;
  4946. ctx = cvs.getContext('2d');
  4947. ctx.fillStyle = color;
  4948. ctx.fillRect(0, 0, 1, 1);
  4949. var data = ctx.getImageData(0, 0, 1, 1).data;
  4950. return data;
  4951. }
  4952. }, {
  4953. key: "byteToHex",
  4954. value: function byteToHex(num) {
  4955. // Turns a number (0-255) into a 2-character hex number (00-ff)
  4956. return ('0' + num.toString(16)).slice(-2);
  4957. }
  4958. }, {
  4959. key: "colorToHex",
  4960. value: function colorToHex(color) {
  4961. var _this2 = this;
  4962. // Convert any CSS color to a hex representation
  4963. // Examples:
  4964. // colorToHex('red') # '#ff0000'
  4965. // colorToHex('rgb(255, 0, 0)') # '#ff0000'
  4966. var rgba, hex;
  4967. rgba = this.colorToRGBA(color);
  4968. hex = [0, 1, 2].map(function (idx) {
  4969. return _this2.byteToHex(rgba[idx]);
  4970. }).join('');
  4971. return "0x" + hex;
  4972. }
  4973. //A function to find out if the user entered a number (a hex color
  4974. //code) or a string (an HTML color string)
  4975. }, {
  4976. key: "color",
  4977. value: function color(value) {
  4978. //Check if it's a number
  4979. if (!isNaN(value)) {
  4980. //Yes, it is a number, so just return it
  4981. return value;
  4982. }
  4983. //No it's not a number, so it must be a string
  4984. else {
  4985. return parseInt(this.colorToHex(value));
  4986. /*
  4987. //Find out what kind of color string it is.
  4988. //Let's first grab the first character of the string
  4989. let firstCharacter = value.charAt(0);
  4990. //If the first character is a "#" or a number, then
  4991. //we know it must be a RGBA color
  4992. if (firstCharacter === "#") {
  4993. console.log("first character: " + value.charAt(0))
  4994. }
  4995. */
  4996. }
  4997. /*
  4998. //Find out if the first character in the string is a number
  4999. if (!isNaN(parseInt(string.charAt(0)))) {
  5000. //It's not, so convert it to a hex code
  5001. return colorToHex(string);
  5002. //The use input a number, so it must be a hex code. Just return it
  5003. } else {
  5004. return string;
  5005. }
  5006. */
  5007. }
  5008. }]);
  5009. return SpriteUtilities;
  5010. })();
  5011. //# sourceMappingURL=spriteUtilities.js.map"use strict";
  5012. var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
  5013. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  5014. var GameUtilities = (function () {
  5015. function GameUtilities() {
  5016. _classCallCheck(this, GameUtilities);
  5017. }
  5018. /*
  5019. distance
  5020. ----------------
  5021. Find the distance in pixels between two sprites.
  5022. Parameters:
  5023. a. A sprite object.
  5024. b. A sprite object.
  5025. The function returns the number of pixels distance between the sprites.
  5026. let distanceBetweenSprites = gu.distance(spriteA, spriteB);
  5027. */
  5028. _createClass(GameUtilities, [{
  5029. key: "distance",
  5030. value: function distance(s1, s2) {
  5031. var vx = s2.x + this._getCenter(s2, s2.width, "x") - (s1.x + this._getCenter(s1, s1.width, "x")),
  5032. vy = s2.y + this._getCenter(s2, s2.height, "y") - (s1.y + this._getCenter(s1, s1.height, "y"));
  5033. return Math.sqrt(vx * vx + vy * vy);
  5034. }
  5035. /*
  5036. followEase
  5037. ----------------
  5038. Make a sprite ease to the position of another sprite.
  5039. Parameters:
  5040. a. A sprite object. This is the `follower` sprite.
  5041. b. A sprite object. This is the `leader` sprite that the follower will chase.
  5042. c. The easing value, such as 0.3. A higher number makes the follower move faster.
  5043. gu.followEase(follower, leader, speed);
  5044. Use it inside a game loop.
  5045. */
  5046. }, {
  5047. key: "followEase",
  5048. value: function followEase(follower, leader, speed) {
  5049. //Figure out the distance between the sprites
  5050. /*
  5051. let vx = (leader.x + leader.width / 2) - (follower.x + follower.width / 2),
  5052. vy = (leader.y + leader.height / 2) - (follower.y + follower.height / 2),
  5053. distance = Math.sqrt(vx * vx + vy * vy);
  5054. */
  5055. var vx = leader.x + this._getCenter(leader, leader.width, "x") - (follower.x + this._getCenter(follower, follower.width, "x")),
  5056. vy = leader.y + this._getCenter(leader, leader.height, "y") - (follower.y + this._getCenter(follower, follower.height, "y")),
  5057. distance = Math.sqrt(vx * vx + vy * vy);
  5058. //Move the follower if it's more than 1 pixel
  5059. //away from the leader
  5060. if (distance >= 1) {
  5061. follower.x += vx * speed;
  5062. follower.y += vy * speed;
  5063. }
  5064. }
  5065. /*
  5066. followConstant
  5067. ----------------
  5068. Make a sprite move towards another sprite at a constant speed.
  5069. Parameters:
  5070. a. A sprite object. This is the `follower` sprite.
  5071. b. A sprite object. This is the `leader` sprite that the follower will chase.
  5072. c. The speed value, such as 3. The is the pixels per frame that the sprite will move. A higher number makes the follower move faster.
  5073. gu.followConstant(follower, leader, speed);
  5074. */
  5075. }, {
  5076. key: "followConstant",
  5077. value: function followConstant(follower, leader, speed) {
  5078. //Figure out the distance between the sprites
  5079. var vx = leader.x + this._getCenter(leader, leader.width, "x") - (follower.x + this._getCenter(follower, follower.width, "x")),
  5080. vy = leader.y + this._getCenter(leader, leader.height, "y") - (follower.y + this._getCenter(follower, follower.height, "y")),
  5081. distance = Math.sqrt(vx * vx + vy * vy);
  5082. //Move the follower if it's more than 1 move
  5083. //away from the leader
  5084. if (distance >= speed) {
  5085. follower.x += vx / distance * speed;
  5086. follower.y += vy / distance * speed;
  5087. }
  5088. }
  5089. /*
  5090. angle
  5091. -----
  5092. Return the angle in Radians between two sprites.
  5093. Parameters:
  5094. a. A sprite object.
  5095. b. A sprite object.
  5096. You can use it to make a sprite rotate towards another sprite like this:
  5097. box.rotation = gu.angle(box, pointer);
  5098. */
  5099. }, {
  5100. key: "angle",
  5101. value: function angle(s1, s2) {
  5102. return Math.atan2(
  5103. //This is the code you need if you don't want to compensate
  5104. //for a possible shift in the sprites' x/y anchor points
  5105. /*
  5106. (s2.y + s2.height / 2) - (s1.y + s1.height / 2),
  5107. (s2.x + s2.width / 2) - (s1.x + s1.width / 2)
  5108. */
  5109. //This code adapts to a shifted anchor point
  5110. s2.y + this._getCenter(s2, s2.height, "y") - (s1.y + this._getCenter(s1, s1.height, "y")), s2.x + this._getCenter(s2, s2.width, "x") - (s1.x + this._getCenter(s1, s1.width, "x")));
  5111. }
  5112. /*
  5113. _getCenter
  5114. ----------
  5115. A utility that finds the center point of the sprite. If it's anchor point is the
  5116. sprite's top left corner, then the center is calculated from that point.
  5117. If the anchor point has been shifted, then the anchor x/y point is used as the sprite's center
  5118. */
  5119. }, {
  5120. key: "_getCenter",
  5121. value: function _getCenter(o, dimension, axis) {
  5122. if (o.anchor !== undefined) {
  5123. if (o.anchor[axis] !== 0) {
  5124. return 0;
  5125. } else {
  5126. //console.log(o.anchor[axis])
  5127. return dimension / 2;
  5128. }
  5129. } else {
  5130. return dimension;
  5131. }
  5132. }
  5133. /*
  5134. rotateAroundSprite
  5135. ------------
  5136. Make a sprite rotate around another sprite.
  5137. Parameters:
  5138. a. The sprite you want to rotate.
  5139. b. The sprite around which you want to rotate the first sprite.
  5140. c. The distance, in pixels, that the roating sprite should be offset from the center.
  5141. d. The angle of rotations, in radians.
  5142. gu.rotateAroundSprite(orbitingSprite, centerSprite, 50, angleInRadians);
  5143. Use it inside a game loop, and make sure you update the angle value (the 4th argument) each frame.
  5144. */
  5145. }, {
  5146. key: "rotateAroundSprite",
  5147. value: function rotateAroundSprite(rotatingSprite, centerSprite, distance, angle) {
  5148. rotatingSprite.x = centerSprite.x + this._getCenter(centerSprite, centerSprite.width, "x") - rotatingSprite.parent.x + distance * Math.cos(angle) - this._getCenter(rotatingSprite, rotatingSprite.width, "x");
  5149. rotatingSprite.y = centerSprite.y + this._getCenter(centerSprite, centerSprite.height, "y") - rotatingSprite.parent.y + distance * Math.sin(angle) - this._getCenter(rotatingSprite, rotatingSprite.height, "y");
  5150. }
  5151. /*
  5152. rotateAroundPoint
  5153. -----------------
  5154. Make a point rotate around another point.
  5155. Parameters:
  5156. a. The point you want to rotate.
  5157. b. The point around which you want to rotate the first point.
  5158. c. The distance, in pixels, that the roating sprite should be offset from the center.
  5159. d. The angle of rotations, in radians.
  5160. gu.rotateAroundPoint(orbitingPoint, centerPoint, 50, angleInRadians);
  5161. Use it inside a game loop, and make sure you update the angle value (the 4th argument) each frame.
  5162. */
  5163. }, {
  5164. key: "rotateAroundPoint",
  5165. value: function rotateAroundPoint(pointX, pointY, distanceX, distanceY, angle) {
  5166. var point = {};
  5167. point.x = pointX + Math.cos(angle) * distanceX;
  5168. point.y = pointY + Math.sin(angle) * distanceY;
  5169. return point;
  5170. }
  5171. /*
  5172. randomInt
  5173. ---------
  5174. Return a random integer between a minimum and maximum value
  5175. Parameters:
  5176. a. An integer.
  5177. b. An integer.
  5178. Here's how you can use it to get a random number between, 1 and 10:
  5179. let number = gu.randomInt(1, 10);
  5180. */
  5181. }, {
  5182. key: "randomInt",
  5183. value: function randomInt(min, max) {
  5184. return Math.floor(Math.random() * (max - min + 1)) + min;
  5185. }
  5186. /*
  5187. randomFloat
  5188. -----------
  5189. Return a random floating point number between a minimum and maximum value
  5190. Parameters:
  5191. a. Any number.
  5192. b. Any number.
  5193. Here's how you can use it to get a random floating point number between, 1 and 10:
  5194. let number = gu.randomFloat(1, 10);
  5195. */
  5196. }, {
  5197. key: "randomFloat",
  5198. value: function randomFloat(min, max) {
  5199. return min + Math.random() * (max - min);
  5200. }
  5201. /*
  5202. Wait
  5203. ----
  5204. Lets you wait for a specific number of milliseconds before running the
  5205. next function.
  5206. gu.wait(1000, runThisFunctionNext());
  5207. */
  5208. }, {
  5209. key: "wait",
  5210. value: function wait(duration, callBack) {
  5211. setTimeout(callBack, duration);
  5212. }
  5213. /*
  5214. Move
  5215. ----
  5216. Move a sprite by adding it's velocity to it's position. The sprite
  5217. must have `vx` and `vy` values for this to work. You can supply a
  5218. single sprite, or a list of sprites, separated by commas.
  5219. gu.move(sprite);
  5220. */
  5221. }, {
  5222. key: "move",
  5223. value: function move() {
  5224. for (var _len = arguments.length, sprites = Array(_len), _key = 0; _key < _len; _key++) {
  5225. sprites[_key] = arguments[_key];
  5226. }
  5227. //Move sprites that's aren't in an array
  5228. if (!(sprites[0] instanceof Array)) {
  5229. if (sprites.length > 1) {
  5230. sprites.forEach(function (sprite) {
  5231. sprite.x += sprite.vx;
  5232. sprite.y += sprite.vy;
  5233. });
  5234. } else {
  5235. sprites[0].x += sprites[0].vx;
  5236. sprites[0].y += sprites[0].vy;
  5237. }
  5238. }
  5239. //Move sprites in an array of sprites
  5240. else {
  5241. var spritesArray = sprites[0];
  5242. if (spritesArray.length > 0) {
  5243. for (var i = spritesArray.length - 1; i >= 0; i--) {
  5244. var sprite = spritesArray[i];
  5245. sprite.x += sprite.vx;
  5246. sprite.y += sprite.vy;
  5247. }
  5248. }
  5249. }
  5250. }
  5251. /*
  5252. World camera
  5253. ------------
  5254. The `worldCamera` method returns a `camera` object
  5255. with `x` and `y` properties. It has
  5256. two useful methods: `centerOver`, to center the camera over
  5257. a sprite, and `follow` to make it follow a sprite.
  5258. `worldCamera` arguments: worldObject, theCanvas
  5259. The worldObject needs to have a `width` and `height` property.
  5260. */
  5261. }, {
  5262. key: "worldCamera",
  5263. value: function worldCamera(world, worldWidth, worldHeight, canvas) {
  5264. //Define a `camera` object with helpful properties
  5265. var camera = {
  5266. width: canvas.width,
  5267. height: canvas.height,
  5268. _x: 0,
  5269. _y: 0,
  5270. //`x` and `y` getters/setters
  5271. //When you change the camera's position,
  5272. //they shift the position of the world in the opposite direction
  5273. get x() {
  5274. return this._x;
  5275. },
  5276. set x(value) {
  5277. this._x = value;
  5278. world.x = -this._x;
  5279. //world._previousX = world.x;
  5280. },
  5281. get y() {
  5282. return this._y;
  5283. },
  5284. set y(value) {
  5285. this._y = value;
  5286. world.y = -this._y;
  5287. //world._previousY = world.y;
  5288. },
  5289. //The center x and y position of the camera
  5290. get centerX() {
  5291. return this.x + this.width / 2;
  5292. },
  5293. get centerY() {
  5294. return this.y + this.height / 2;
  5295. },
  5296. //Boundary properties that define a rectangular area, half the size
  5297. //of the game screen. If the sprite that the camera is following
  5298. //is inide this area, the camera won't scroll. If the sprite
  5299. //crosses this boundary, the `follow` function ahead will change
  5300. //the camera's x and y position to scroll the game world
  5301. get rightInnerBoundary() {
  5302. return this.x + this.width / 2 + this.width / 4;
  5303. },
  5304. get leftInnerBoundary() {
  5305. return this.x + this.width / 2 - this.width / 4;
  5306. },
  5307. get topInnerBoundary() {
  5308. return this.y + this.height / 2 - this.height / 4;
  5309. },
  5310. get bottomInnerBoundary() {
  5311. return this.y + this.height / 2 + this.height / 4;
  5312. },
  5313. //The code next defines two camera
  5314. //methods: `follow` and `centerOver`
  5315. //Use the `follow` method to make the camera follow a sprite
  5316. follow: function follow(sprite) {
  5317. //Check the sprites position in relation to the inner
  5318. //boundary. Move the camera to follow the sprite if the sprite
  5319. //strays outside the boundary
  5320. if (sprite.x < this.leftInnerBoundary) {
  5321. this.x = sprite.x - this.width / 4;
  5322. }
  5323. if (sprite.y < this.topInnerBoundary) {
  5324. this.y = sprite.y - this.height / 4;
  5325. }
  5326. if (sprite.x + sprite.width > this.rightInnerBoundary) {
  5327. this.x = sprite.x + sprite.width - this.width / 4 * 3;
  5328. }
  5329. if (sprite.y + sprite.height > this.bottomInnerBoundary) {
  5330. this.y = sprite.y + sprite.height - this.height / 4 * 3;
  5331. }
  5332. //If the camera reaches the edge of the map, stop it from moving
  5333. if (this.x < 0) {
  5334. this.x = 0;
  5335. }
  5336. if (this.y < 0) {
  5337. this.y = 0;
  5338. }
  5339. if (this.x + this.width > worldWidth) {
  5340. this.x = worldWidth - this.width;
  5341. }
  5342. if (this.y + this.height > worldHeight) {
  5343. this.y = worldHeight - this.height;
  5344. }
  5345. },
  5346. //Use the `centerOver` method to center the camera over a sprite
  5347. centerOver: function centerOver(sprite) {
  5348. //Center the camera over a sprite
  5349. this.x = sprite.x + sprite.halfWidth - this.width / 2;
  5350. this.y = sprite.y + sprite.halfHeight - this.height / 2;
  5351. }
  5352. };
  5353. //Return the `camera` object
  5354. return camera;
  5355. }
  5356. }, {
  5357. key: "lineOfSight",
  5358. /*
  5359. Line of sight
  5360. ------------
  5361. The `lineOfSight` method will return `true` if there’s clear line of sight
  5362. between two sprites, and `false` if there isn’t. Here’s how to use it in your game code:
  5363. monster.lineOfSight = gu.lineOfSight(
  5364. monster, //Sprite one
  5365. alien, //Sprite two
  5366. boxes, //An array of obstacle sprites
  5367. 16 //The distance between each collision point
  5368. );
  5369. The 4th argument determines the distance between collision points.
  5370. For better performance, make this a large number, up to the maximum
  5371. width of your smallest sprite (such as 64 or 32). For greater precision,
  5372. use a smaller number. You can use the lineOfSight value to decide how
  5373. to change certain things in your game. For example:
  5374. if (monster.lineOfSight) {
  5375. monster.show(monster.states.angry)
  5376. } else {
  5377. monster.show(monster.states.normal)
  5378. }
  5379. */
  5380. value: function lineOfSight(s1, //The first sprite, with `centerX` and `centerY` properties
  5381. s2, //The second sprite, with `centerX` and `centerY` properties
  5382. obstacles) //The distance between collision points
  5383. {
  5384. var segment = arguments.length <= 3 || arguments[3] === undefined ? 32 : arguments[3];
  5385. //Calculate the center points of each sprite
  5386. spriteOneCenterX = s1.x + this._getCenter(s1, s1.width, "x");
  5387. spriteOneCenterY = s1.y + this._getCenter(s1, s1.height, "y");
  5388. spriteTwoCenterX = s2.x + this._getCenter(s2, s2.width, "x");
  5389. spriteTwoCenterY = s2.y + this._getCenter(s2, s2.height, "y");
  5390. //Plot a vector between spriteTwo and spriteOne
  5391. var vx = spriteTwoCenterX - spriteOneCenterX,
  5392. vy = spriteTwoCenterY - spriteOneCenterY;
  5393. //Find the vector's magnitude (its length in pixels)
  5394. var magnitude = Math.sqrt(vx * vx + vy * vy);
  5395. //How many points will we need to test?
  5396. var numberOfPoints = magnitude / segment;
  5397. //Create an array of x/y points, separated by 64 pixels, that
  5398. //extends from `spriteOne` to `spriteTwo`
  5399. var points = function points() {
  5400. //Initialize an array that is going to store all our points
  5401. //along the vector
  5402. var arrayOfPoints = [];
  5403. //Create a point object for each segment of the vector and
  5404. //store its x/y position as well as its index number on
  5405. //the map array
  5406. for (var i = 1; i <= numberOfPoints; i++) {
  5407. //Calculate the new magnitude for this iteration of the loop
  5408. var newMagnitude = segment * i;
  5409. //Find the unit vector. This is a small, scaled down version of
  5410. //the vector between the sprites that's less than one pixel long.
  5411. //It points in the same direction as the main vector, but because it's
  5412. //the smallest size that the vector can be, we can use it to create
  5413. //new vectors of varying length
  5414. var dx = vx / magnitude,
  5415. dy = vy / magnitude;
  5416. //Use the unit vector and newMagnitude to figure out the x/y
  5417. //position of the next point in this loop iteration
  5418. var x = spriteOneCenterX + dx * newMagnitude,
  5419. y = spriteOneCenterY + dy * newMagnitude;
  5420. //Push a point object with x and y properties into the `arrayOfPoints`
  5421. arrayOfPoints.push({
  5422. x: x, y: y
  5423. });
  5424. }
  5425. //Return the array of point objects
  5426. return arrayOfPoints;
  5427. };
  5428. //Test for a collision between a point and a sprite
  5429. var hitTestPoint = function hitTestPoint(point, sprite) {
  5430. //Find out if the point's position is inside the area defined
  5431. //by the sprite's left, right, top and bottom sides
  5432. var left = point.x > sprite.x,
  5433. right = point.x < sprite.x + sprite.width,
  5434. top = point.y > sprite.y,
  5435. bottom = point.y < sprite.y + sprite.height;
  5436. //If all the collision conditions are met, you know the
  5437. //point is intersecting the sprite
  5438. return left && right && top && bottom;
  5439. };
  5440. //The `noObstacles` function will return `true` if all the tile
  5441. //index numbers along the vector are `0`, which means they contain
  5442. //no obstacles. If any of them aren't 0, then the function returns
  5443. //`false` which means there's an obstacle in the way
  5444. var noObstacles = points().every(function (point) {
  5445. return obstacles.every(function (obstacle) {
  5446. return !hitTestPoint(point, obstacle);
  5447. });
  5448. });
  5449. //Return the true/false value of the collision test
  5450. return noObstacles;
  5451. }
  5452. }]);
  5453. return GameUtilities;
  5454. })();
  5455. //# sourceMappingURL=gameUtilities.js.map"use strict";
  5456. var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
  5457. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  5458. var Smoothie = (function () {
  5459. function Smoothie() //Refers to `tileposition` and `tileScale` x and y properties
  5460. {
  5461. var options = arguments.length <= 0 || arguments[0] === undefined ? {
  5462. engine: PIXI, //The rendering engine (Pixi)
  5463. renderer: undefined, //The Pixi renderer you created in your application
  5464. root: undefined, //The root Pixi display object (usually the `stage`)
  5465. update: undefined, //A logic function that should be called every frame of the game loop
  5466. interpolate: true, //A Boolean to turn interpolation on or off
  5467. fps: 60, //The frame rate at which the application's looping logic function should update
  5468. renderFps: undefined, //The frame rate at which sprites should be rendered
  5469. properties: { //Sprite roperties that should be interpolated
  5470. position: true,
  5471. rotation: true,
  5472. size: false,
  5473. scale: false,
  5474. alpha: false,
  5475. tile: false }
  5476. } : arguments[0];
  5477. _classCallCheck(this, Smoothie);
  5478. if (options.engine === undefined) throw new Error("Please assign a rendering engine as Smoothie's engine option");
  5479. //Find out which rendering engine is being used (the default is Pixi)
  5480. this.engine = "";
  5481. //If the `renderingEngine` is Pixi, set up Pixi object aliases
  5482. if (options.engine.ParticleContainer && options.engine.Sprite) {
  5483. this.renderingEngine = "pixi";
  5484. this.Container = options.engine.Container;
  5485. this.Sprite = options.engine.Sprite;
  5486. this.MovieClip = options.engine.extras.MovieClip;
  5487. }
  5488. //Check to make sure the user had supplied a renderer. If you're
  5489. //using Pixi, this should be the instantiated `renderer` object
  5490. //that you created in your main application
  5491. if (options.renderer === undefined) {
  5492. throw new Error("Please assign a renderer object as Smoothie's renderer option");
  5493. } else {
  5494. this.renderer = options.renderer;
  5495. }
  5496. //Check to make sure the user has supplied a root container. This
  5497. //is the object is at the top of the display list heirarchy. If
  5498. //you're using Pixi, it would be a `Container` object, often by
  5499. //convention called the `stage`
  5500. if (options.root === undefined) {
  5501. throw new Error("Please assign a root container object (the stage) as Smoothie's rootr option");
  5502. } else {
  5503. this.stage = options.root;
  5504. }
  5505. if (options.update === undefined) {
  5506. throw new Error("Please assign a function that you want to update on each frame as Smoothie's update option");
  5507. } else {
  5508. this.update = options.update;
  5509. }
  5510. //Define the sprite properties that should be interpolated
  5511. if (options.properties === undefined) {
  5512. this.properties = { position: true, rotation: true };
  5513. } else {
  5514. this.properties = options.properties;
  5515. }
  5516. //The upper-limit frames per second that the game' logic update
  5517. //function should run at.
  5518. //Smoothie defaults to 60 fps.
  5519. if (options.fps !== undefined) {
  5520. this._fps = options.fps;
  5521. } else {
  5522. this._fps = undefined;
  5523. }
  5524. //Optionally Clamp the upper-limit frame rate at which sprites should render
  5525. if (options.renderFps !== undefined) {
  5526. this._renderFps = options.renderFps;
  5527. } else {
  5528. this._renderFps = undefined;
  5529. }
  5530. //Set sprite rendering position interpolation to
  5531. //`true` by default
  5532. if (options.interpolate === false) {
  5533. this.interpolate = false;
  5534. } else {
  5535. this.interpolate = true;
  5536. }
  5537. //A variable that can be used to pause and play Smoothie
  5538. this.paused = false;
  5539. //Private properties used to set the frame rate and figure out the interpolation values
  5540. this._startTime = Date.now();
  5541. this._frameDuration = 1000 / this._fps;
  5542. this._lag = 0;
  5543. this._lagOffset = 0;
  5544. this._renderStartTime = 0;
  5545. if (this._renderFps !== undefined) {
  5546. this._renderDuration = 1000 / this._renderFps;
  5547. }
  5548. }
  5549. //Getters and setters
  5550. //Fps
  5551. _createClass(Smoothie, [{
  5552. key: "pause",
  5553. //Methods to pause and resume Smoothie
  5554. value: function pause() {
  5555. this.paused = true;
  5556. }
  5557. }, {
  5558. key: "resume",
  5559. value: function resume() {
  5560. this.paused = false;
  5561. }
  5562. //The `start` method gets Smoothie's game loop running
  5563. }, {
  5564. key: "start",
  5565. value: function start() {
  5566. //Start the game loop
  5567. this.gameLoop();
  5568. }
  5569. //The core game loop
  5570. }, {
  5571. key: "gameLoop",
  5572. value: function gameLoop(timestamp) {
  5573. var _this = this;
  5574. requestAnimationFrame(this.gameLoop.bind(this));
  5575. //Only run if Smoothie isn't paused
  5576. if (!this.paused) {
  5577. //The `interpolate` function updates the logic function at the
  5578. //same rate as the user-defined fps, renders the sprites, with
  5579. //interpolation, at the maximum frame rate the system is capbale
  5580. //of
  5581. var interpolate = function interpolate() {
  5582. //Calculate the time that has elapsed since the last frame
  5583. var current = Date.now(),
  5584. elapsed = current - _this._startTime;
  5585. //Catch any unexpectedly large frame rate spikes
  5586. if (elapsed > 1000) elapsed = _this._frameDuration;
  5587. //For interpolation:
  5588. _this._startTime = current;
  5589. //Add the elapsed time to the lag counter
  5590. _this._lag += elapsed;
  5591. //Update the frame if the lag counter is greater than or
  5592. //equal to the frame duration
  5593. while (_this._lag >= _this._frameDuration) {
  5594. //Capture the sprites' previous properties for rendering
  5595. //interpolation
  5596. _this.capturePreviousSpriteProperties();
  5597. //Update the logic in the user-defined update function
  5598. _this.update();
  5599. //Reduce the lag counter by the frame duration
  5600. _this._lag -= _this._frameDuration;
  5601. }
  5602. //Calculate the lag offset and use it to render the sprites
  5603. _this._lagOffset = _this._lag / _this._frameDuration;
  5604. _this.render(_this._lagOffset);
  5605. };
  5606. //If the `fps` hasn't been defined, call the user-defined update
  5607. //function and render the sprites at the maximum rate the
  5608. //system is capable of
  5609. if (this._fps === undefined) {
  5610. //Run the user-defined game logic function each frame of the
  5611. //game at the maxium frame rate your system is capable of
  5612. this.update();
  5613. this.render();
  5614. } else {
  5615. if (this._renderFps === undefined) {
  5616. interpolate();
  5617. } else {
  5618. //Implement optional frame rate rendering clamping
  5619. if (timestamp >= this._renderStartTime) {
  5620. //Update the current logic frame and render with
  5621. //interpolation
  5622. interpolate();
  5623. //Reset the frame render start time
  5624. this._renderStartTime = timestamp + this._renderDuration;
  5625. }
  5626. }
  5627. }
  5628. }
  5629. }
  5630. //`capturePreviousSpritePositions`
  5631. //This function is run in the game loop just before the logic update
  5632. //to store all the sprites' previous positions from the last frame.
  5633. //It allows the render function to interpolate the sprite positions
  5634. //for ultra-smooth sprite rendering at any frame rate
  5635. }, {
  5636. key: "capturePreviousSpriteProperties",
  5637. value: function capturePreviousSpriteProperties() {
  5638. var _this2 = this;
  5639. //A function that capture's the sprites properties
  5640. var setProperties = function setProperties(sprite) {
  5641. if (_this2.properties.position) {
  5642. sprite._previousX = sprite.x;
  5643. sprite._previousY = sprite.y;
  5644. }
  5645. if (_this2.properties.rotation) {
  5646. sprite._previousRotation = sprite.rotation;
  5647. }
  5648. if (_this2.properties.size) {
  5649. sprite._previousWidth = sprite.width;
  5650. sprite._previousHeight = sprite.height;
  5651. }
  5652. if (_this2.properties.scale) {
  5653. sprite._previousScaleX = sprite.scale.x;
  5654. sprite._previousScaleY = sprite.scale.y;
  5655. }
  5656. if (_this2.properties.alpha) {
  5657. sprite._previousAlpha = sprite.alpha;
  5658. }
  5659. if (_this2.properties.tile) {
  5660. if (sprite.tilePosition !== undefined) {
  5661. sprite._previousTilePositionX = sprite.tilePosition.x;
  5662. sprite._previousTilePositionY = sprite.tilePosition.y;
  5663. }
  5664. if (sprite.tileScale !== undefined) {
  5665. sprite._previousTileScaleX = sprite.tileScale.x;
  5666. sprite._previousTileScaleY = sprite.tileScale.y;
  5667. }
  5668. }
  5669. if (sprite.children && sprite.children.length > 0) {
  5670. for (var i = 0; i < sprite.children.length; i++) {
  5671. var child = sprite.children[i];
  5672. setProperties(child);
  5673. }
  5674. }
  5675. };
  5676. //loop through the all the sprites and capture their properties
  5677. for (var i = 0; i < this.stage.children.length; i++) {
  5678. var sprite = this.stage.children[i];
  5679. setProperties(sprite);
  5680. }
  5681. }
  5682. //Smoothie's `render` method will interpolate the sprite positions and
  5683. //rotation for
  5684. //ultra-smooth animation, if Hexi's `interpolate` property is `true`
  5685. //(it is by default)
  5686. }, {
  5687. key: "render",
  5688. value: function render() {
  5689. var _this3 = this;
  5690. var lagOffset = arguments.length <= 0 || arguments[0] === undefined ? 1 : arguments[0];
  5691. //Calculate the sprites' interpolated render positions if
  5692. //`this.interpolate` is `true` (It is true by default)
  5693. if (this.interpolate) {
  5694. (function () {
  5695. //A recursive function that does the work of figuring out the
  5696. //interpolated positions
  5697. var interpolateSprite = function interpolateSprite(sprite) {
  5698. //Position (`x` and `y` properties)
  5699. if (_this3.properties.position) {
  5700. //Capture the sprite's current x and y positions
  5701. sprite._currentX = sprite.x;
  5702. sprite._currentY = sprite.y;
  5703. //Figure out its interpolated positions
  5704. if (sprite._previousX !== undefined) {
  5705. sprite.x = (sprite.x - sprite._previousX) * lagOffset + sprite._previousX;
  5706. }
  5707. if (sprite._previousY !== undefined) {
  5708. sprite.y = (sprite.y - sprite._previousY) * lagOffset + sprite._previousY;
  5709. }
  5710. }
  5711. //Rotation (`rotation` property)
  5712. if (_this3.properties.rotation) {
  5713. //Capture the sprite's current rotation
  5714. sprite._currentRotation = sprite.rotation;
  5715. //Figure out its interpolated rotation
  5716. if (sprite._previousRotation !== undefined) {
  5717. sprite.rotation = (sprite.rotation - sprite._previousRotation) * lagOffset + sprite._previousRotation;
  5718. }
  5719. }
  5720. //Size (`width` and `height` properties)
  5721. if (_this3.properties.size) {
  5722. //Only allow this for Sprites or MovieClips. Because
  5723. //Containers vary in size when the sprites they contain
  5724. //move, the interpolation will cause them to scale erraticly
  5725. if (sprite instanceof _this3.Sprite || sprite instanceof _this3.MovieClip) {
  5726. //Capture the sprite's current size
  5727. sprite._currentWidth = sprite.width;
  5728. sprite._currentHeight = sprite.height;
  5729. //Figure out the sprite's interpolated size
  5730. if (sprite._previousWidth !== undefined) {
  5731. sprite.width = (sprite.width - sprite._previousWidth) * lagOffset + sprite._previousWidth;
  5732. }
  5733. if (sprite._previousHeight !== undefined) {
  5734. sprite.height = (sprite.height - sprite._previousHeight) * lagOffset + sprite._previousHeight;
  5735. }
  5736. }
  5737. }
  5738. //Scale (`scale.x` and `scale.y` properties)
  5739. if (_this3.properties.scale) {
  5740. //Capture the sprite's current scale
  5741. sprite._currentScaleX = sprite.scale.x;
  5742. sprite._currentScaleY = sprite.scale.y;
  5743. //Figure out the sprite's interpolated scale
  5744. if (sprite._previousScaleX !== undefined) {
  5745. sprite.scale.x = (sprite.scale.x - sprite._previousScaleX) * lagOffset + sprite._previousScaleX;
  5746. }
  5747. if (sprite._previousScaleY !== undefined) {
  5748. sprite.scale.y = (sprite.scale.y - sprite._previousScaleY) * lagOffset + sprite._previousScaleY;
  5749. }
  5750. }
  5751. //Alpha (`alpha` property)
  5752. if (_this3.properties.alpha) {
  5753. //Capture the sprite's current alpha
  5754. sprite._currentAlpha = sprite.alpha;
  5755. //Figure out its interpolated alpha
  5756. if (sprite._previousAlpha !== undefined) {
  5757. sprite.alpha = (sprite.alpha - sprite._previousAlpha) * lagOffset + sprite._previousAlpha;
  5758. }
  5759. }
  5760. //Tiling sprite properties (`tileposition` and `tileScale` x
  5761. //and y values)
  5762. if (_this3.properties.tile) {
  5763. //`tilePosition.x` and `tilePosition.y`
  5764. if (sprite.tilePosition !== undefined) {
  5765. //Capture the sprite's current tile x and y positions
  5766. sprite._currentTilePositionX = sprite.tilePosition.x;
  5767. sprite._currentTilePositionY = sprite.tilePosition.y;
  5768. //Figure out its interpolated positions
  5769. if (sprite._previousTilePositionX !== undefined) {
  5770. sprite.tilePosition.x = (sprite.tilePosition.x - sprite._previousTilePositionX) * lagOffset + sprite._previousTilePositionX;
  5771. }
  5772. if (sprite._previousTilePositionY !== undefined) {
  5773. sprite.tilePosition.y = (sprite.tilePosition.y - sprite._previousTilePositionY) * lagOffset + sprite._previousTilePositionY;
  5774. }
  5775. }
  5776. //`tileScale.x` and `tileScale.y`
  5777. if (sprite.tileScale !== undefined) {
  5778. //Capture the sprite's current tile scale
  5779. sprite._currentTileScaleX = sprite.tileScale.x;
  5780. sprite._currentTileScaleY = sprite.tileScale.y;
  5781. //Figure out the sprite's interpolated scale
  5782. if (sprite._previousTileScaleX !== undefined) {
  5783. sprite.tileScale.x = (sprite.tileScale.x - sprite._previousTileScaleX) * lagOffset + sprite._previousTileScaleX;
  5784. }
  5785. if (sprite._previousTileScaleY !== undefined) {
  5786. sprite.tileScale.y = (sprite.tileScale.y - sprite._previousTileScaleY) * lagOffset + sprite._previousTileScaleY;
  5787. }
  5788. }
  5789. }
  5790. //Interpolate the sprite's children, if it has any
  5791. if (sprite.children.length !== 0) {
  5792. for (var j = 0; j < sprite.children.length; j++) {
  5793. //Find the sprite's child
  5794. var child = sprite.children[j];
  5795. //display the child
  5796. interpolateSprite(child);
  5797. }
  5798. }
  5799. };
  5800. //loop through the all the sprites and interpolate them
  5801. for (var i = 0; i < _this3.stage.children.length; i++) {
  5802. var sprite = _this3.stage.children[i];
  5803. interpolateSprite(sprite);
  5804. }
  5805. })();
  5806. }
  5807. //Render the stage. If the sprite positions have been
  5808. //interpolated, those position values will be used to render the
  5809. //sprite
  5810. this.renderer.render(this.stage);
  5811. //Restore the sprites' original x and y values if they've been
  5812. //interpolated for this frame
  5813. if (this.interpolate) {
  5814. (function () {
  5815. //A recursive function that restores the sprite's original,
  5816. //uninterpolated x and y positions
  5817. var restoreSpriteProperties = function restoreSpriteProperties(sprite) {
  5818. if (_this3.properties.position) {
  5819. sprite.x = sprite._currentX;
  5820. sprite.y = sprite._currentY;
  5821. }
  5822. if (_this3.properties.rotation) {
  5823. sprite.rotation = sprite._currentRotation;
  5824. }
  5825. if (_this3.properties.size) {
  5826. //Only allow this for Sprites or Movie clips, to prevent
  5827. //Container scaling bug
  5828. if (sprite instanceof _this3.Sprite || sprite instanceof _this3.MovieClip) {
  5829. sprite.width = sprite._currentWidth;
  5830. sprite.height = sprite._currentHeight;
  5831. }
  5832. }
  5833. if (_this3.properties.scale) {
  5834. sprite.scale.x = sprite._currentScaleX;
  5835. sprite.scale.y = sprite._currentScaleY;
  5836. }
  5837. if (_this3.properties.alpha) {
  5838. sprite.alpha = sprite._currentAlpha;
  5839. }
  5840. if (_this3.properties.tile) {
  5841. if (sprite.tilePosition !== undefined) {
  5842. sprite.tilePosition.x = sprite._currentTilePositionX;
  5843. sprite.tilePosition.y = sprite._currentTilePositionY;
  5844. }
  5845. if (sprite.tileScale !== undefined) {
  5846. sprite.tileScale.x = sprite._currentTileScaleX;
  5847. sprite.tileScale.y = sprite._currentTileScaleY;
  5848. }
  5849. }
  5850. //Restore the sprite's children, if it has any
  5851. if (sprite.children.length !== 0) {
  5852. for (var i = 0; i < sprite.children.length; i++) {
  5853. //Find the sprite's child
  5854. var child = sprite.children[i];
  5855. //Restore the child sprite properties
  5856. restoreSpriteProperties(child);
  5857. }
  5858. }
  5859. };
  5860. for (var i = 0; i < _this3.stage.children.length; i++) {
  5861. var sprite = _this3.stage.children[i];
  5862. restoreSpriteProperties(sprite);
  5863. }
  5864. })();
  5865. }
  5866. }
  5867. }, {
  5868. key: "fps",
  5869. get: function get() {
  5870. return this._fps;
  5871. },
  5872. set: function set(value) {
  5873. this._fps = value;
  5874. this._frameDuration = 1000 / this._fps;
  5875. }
  5876. //renderFps
  5877. }, {
  5878. key: "renderFps",
  5879. get: function get() {
  5880. return this._renderFps;
  5881. },
  5882. set: function set(value) {
  5883. this._renderFps = value;
  5884. this._renderDuration = 1000 / this._renderFps;
  5885. }
  5886. //`dt` (Delta time, the `this._lagOffset` value in Smoothie's code)
  5887. }, {
  5888. key: "dt",
  5889. get: function get() {
  5890. return this._lagOffset;
  5891. }
  5892. }]);
  5893. return Smoothie;
  5894. })();
  5895. //# sourceMappingURL=smoothie.js.map"use strict";
  5896. var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
  5897. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  5898. var TileUtilities = (function () {
  5899. function TileUtilities() {
  5900. var renderingEngine = arguments.length <= 0 || arguments[0] === undefined ? PIXI : arguments[0];
  5901. _classCallCheck(this, TileUtilities);
  5902. if (renderingEngine === undefined) throw new Error("Please assign a rendering engine in the constructor before using bump.js");
  5903. //Find out which rendering engine is being used (the default is Pixi)
  5904. this.renderer = "";
  5905. //If the `renderingEngine` is Pixi, set up Pixi object aliases
  5906. if (renderingEngine.ParticleContainer && renderingEngine.Sprite) {
  5907. this.renderingEngine = renderingEngine;
  5908. this.renderer = "pixi";
  5909. this.Container = this.renderingEngine.Container;
  5910. this.TextureCache = this.renderingEngine.utils.TextureCache;
  5911. this.Texture = this.renderingEngine.Texture;
  5912. this.Sprite = this.renderingEngine.Sprite;
  5913. this.Rectangle = this.renderingEngine.Rectangle;
  5914. this.Graphics = this.renderingEngine.Graphics;
  5915. this.loader = this.renderingEngine.loader;
  5916. this.resources = this.renderingEngine.loader.resources;
  5917. }
  5918. }
  5919. //Make a texture from a frame in another texture or image
  5920. _createClass(TileUtilities, [{
  5921. key: "frame",
  5922. value: function frame(source, x, y, width, height) {
  5923. var texture = undefined,
  5924. imageFrame = undefined;
  5925. //If the source is a string, it's either a texture in the
  5926. //cache or an image file
  5927. if (typeof source === "string") {
  5928. if (this.TextureCache[source]) {
  5929. texture = new this.Texture(this.TextureCache[source]);
  5930. }
  5931. }
  5932. //If the `source` is a texture, use it
  5933. else if (source instanceof this.Texture) {
  5934. texture = new this.Texture(source);
  5935. }
  5936. if (!texture) {
  5937. throw new Error("Please load the " + source + " texture into the cache.");
  5938. } else {
  5939. //Make a rectangle the size of the sub-image
  5940. imageFrame = new this.Rectangle(x, y, width, height);
  5941. texture.frame = imageFrame;
  5942. return texture;
  5943. }
  5944. }
  5945. //#### getIndex
  5946. //The `getIndex` helper method
  5947. //converts a sprite's x and y position to an array index number.
  5948. //It returns a single index value that tells you the map array
  5949. //index number that the sprite is in
  5950. }, {
  5951. key: "getIndex",
  5952. value: function getIndex(x, y, tilewidth, tileheight, mapWidthInTiles) {
  5953. var index = {};
  5954. //Convert pixel coordinates to map index coordinates
  5955. index.x = Math.floor(x / tilewidth);
  5956. index.y = Math.floor(y / tileheight);
  5957. //Return the index number
  5958. return index.x + index.y * mapWidthInTiles;
  5959. }
  5960. /*
  5961. #### getTile
  5962. The `getTile` helper method
  5963. converts a tile's index number into x/y screen
  5964. coordinates, and capture's the tile's grid index (`gid`) number.
  5965. It returns an object with `x`, `y`, `centerX`, `centerY`, `width`, `height`, `halfWidth`
  5966. `halffHeight` and `gid` properties. (The `gid` number is the value that the tile has in the
  5967. mapArray) This lets you use the returned object
  5968. with the 2d geometric collision functions like `hitTestRectangle`
  5969. or `rectangleCollision`
  5970. The `world` object requires these properties:
  5971. `x`, `y`, `tilewidth`, `tileheight` and `widthInTiles`
  5972. */
  5973. }, {
  5974. key: "getTile",
  5975. value: function getTile(index, mapArray, world) {
  5976. var tile = {};
  5977. tile.gid = mapArray[index];
  5978. tile.width = world.tilewidth;
  5979. tile.height = world.tileheight;
  5980. tile.halfWidth = world.tilewidth / 2;
  5981. tile.halfHeight = world.tileheight / 2;
  5982. tile.x = index % world.widthInTiles * world.tilewidth + world.x;
  5983. tile.y = Math.floor(index / world.widthInTiles) * world.tileheight + world.y;
  5984. tile.gx = tile.x;
  5985. tile.gy = tile.y;
  5986. tile.centerX = tile.x + world.tilewidth / 2;
  5987. tile.centery = tile.y + world.tileheight / 2;
  5988. //Return the tile object
  5989. return tile;
  5990. }
  5991. /*
  5992. #### surroundingCells
  5993. The `surroundingCells` helper method returns an array containing 9
  5994. index numbers of map array cells around any given index number.
  5995. Use it for an efficient broadphase/narrowphase collision test.
  5996. The 2 arguments are the index number that represents the center cell,
  5997. and the width of the map array.
  5998. */
  5999. }, {
  6000. key: "surroundingCells",
  6001. value: function surroundingCells(index, widthInTiles) {
  6002. return [index - widthInTiles - 1, index - widthInTiles, index - widthInTiles + 1, index - 1, index, index + 1, index + widthInTiles - 1, index + widthInTiles, index + widthInTiles + 1];
  6003. }
  6004. //#### getPoints
  6005. /*
  6006. The `getPoints` method takes a sprite and returns
  6007. an object that tells you what all its corner points are. The return
  6008. object has four properties, each of which is an object with `x` and `y` properties:
  6009. - `topLeft`: `x` and `y` properties describing the top left corner
  6010. point.
  6011. - `topRight`: `x` and `y` properties describing the top right corner
  6012. point.
  6013. - `bottomLeft`: `x` and `y` properties describing the bottom left corner
  6014. point.
  6015. - `bottomRight`: `x` and `y` properties describing the bottom right corner
  6016. point.
  6017. If the sprite has a `collisionArea` property that defines a
  6018. smaller rectangular area inside the sprite, that collision
  6019. area can be used instead for collisions instead of the sprite's dimensions. Here's
  6020. How you could define a `collsionArea` on a sprite called `elf`:
  6021. ```js
  6022. elf.collisionArea = {x: 22, y: 44, width: 20, height: 20};
  6023. ```
  6024. Here's how you could use the `getPoints` method to find all the collision area's corner points.
  6025. ```js
  6026. let cornerPoints = tu.getPoints(elf.collisionArea);
  6027. ```
  6028. */
  6029. }, {
  6030. key: "getPoints",
  6031. value: function getPoints(s) {
  6032. var ca = s.collisionArea;
  6033. if (ca !== undefined) {
  6034. return {
  6035. topLeft: {
  6036. x: s.x + ca.x,
  6037. y: s.y + ca.y
  6038. },
  6039. topRight: {
  6040. x: s.x + ca.x + ca.width,
  6041. y: s.y + ca.y
  6042. },
  6043. bottomLeft: {
  6044. x: s.x + ca.x,
  6045. y: s.y + ca.y + ca.height
  6046. },
  6047. bottomRight: {
  6048. x: s.x + ca.x + ca.width,
  6049. y: s.y + ca.y + ca.height
  6050. }
  6051. };
  6052. } else {
  6053. return {
  6054. topLeft: {
  6055. x: s.x,
  6056. y: s.y
  6057. },
  6058. topRight: {
  6059. x: s.x + s.width - 1,
  6060. y: s.y
  6061. },
  6062. bottomLeft: {
  6063. x: s.x,
  6064. y: s.y + s.height - 1
  6065. },
  6066. bottomRight: {
  6067. x: s.x + s.width - 1,
  6068. y: s.y + s.height - 1
  6069. }
  6070. };
  6071. }
  6072. }
  6073. //### hitTestTile
  6074. /*
  6075. `hitTestTile` checks for a
  6076. collision between a sprite and a tile in any map array that you
  6077. specify. It returns a `collision` object.
  6078. `collision.hit` is a Boolean that tells you if a sprite is colliding
  6079. with the tile that you're checking. `collision.index` tells you the
  6080. map array's index number of the colliding sprite. You can check for
  6081. a collision with the tile against "every" corner point on the
  6082. sprite, "some" corner points, or the sprite's "center" point.
  6083. `hitTestTile` arguments:
  6084. sprite, array, collisionTileGridIdNumber, worldObject, spritesPointsToCheck
  6085. ```js
  6086. tu.hitTestTile(sprite, array, collisioGid, world, pointsToCheck);
  6087. ```
  6088. The `world` object (the 4th argument) has to have these properties:
  6089. `tileheight`, `tilewidth`, `widthInTiles`.
  6090. Here's how you could use `hitTestTile` to check for a collision between a sprite
  6091. called `alien` and an array of wall sprites with map gid numbers of 0.
  6092. ```js
  6093. let alienVsFloor = g.hitTestTile(alien, wallMapArray, 0, world, "every");
  6094. ```
  6095. */
  6096. }, {
  6097. key: "hitTestTile",
  6098. value: function hitTestTile(sprite, mapArray, gidToCheck, world, pointsToCheck) {
  6099. var _this = this;
  6100. //The `checkPoints` helper function Loop through the sprite's corner points to
  6101. //find out if they are inside an array cell that you're interested in.
  6102. //Return `true` if they are
  6103. var checkPoints = function checkPoints(key) {
  6104. //Get a reference to the current point to check.
  6105. //(`topLeft`, `topRight`, `bottomLeft` or `bottomRight` )
  6106. var point = sprite.collisionPoints[key];
  6107. //Find the point's index number in the map array
  6108. collision.index = _this.getIndex(point.x, point.y, world.tilewidth, world.tileheight, world.widthInTiles);
  6109. //Find out what the gid value is in the map position
  6110. //that the point is currently over
  6111. collision.gid = mapArray[collision.index];
  6112. //If it matches the value of the gid that we're interested, in
  6113. //then there's been a collision
  6114. if (collision.gid === gidToCheck) {
  6115. return true;
  6116. } else {
  6117. return false;
  6118. }
  6119. };
  6120. //Assign "some" as the default value for `pointsToCheck`
  6121. pointsToCheck = pointsToCheck || "some";
  6122. //The collision object that will be returned by this function
  6123. var collision = {};
  6124. //Which points do you want to check?
  6125. //"every", "some" or "center"?
  6126. switch (pointsToCheck) {
  6127. case "center":
  6128. //`hit` will be true only if the center point is touching
  6129. var point = {
  6130. center: {
  6131. x: sprite.centerX,
  6132. y: sprite.centerY
  6133. }
  6134. };
  6135. sprite.collisionPoints = point;
  6136. collision.hit = Object.keys(sprite.collisionPoints).some(checkPoints);
  6137. break;
  6138. case "every":
  6139. //`hit` will be true if every point is touching
  6140. sprite.collisionPoints = this.getPoints(sprite);
  6141. collision.hit = Object.keys(sprite.collisionPoints).every(checkPoints);
  6142. break;
  6143. case "some":
  6144. //`hit` will be true only if some points are touching
  6145. sprite.collisionPoints = this.getPoints(sprite);
  6146. collision.hit = Object.keys(sprite.collisionPoints).some(checkPoints);
  6147. break;
  6148. }
  6149. //Return the collision object.
  6150. //`collision.hit` will be true if a collision is detected.
  6151. //`collision.index` tells you the map array index number where the
  6152. //collision occured
  6153. return collision;
  6154. }
  6155. //### updateMap
  6156. /*
  6157. `updateMap` takes a map array and adds a sprite's grid index number (`gid`) to it.
  6158. It finds the sprite's new index position, and retuns the new map array.
  6159. You can use it to do very efficient collision detection in tile based game worlds.
  6160. `updateMap` arguments:
  6161. array, singleSpriteOrArrayOfSprites, worldObject
  6162. The `world` object (the 4th argument) has to have these properties:
  6163. `tileheight`, `tilewidth`, `widthInTiles`.
  6164. The sprite objects have to have have these properties:
  6165. `centerX`, `centerY`, `index`, `gid` (The number in the array that represpents the sprite)
  6166. Here's an example of how you could use `updateMap` in your game code like this:
  6167. blockLayer.data = updateMap(blockLayer.data, blockLayer.children, world);
  6168. The `blockLayer.data` array would now contain the new index position numbers of all the
  6169. child sprites on that layer.
  6170. */
  6171. }, {
  6172. key: "updateMap",
  6173. value: function updateMap(mapArray, spritesToUpdate, world) {
  6174. var _this2 = this;
  6175. //First create a map a new array filled with zeros.
  6176. //The new map array will be exactly the same size as the original
  6177. var newMapArray = mapArray.map(function (gid) {
  6178. gid = 0;
  6179. return gid;
  6180. });
  6181. //Is `spriteToUpdate` an array of sprites?
  6182. if (spritesToUpdate instanceof Array) {
  6183. (function () {
  6184. //Get the index number of each sprite in the `spritesToUpdate` array
  6185. //and add the sprite's `gid` to the matching index on the map
  6186. var self = _this2;
  6187. spritesToUpdate.forEach(function (sprite) {
  6188. //Find the new index number
  6189. sprite.index = self.getIndex(sprite.centerX, sprite.centerY, world.tilewidth, world.tileheight, world.widthInTiles);
  6190. //Add the sprite's `gid` number to the correct index on the map
  6191. newMapArray[sprite.index] = sprite.gid;
  6192. });
  6193. })();
  6194. }
  6195. //Is `spritesToUpdate` just a single sprite?
  6196. else {
  6197. var sprite = spritesToUpdate;
  6198. //Find the new index number
  6199. sprite.index = this.getIndex(sprite.centerX, sprite.centerY, world.tilewidth, world.tileheight, world.widthInTiles);
  6200. //Add the sprite's `gid` number to the correct index on the map
  6201. newMapArray[sprite.index] = sprite.gid;
  6202. }
  6203. //Return the new map array to replace the previous one
  6204. return newMapArray;
  6205. }
  6206. /*
  6207. ###makeTiledWorld
  6208. `makeTiledWorld` is a quick and easy way to display a game world designed in
  6209. Tiled Editor. Supply `makeTiledWorld` with 2 **string arguments**:
  6210. 1. A JSON file generated by Tiled Editor.
  6211. 2. A source image that represents the tile set you used to create the Tiled Editor world.
  6212. ```js
  6213. let world = makeTiledWorld("tiledEditorMapData.json", "tileset.png");
  6214. ```
  6215. (Note: `makeTiledWorld` looks for the JSON data file in Pixi's `loader.resources` object. So,
  6216. make sure you've loaded the JSON file using Pixi's `loader`.)
  6217. `makeTiledWorld` will return a Pixi `Container` that contains all the things in your Tiled Editor
  6218. map as Pixi sprites.
  6219. All the image tiles you create in Tiled Editor are automatically converted into Pixi sprites
  6220. for you by `makeTiledWorld`. You can access all of them using two methods: `getObject` (for
  6221. single sprites) and `getObjects` (with an "s") for multiple sprites. Let's find out how they work.
  6222. ####world.getObject
  6223. Tile Editor lets you assign a "name" properties any object.
  6224. You can access any sprite by this name using the `getObject` method. `getObject` searches for and
  6225. returns a sprite in the `world` that has the same `name` property that you assigned
  6226. in Tiled Editor. Here's how to use `getObject` to look for an object called "alien"
  6227. in the Tiled map data and assign it to a variable called `alien`
  6228. ```js
  6229. let alien = world.getObject("alien");
  6230. ```
  6231. `alien` is now an ordinary Pixi sprite that you can control just like any other Pixi
  6232. sprite in your games.
  6233. #### Creating sprites from generic objects
  6234. Tiled Editor lets you create generic objects. These are objects that don't have images associated
  6235. with them. Generic objects are handy to use, because they let you create complex game objects inside
  6236. Tiled Editor, as pure data. You can then use that data your game code to build complex game objects.
  6237. For example, imagine that you want to create a complex animated walking sprite called "elf".
  6238. First, create the elf object in Tiled Editor as a generic object, but don't assign any image tiles
  6239. to it. Next, in your game code, create a new Pixi MovieClip called `elf` and give it any textures you want
  6240. to use for its animation states.
  6241. ```js
  6242. //Create a new Pixi MovieClip sprite
  6243. let elf = new PIXI.MovieClip(elfSpriteTextures);
  6244. ```
  6245. Then use the `x` and `y` data from the generic "elf" object you created in Tiled Editor to position the
  6246. `elf` sprite.
  6247. ```js
  6248. elf.x = world.getObject("elf").x;
  6249. elf.y = world.getObject("elf").y;
  6250. ```
  6251. This is a simple example, but you could make very complex data objects in Tiled Editor and
  6252. use them to build complex sprites in the same way.
  6253. ####Accessing Tiled Editor layer groups
  6254. Tiled Editor lets you create **layer groups**. Each layer group you create
  6255. in Tiled Editor is automatically converted by `makeTiledWorld` into a Pixi `Container`
  6256. object. You can access those containers using `getObject` to extract the layer group
  6257. container.
  6258. Here's how you could extract the layer group called "objects" and add the
  6259. `elf` sprite to it.
  6260. ```js
  6261. let objectsLayer = world.getObject("objects");
  6262. objectsLayer.addChild(elf);
  6263. ```
  6264. If you want to add the sprite to a different world layer, you can do it like this:
  6265. ```js
  6266. world.getObject("treeTops").addChild(elf);
  6267. ```
  6268. If you want to access all the sprites in a specific Tiled Editor layer, just supply
  6269. `getObject` with the name of the layer. For example, if the layer name is "items", you
  6270. can access it like this:
  6271. ```js
  6272. let itemsLayer = world.getObject("items");
  6273. ```
  6274. `itemsLayer` is now a Pixi container with a `children` array that contains all the sprites
  6275. on that layer.
  6276. To be safe, clone this array to create a new version
  6277. that doesn't point to the original data file:
  6278. ```js
  6279. items = itemsLayer.children.slice(0);
  6280. ```
  6281. You can now manipulate the `items` array freely without worrying about changing
  6282. the original array. This can possibly help prevent some weird bugs in a complex game.
  6283. ###Finding the "gid" values
  6284. Tiled Editor uses "gid" numbers to identify different kinds of things in the world.
  6285. If you ever need to extract sprites with specific `gid` numbers in a
  6286. layer that contains different kinds of things, you can do it like this:
  6287. ```js
  6288. let items = itemsLayer.children.map(sprite => {
  6289. if (sprite.gid !== 0) return sprite;
  6290. });
  6291. ```
  6292. Every sprite created by `makeTiledWorld` has a `gid` property with a value that matches its
  6293. Tiled Editor "gid" value.
  6294. ####Accessing a layer's "data" array
  6295. Tiled Editor's layers have a `data` property
  6296. that is an array containing all the grid index numbers (`gid`) of
  6297. the tiles in that array. Imagine that you've got a layer full of similar
  6298. tiles representing the walls in a game. How do you access the array
  6299. containing all the "gid" numbers of the wall sprites in that layer? If the layer's name is called "wallLayer", you
  6300. can access the `wallLayer`'s `data` array of sprites like this:
  6301. ```js
  6302. wallMapArray = world.getObject("wallLayer").data;
  6303. ```
  6304. `wallMapArray` is now an array of "gid" numbers referring to all the sprites on that
  6305. layer. You can now use this data for collision detection, or doing any other kind
  6306. of world building.
  6307. ###world.getObjects
  6308. There's another method called `getObjects` (with an "s"!) that lets you extract
  6309. an array of sprites from the Tiled Editor data. Imagine that you created three
  6310. game objects in Tiled Editor called "marmot", "skull" and "heart". `makeTiledWorld`
  6311. automatically turns them into sprites, and you can access
  6312. all of them as array of sprites using `getObjects` like this:
  6313. ```js
  6314. let gameItemsArray = world.getObjects("marmot", "skull", "heart");
  6315. ```
  6316. */
  6317. }, {
  6318. key: "makeTiledWorld",
  6319. value: function makeTiledWorld(jsonTiledMap, tileset) {
  6320. var _this3 = this;
  6321. //Create a group called `world` to contain all the layers, sprites
  6322. //and objects from the `tiledMap`. The `world` object is going to be
  6323. //returned to the main game program
  6324. var tiledMap = PIXI.loader.resources[jsonTiledMap].data;
  6325. var world = new this.Container();
  6326. world.tileheight = tiledMap.tileheight;
  6327. world.tilewidth = tiledMap.tilewidth;
  6328. //Calculate the `width` and `height` of the world, in pixels
  6329. world.worldWidth = tiledMap.width * tiledMap.tilewidth;
  6330. world.worldHeight = tiledMap.height * tiledMap.tileheight;
  6331. //Get a reference to the world's height and width in
  6332. //tiles, in case you need to know this later (you will!)
  6333. world.widthInTiles = tiledMap.width;
  6334. world.heightInTiles = tiledMap.height;
  6335. //Create an `objects` array to store references to any
  6336. //named objects in the map. Named objects all have
  6337. //a `name` property that was assigned in Tiled Editor
  6338. world.objects = [];
  6339. //The optional spacing (padding) around each tile
  6340. //This is to account for spacing around tiles
  6341. //that's commonly used with texture atlas tilesets. Set the
  6342. //`spacing` property when you create a new map in Tiled Editor
  6343. var spacing = tiledMap.tilesets[0].spacing;
  6344. //Figure out how many columns there are on the tileset.
  6345. //This is the width of the image, divided by the width
  6346. //of each tile, plus any optional spacing thats around each tile
  6347. var numberOfTilesetColumns = Math.floor(tiledMap.tilesets[0].imagewidth / (tiledMap.tilewidth + spacing));
  6348. //Loop through all the map layers
  6349. tiledMap.layers.forEach(function (tiledLayer) {
  6350. //Make a group for this layer and copy
  6351. //all of the layer properties onto it.
  6352. var layerGroup = new _this3.Container();
  6353. Object.keys(tiledLayer).forEach(function (key) {
  6354. //Add all the layer's properties to the group, except the
  6355. //width and height (because the group will work those our for
  6356. //itself based on its content).
  6357. if (key !== "width" && key !== "height") {
  6358. layerGroup[key] = tiledLayer[key];
  6359. }
  6360. });
  6361. //Set the width and height of the layer to
  6362. //the `world`'s width and height
  6363. //layerGroup.width = world.width;
  6364. //layerGroup.height = world.height;
  6365. //Translate `opacity` to `alpha`
  6366. layerGroup.alpha = tiledLayer.opacity;
  6367. //Add the group to the `world`
  6368. world.addChild(layerGroup);
  6369. //Push the group into the world's `objects` array
  6370. //So you can access it later
  6371. world.objects.push(layerGroup);
  6372. //Is this current layer a `tilelayer`?
  6373. if (tiledLayer.type === "tilelayer") {
  6374. //Loop through the `data` array of this layer
  6375. tiledLayer.data.forEach(function (gid, index) {
  6376. var tileSprite = undefined,
  6377. texture = undefined,
  6378. mapX = undefined,
  6379. mapY = undefined,
  6380. tilesetX = undefined,
  6381. tilesetY = undefined,
  6382. mapColumn = undefined,
  6383. mapRow = undefined,
  6384. tilesetColumn = undefined,
  6385. tilesetRow = undefined;
  6386. //If the grid id number (`gid`) isn't zero, create a sprite
  6387. if (gid !== 0) {
  6388. (function () {
  6389. //Figure out the map column and row number that we're on, and then
  6390. //calculate the grid cell's x and y pixel position.
  6391. mapColumn = index % world.widthInTiles;
  6392. mapRow = Math.floor(index / world.widthInTiles);
  6393. mapX = mapColumn * world.tilewidth;
  6394. mapY = mapRow * world.tileheight;
  6395. //Figure out the column and row number that the tileset
  6396. //image is on, and then use those values to calculate
  6397. //the x and y pixel position of the image on the tileset
  6398. tilesetColumn = (gid - 1) % numberOfTilesetColumns;
  6399. tilesetRow = Math.floor((gid - 1) / numberOfTilesetColumns);
  6400. tilesetX = tilesetColumn * world.tilewidth;
  6401. tilesetY = tilesetRow * world.tileheight;
  6402. //Compensate for any optional spacing (padding) around the tiles if
  6403. //there is any. This bit of code accumlates the spacing offsets from the
  6404. //left side of the tileset and adds them to the current tile's position
  6405. if (spacing > 0) {
  6406. tilesetX += spacing + spacing * ((gid - 1) % numberOfTilesetColumns);
  6407. tilesetY += spacing + spacing * Math.floor((gid - 1) / numberOfTilesetColumns);
  6408. }
  6409. //Use the above values to create the sprite's image from
  6410. //the tileset image
  6411. texture = _this3.frame(tileset, tilesetX, tilesetY, world.tilewidth, world.tileheight);
  6412. //I've dedcided that any tiles that have a `name` property are important
  6413. //and should be accessible in the `world.objects` array.
  6414. var tileproperties = tiledMap.tilesets[0].tileproperties,
  6415. key = String(gid - 1);
  6416. //If the JSON `tileproperties` object has a sub-object that
  6417. //matches the current tile, and that sub-object has a `name` property,
  6418. //then create a sprite and assign the tile properties onto
  6419. //the sprite
  6420. if (tileproperties[key] && tileproperties[key].name) {
  6421. //Make a sprite
  6422. tileSprite = new _this3.Sprite(texture);
  6423. //Copy all of the tile's properties onto the sprite
  6424. //(This includes the `name` property)
  6425. Object.keys(tileproperties[key]).forEach(function (property) {
  6426. //console.log(tileproperties[key][property])
  6427. tileSprite[property] = tileproperties[key][property];
  6428. });
  6429. //Push the sprite into the world's `objects` array
  6430. //so that you can access it by `name` later
  6431. world.objects.push(tileSprite);
  6432. }
  6433. //If the tile doesn't have a `name` property, just use it to
  6434. //create an ordinary sprite (it will only need one texture)
  6435. else {
  6436. tileSprite = new _this3.Sprite(texture);
  6437. }
  6438. //Position the sprite on the map
  6439. tileSprite.x = mapX;
  6440. tileSprite.y = mapY;
  6441. //Make a record of the sprite's index number in the array
  6442. //(We'll use this for collision detection later)
  6443. tileSprite.index = index;
  6444. //Make a record of the sprite's `gid` on the tileset.
  6445. //This will also be useful for collision detection later
  6446. tileSprite.gid = gid;
  6447. //Add the sprite to the current layer group
  6448. layerGroup.addChild(tileSprite);
  6449. })();
  6450. }
  6451. });
  6452. }
  6453. //Is this layer an `objectgroup`?
  6454. if (tiledLayer.type === "objectgroup") {
  6455. tiledLayer.objects.forEach(function (object) {
  6456. //We're just going to capture the object's properties
  6457. //so that we can decide what to do with it later
  6458. //Get a reference to the layer group the object is in
  6459. object.group = layerGroup;
  6460. //Because this is an object layer, it doesn't contain any
  6461. //sprites, just data object. That means it won't be able to
  6462. //calucalte its own height and width. To help it out, give
  6463. //the `layerGroup` the same `width` and `height` as the `world`
  6464. //layerGroup.width = world.width;
  6465. //layerGroup.height = world.height;
  6466. //Push the object into the world's `objects` array
  6467. world.objects.push(object);
  6468. });
  6469. }
  6470. });
  6471. //Search functions
  6472. //`world.getObject` and `world.getObjects` search for and return
  6473. //any sprites or objects in the `world.objects` array.
  6474. //Any object that has a `name` propery in
  6475. //Tiled Editor will show up in a search.
  6476. //`getObject` gives you a single object, `getObjects` gives you an array
  6477. //of objects.
  6478. //`getObject` returns the actual search function, so you
  6479. //can use the following format to directly access a single object:
  6480. //sprite.x = world.getObject("anySprite").x;
  6481. //sprite.y = world.getObject("anySprite").y;
  6482. world.getObject = function (objectName) {
  6483. var searchForObject = function searchForObject() {
  6484. var foundObject = undefined;
  6485. world.objects.some(function (object) {
  6486. if (object.name && object.name === objectName) {
  6487. foundObject = object;
  6488. return true;
  6489. }
  6490. });
  6491. if (foundObject) {
  6492. return foundObject;
  6493. } else {
  6494. throw new Error("There is no object with the property name: " + objectName);
  6495. }
  6496. };
  6497. //Return the search function
  6498. return searchForObject();
  6499. };
  6500. world.getObjects = function (objectNames) {
  6501. var foundObjects = [];
  6502. world.objects.forEach(function (object) {
  6503. if (object.name && objectNames.indexOf(object.name) !== -1) {
  6504. foundObjects.push(object);
  6505. }
  6506. });
  6507. if (foundObjects.length > 0) {
  6508. return foundObjects;
  6509. } else {
  6510. throw new Error("I could not find those objects");
  6511. }
  6512. return foundObjects;
  6513. };
  6514. //That's it, we're done!
  6515. //Finally, return the `world` object back to the game program
  6516. return world;
  6517. }
  6518. /* Isometric tile utilities */
  6519. /*
  6520. ### byDepth
  6521. And array `sort` function that depth-sorts sprites according to
  6522. their `z` properties
  6523. */
  6524. }, {
  6525. key: "byDepth",
  6526. value: function byDepth(a, b) {
  6527. //Calculate the depths of `a` and `b`
  6528. //(add `1` to `a.z` and `b.x` to avoid multiplying by 0)
  6529. a.depth = (a.cartX + a.cartY) * (a.z + 1);
  6530. b.depth = (b.cartX + b.cartY) * (b.z + 1);
  6531. //Move sprites with a lower depth to a higher position in the array
  6532. if (a.depth < b.depth) {
  6533. return -1;
  6534. } else if (a.depth > b.depth) {
  6535. return 1;
  6536. } else {
  6537. return 0;
  6538. }
  6539. }
  6540. /*
  6541. ### hitTestIsoTile
  6542. Same API as `hitTestTile`, except that it works with isometric sprites.
  6543. Make sure that your `world` object has properties called
  6544. `cartTileWidth` and `cartTileHeight` that define the Cartesian with and
  6545. height of your tile cells, in pixels.
  6546. */
  6547. }, {
  6548. key: "hitTestIsoTile",
  6549. value: function hitTestIsoTile(sprite, mapArray, gidToCheck, world, pointsToCheck) {
  6550. var _this4 = this;
  6551. //The `checkPoints` helper function Loop through the sprite's corner points to
  6552. //find out if they are inside an array cell that you're interested in.
  6553. //Return `true` if they are
  6554. var checkPoints = function checkPoints(key) {
  6555. //Get a reference to the current point to check.
  6556. //(`topLeft`, `topRight`, `bottomLeft` or `bottomRight` )
  6557. var point = sprite.collisionPoints[key];
  6558. //Find the point's index number in the map array
  6559. collision.index = _this4.getIndex(point.x, point.y, world.cartTilewidth, world.cartTileheight, world.widthInTiles);
  6560. //Find out what the gid value is in the map position
  6561. //that the point is currently over
  6562. collision.gid = mapArray[collision.index];
  6563. //If it matches the value of the gid that we're interested, in
  6564. //then there's been a collision
  6565. if (collision.gid === gidToCheck) {
  6566. return true;
  6567. } else {
  6568. return false;
  6569. }
  6570. };
  6571. //Assign "some" as the default value for `pointsToCheck`
  6572. pointsToCheck = pointsToCheck || "some";
  6573. //The collision object that will be returned by this function
  6574. var collision = {};
  6575. //Which points do you want to check?
  6576. //"every", "some" or "center"?
  6577. switch (pointsToCheck) {
  6578. case "center":
  6579. //`hit` will be true only if the center point is touching
  6580. var point = {
  6581. center: {
  6582. //x: sprite.centerX,
  6583. //y: sprite.centerY
  6584. x: s.cartX + ca.x + ca.width / 2,
  6585. y: s.cartY + ca.y + ca.height / 2
  6586. }
  6587. };
  6588. sprite.collisionPoints = point;
  6589. collision.hit = Object.keys(sprite.collisionPoints).some(checkPoints);
  6590. break;
  6591. case "every":
  6592. //`hit` will be true if every point is touching
  6593. sprite.collisionPoints = this.getIsoPoints(sprite);
  6594. collision.hit = Object.keys(sprite.collisionPoints).every(checkPoints);
  6595. break;
  6596. case "some":
  6597. //`hit` will be true only if some points are touching
  6598. sprite.collisionPoints = this.getIsoPoints(sprite);
  6599. collision.hit = Object.keys(sprite.collisionPoints).some(checkPoints);
  6600. break;
  6601. }
  6602. //Return the collision object.
  6603. //`collision.hit` will be true if a collision is detected.
  6604. //`collision.index` tells you the map array index number where the
  6605. //collision occured
  6606. return collision;
  6607. }
  6608. /*
  6609. ### getIsoPoints
  6610. The isomertic version of `getPoints`
  6611. */
  6612. }, {
  6613. key: "getIsoPoints",
  6614. value: function getIsoPoints(s) {
  6615. var ca = s.collisionArea;
  6616. if (ca !== undefined) {
  6617. return {
  6618. topLeft: {
  6619. x: s.cartX + ca.x,
  6620. y: s.cartY + ca.y
  6621. },
  6622. topRight: {
  6623. x: s.cartX + ca.x + ca.width,
  6624. y: s.cartY + ca.y
  6625. },
  6626. bottomLeft: {
  6627. x: s.cartX + ca.x,
  6628. y: s.cartY + ca.y + ca.height
  6629. },
  6630. bottomRight: {
  6631. x: s.cartX + ca.x + ca.width,
  6632. y: s.cartY + ca.y + ca.height
  6633. }
  6634. };
  6635. } else {
  6636. return {
  6637. topLeft: {
  6638. x: s.cartX,
  6639. y: s.cartY
  6640. },
  6641. topRight: {
  6642. x: s.cartX + s.cartWidth - 1,
  6643. y: s.cartY
  6644. },
  6645. bottomLeft: {
  6646. x: s.cartX,
  6647. y: s.cartY + s.cartHeight - 1
  6648. },
  6649. bottomRight: {
  6650. x: s.cartX + s.cartWidth - 1,
  6651. y: s.cartY + s.cartHeight - 1
  6652. }
  6653. };
  6654. }
  6655. }
  6656. /*
  6657. ### makeIsoPointer
  6658. Used to add a isometric properties to any mouse/touch `pointer` object with
  6659. `x` and `y` properties. Supply `makeIsoPointer` with the pointer object and
  6660. the isometric `world` object
  6661. */
  6662. //Create some useful properties on the pointer
  6663. }, {
  6664. key: "makeIsoPointer",
  6665. value: function makeIsoPointer(pointer, world) {
  6666. Object.defineProperties(pointer, {
  6667. //The isometric's world's Cartesian coordiantes
  6668. cartX: {
  6669. get: function get() {
  6670. var x = (2 * this.y + this.x - (2 * world.y + world.x)) / 2 - world.cartTilewidth / 2;
  6671. return x;
  6672. },
  6673. enumerable: true,
  6674. configurable: true
  6675. },
  6676. cartY: {
  6677. get: function get() {
  6678. var y = (2 * this.y - this.x - (2 * world.y - world.x)) / 2 + world.cartTileheight / 2;
  6679. return y;
  6680. },
  6681. enumerable: true,
  6682. configurable: true
  6683. },
  6684. //The tile's column and row in the array
  6685. column: {
  6686. get: function get() {
  6687. return Math.floor(this.cartX / world.cartTilewidth);
  6688. },
  6689. enumerable: true,
  6690. configurable: true
  6691. },
  6692. row: {
  6693. get: function get() {
  6694. return Math.floor(this.cartY / world.cartTileheight);
  6695. },
  6696. enumerable: true,
  6697. configurable: true
  6698. },
  6699. //The tile's index number in the array
  6700. index: {
  6701. get: function get() {
  6702. var index = {};
  6703. //Convert pixel coordinates to map index coordinates
  6704. index.x = Math.floor(this.cartX / world.cartTilewidth);
  6705. index.y = Math.floor(this.cartY / world.cartTileheight);
  6706. //Return the index number
  6707. return index.x + index.y * world.widthInTiles;
  6708. },
  6709. enumerable: true,
  6710. configurable: true
  6711. }
  6712. });
  6713. }
  6714. /*
  6715. ### isoRectangle
  6716. A function for creating a simple isometric diamond
  6717. shaped rectangle using Pixi's graphics library
  6718. */
  6719. }, {
  6720. key: "isoRectangle",
  6721. value: function isoRectangle(width, height, fillStyle) {
  6722. //Figure out the `halfHeight` value
  6723. var halfHeight = height / 2;
  6724. //Draw the flattened and rotated square (diamond shape)
  6725. var rectangle = new this.Graphics();
  6726. rectangle.beginFill(fillStyle);
  6727. rectangle.moveTo(0, 0);
  6728. rectangle.lineTo(width, halfHeight);
  6729. rectangle.lineTo(0, height);
  6730. rectangle.lineTo(-width, halfHeight);
  6731. rectangle.lineTo(0, 0);
  6732. rectangle.endFill();
  6733. //Generate a texture from the rectangle
  6734. var texture = rectangle.generateTexture();
  6735. //Use the texture to create a sprite
  6736. var sprite = new this.Sprite(texture);
  6737. //Return it to the main program
  6738. return sprite;
  6739. }
  6740. /*
  6741. ### addIsoProperties
  6742. Add properties to a sprite to help work between Cartesian
  6743. and isometric properties: `isoX`, `isoY`, `cartX`,
  6744. `cartWidth` and `cartHeight`.
  6745. */
  6746. }, {
  6747. key: "addIsoProperties",
  6748. value: function addIsoProperties(sprite, x, y, width, height) {
  6749. //Cartisian (flat 2D) properties
  6750. sprite.cartX = x;
  6751. sprite.cartY = y;
  6752. sprite.cartWidth = width;
  6753. sprite.cartHeight = height;
  6754. //Add a getter/setter for the isometric properties
  6755. Object.defineProperties(sprite, {
  6756. isoX: {
  6757. get: function get() {
  6758. return this.cartX - this.cartY;
  6759. },
  6760. enumerable: true,
  6761. configurable: true
  6762. },
  6763. isoY: {
  6764. get: function get() {
  6765. return (this.cartX + this.cartY) / 2;
  6766. },
  6767. enumerable: true,
  6768. configurable: true
  6769. }
  6770. });
  6771. }
  6772. /*
  6773. ### makeIsoTiledWorld
  6774. Make an isometric world from TiledEditor map data. Uses the same API as `makeTiledWorld`
  6775. */
  6776. }, {
  6777. key: "makeIsoTiledWorld",
  6778. value: function makeIsoTiledWorld(jsonTiledMap, tileset) {
  6779. var _this5 = this;
  6780. //Create a group called `world` to contain all the layers, sprites
  6781. //and objects from the `tiledMap`. The `world` object is going to be
  6782. //returned to the main game program
  6783. var tiledMap = PIXI.loader.resources[jsonTiledMap].data;
  6784. //A. You need to add three custom properties to your Tiled Editor
  6785. //map: `cartTilewidth`,`cartTileheight` and `tileDepth`. They define the Cartesian
  6786. //dimesions of the tiles (32x32x64).
  6787. //Check to make sure that these custom properties exist
  6788. if (!tiledMap.properties.cartTilewidth && !tiledMap.properties.cartTileheight && !tiledMao.properties.tileDepth) {
  6789. throw new Error("Set custom cartTilewidth, cartTileheight and tileDepth map properties in Tiled Editor");
  6790. }
  6791. //Create the `world` container
  6792. var world = new this.Container();
  6793. //B. Set the `tileHeight` to the `tiledMap`'s `tileDepth` property
  6794. //so that it matches the pixel height of the sprite tile image
  6795. world.tileheight = parseInt(tiledMap.properties.tileDepth);
  6796. world.tilewidth = tiledMap.tilewidth;
  6797. //C. Define the Cartesian dimesions of each tile
  6798. world.cartTileheight = parseInt(tiledMap.properties.cartTileheight);
  6799. world.cartTilewidth = parseInt(tiledMap.properties.cartTilewidth);
  6800. //D. Calculate the `width` and `height` of the world, in pixels
  6801. //using the `world.cartTileHeight` and `world.cartTilewidth`
  6802. //values
  6803. world.worldWidth = tiledMap.width * world.cartTilewidth;
  6804. world.worldHeight = tiledMap.height * world.cartTileheight;
  6805. //Get a reference to the world's height and width in
  6806. //tiles, in case you need to know this later (you will!)
  6807. world.widthInTiles = tiledMap.width;
  6808. world.heightInTiles = tiledMap.height;
  6809. //Create an `objects` array to store references to any
  6810. //named objects in the map. Named objects all have
  6811. //a `name` property that was assigned in Tiled Editor
  6812. world.objects = [];
  6813. //The optional spacing (padding) around each tile
  6814. //This is to account for spacing around tiles
  6815. //that's commonly used with texture atlas tilesets. Set the
  6816. //`spacing` property when you create a new map in Tiled Editor
  6817. var spacing = tiledMap.tilesets[0].spacing;
  6818. //Figure out how many columns there are on the tileset.
  6819. //This is the width of the image, divided by the width
  6820. //of each tile, plus any optional spacing thats around each tile
  6821. var numberOfTilesetColumns = Math.floor(tiledMap.tilesets[0].imagewidth / (tiledMap.tilewidth + spacing));
  6822. //E. A `z` property to help track which depth level the sprites are on
  6823. var z = 0;
  6824. //Loop through all the map layers
  6825. tiledMap.layers.forEach(function (tiledLayer) {
  6826. //Make a group for this layer and copy
  6827. //all of the layer properties onto it.
  6828. var layerGroup = new _this5.Container();
  6829. Object.keys(tiledLayer).forEach(function (key) {
  6830. //Add all the layer's properties to the group, except the
  6831. //width and height (because the group will work those our for
  6832. //itself based on its content).
  6833. if (key !== "width" && key !== "height") {
  6834. layerGroup[key] = tiledLayer[key];
  6835. }
  6836. });
  6837. //Translate `opacity` to `alpha`
  6838. layerGroup.alpha = tiledLayer.opacity;
  6839. //Add the group to the `world`
  6840. world.addChild(layerGroup);
  6841. //Push the group into the world's `objects` array
  6842. //So you can access it later
  6843. world.objects.push(layerGroup);
  6844. //Is this current layer a `tilelayer`?
  6845. if (tiledLayer.type === "tilelayer") {
  6846. //Loop through the `data` array of this layer
  6847. tiledLayer.data.forEach(function (gid, index) {
  6848. var tileSprite = undefined,
  6849. texture = undefined,
  6850. mapX = undefined,
  6851. mapY = undefined,
  6852. tilesetX = undefined,
  6853. tilesetY = undefined,
  6854. mapColumn = undefined,
  6855. mapRow = undefined,
  6856. tilesetColumn = undefined,
  6857. tilesetRow = undefined;
  6858. //If the grid id number (`gid`) isn't zero, create a sprite
  6859. if (gid !== 0) {
  6860. (function () {
  6861. //Figure out the map column and row number that we're on, and then
  6862. //calculate the grid cell's x and y pixel position.
  6863. mapColumn = index % world.widthInTiles;
  6864. mapRow = Math.floor(index / world.widthInTiles);
  6865. //F. Use the Cartesian values to find the
  6866. //`mapX` and `mapY` values
  6867. mapX = mapColumn * world.cartTilewidth;
  6868. mapY = mapRow * world.cartTileheight;
  6869. //Figure out the column and row number that the tileset
  6870. //image is on, and then use those values to calculate
  6871. //the x and y pixel position of the image on the tileset
  6872. tilesetColumn = (gid - 1) % numberOfTilesetColumns;
  6873. tilesetRow = Math.floor((gid - 1) / numberOfTilesetColumns);
  6874. tilesetX = tilesetColumn * world.tilewidth;
  6875. tilesetY = tilesetRow * world.tileheight;
  6876. //Compensate for any optional spacing (padding) around the tiles if
  6877. //there is any. This bit of code accumlates the spacing offsets from the
  6878. //left side of the tileset and adds them to the current tile's position
  6879. if (spacing > 0) {
  6880. tilesetX += spacing + spacing * ((gid - 1) % numberOfTilesetColumns);
  6881. tilesetY += spacing + spacing * Math.floor((gid - 1) / numberOfTilesetColumns);
  6882. }
  6883. //Use the above values to create the sprite's image from
  6884. //the tileset image
  6885. texture = _this5.frame(tileset, tilesetX, tilesetY, world.tilewidth, world.tileheight);
  6886. //I've dedcided that any tiles that have a `name` property are important
  6887. //and should be accessible in the `world.objects` array.
  6888. var tileproperties = tiledMap.tilesets[0].tileproperties,
  6889. key = String(gid - 1);
  6890. //If the JSON `tileproperties` object has a sub-object that
  6891. //matches the current tile, and that sub-object has a `name` property,
  6892. //then create a sprite and assign the tile properties onto
  6893. //the sprite
  6894. if (tileproperties[key] && tileproperties[key].name) {
  6895. //Make a sprite
  6896. tileSprite = new _this5.Sprite(texture);
  6897. //Copy all of the tile's properties onto the sprite
  6898. //(This includes the `name` property)
  6899. Object.keys(tileproperties[key]).forEach(function (property) {
  6900. //console.log(tileproperties[key][property])
  6901. tileSprite[property] = tileproperties[key][property];
  6902. });
  6903. //Push the sprite into the world's `objects` array
  6904. //so that you can access it by `name` later
  6905. world.objects.push(tileSprite);
  6906. }
  6907. //If the tile doesn't have a `name` property, just use it to
  6908. //create an ordinary sprite (it will only need one texture)
  6909. else {
  6910. tileSprite = new _this5.Sprite(texture);
  6911. }
  6912. //G. Add isometric properties to the sprite
  6913. _this5.addIsoProperties(tileSprite, mapX, mapY, world.cartTilewidth, world.cartTileheight);
  6914. //H. Use the isometric position to add the sprite to the world
  6915. tileSprite.x = tileSprite.isoX;
  6916. tileSprite.y = tileSprite.isoY;
  6917. tileSprite.z = z;
  6918. //Make a record of the sprite's index number in the array
  6919. //(We'll use this for collision detection later)
  6920. tileSprite.index = index;
  6921. //Make a record of the sprite's `gid` on the tileset.
  6922. //This will also be useful for collision detection later
  6923. tileSprite.gid = gid;
  6924. //Add the sprite to the current layer group
  6925. layerGroup.addChild(tileSprite);
  6926. })();
  6927. }
  6928. });
  6929. }
  6930. //Is this layer an `objectgroup`?
  6931. if (tiledLayer.type === "objectgroup") {
  6932. tiledLayer.objects.forEach(function (object) {
  6933. //We're just going to capture the object's properties
  6934. //so that we can decide what to do with it later
  6935. //Get a reference to the layer group the object is in
  6936. object.group = layerGroup;
  6937. //Push the object into the world's `objects` array
  6938. world.objects.push(object);
  6939. });
  6940. }
  6941. //I. Add 1 to the z index (the first layer will have a z index of `1`)
  6942. z += 1;
  6943. });
  6944. //Search functions
  6945. //`world.getObject` and `world.getObjects` search for and return
  6946. //any sprites or objects in the `world.objects` array.
  6947. //Any object that has a `name` propery in
  6948. //Tiled Editor will show up in a search.
  6949. //`getObject` gives you a single object, `getObjects` gives you an array
  6950. //of objects.
  6951. //`getObject` returns the actual search function, so you
  6952. //can use the following format to directly access a single object:
  6953. //sprite.x = world.getObject("anySprite").x;
  6954. //sprite.y = world.getObject("anySprite").y;
  6955. world.getObject = function (objectName) {
  6956. var searchForObject = function searchForObject() {
  6957. var foundObject = undefined;
  6958. world.objects.some(function (object) {
  6959. if (object.name && object.name === objectName) {
  6960. foundObject = object;
  6961. return true;
  6962. }
  6963. });
  6964. if (foundObject) {
  6965. return foundObject;
  6966. } else {
  6967. throw new Error("There is no object with the property name: " + objectName);
  6968. }
  6969. };
  6970. //Return the search function
  6971. return searchForObject();
  6972. };
  6973. world.getObjects = function (objectNames) {
  6974. var foundObjects = [];
  6975. world.objects.forEach(function (object) {
  6976. if (object.name && objectNames.indexOf(object.name) !== -1) {
  6977. foundObjects.push(object);
  6978. }
  6979. });
  6980. if (foundObjects.length > 0) {
  6981. return foundObjects;
  6982. } else {
  6983. throw new Error("I could not find those objects");
  6984. }
  6985. return foundObjects;
  6986. };
  6987. //That's it, we're done!
  6988. //Finally, return the `world` object back to the game program
  6989. return world;
  6990. }
  6991. /*
  6992. //### The `shortestPath` function
  6993. An A-Star search algorithm that returns an array of grid index numbers that
  6994. represent the shortest path between two points on a map. Use it like this:
  6995. let shortestPath = tu.shortestPath(
  6996. startIndex, //The start map index
  6997. destinationIndex, //The destination index
  6998. mapArray, //The map array
  6999. mapWidthInTiles, //Map wdith, in tiles
  7000. [1,2], //Obstacle gid array
  7001. "manhattan" //Heuristic to use: "manhatten", "euclidean" or "diagonal"
  7002. );
  7003. */
  7004. }, {
  7005. key: "shortestPath",
  7006. value: function shortestPath(startIndex, destinationIndex, mapArray, mapWidthInTiles) {
  7007. var obstacleGids = arguments.length <= 4 || arguments[4] === undefined ? [] : arguments[4];
  7008. var heuristic = arguments.length <= 5 || arguments[5] === undefined ? "manhattan" : arguments[5];
  7009. var useDiagonalNodes = arguments.length <= 6 || arguments[6] === undefined ? true : arguments[6];
  7010. //The `nodes` function creates the array of node objects
  7011. var nodes = function nodes(mapArray, mapWidthInTiles) {
  7012. return mapArray.map(function (cell, index) {
  7013. //Figure out the row and column of this cell
  7014. var column = index % mapWidthInTiles;
  7015. var row = Math.floor(index / mapWidthInTiles);
  7016. //The node object
  7017. return node = {
  7018. f: 0,
  7019. g: 0,
  7020. h: 0,
  7021. parent: null,
  7022. column: column,
  7023. row: row,
  7024. index: index
  7025. };
  7026. });
  7027. };
  7028. //Initialize theShortestPath array
  7029. var theShortestPath = [];
  7030. //Initialize the node map
  7031. var nodeMap = nodes(mapArray, mapWidthInTiles);
  7032. //Initialize the closed and open list arrays
  7033. var closedList = [];
  7034. var openList = [];
  7035. //Declare the "costs" of travelling in straight or
  7036. //diagonal lines
  7037. var straightCost = 10;
  7038. var diagonalCost = 14;
  7039. //Get the start node
  7040. var startNode = nodeMap[startIndex];
  7041. //Get the current center node. The first one will
  7042. //match the path's start position
  7043. var centerNode = startNode;
  7044. //Push the `centerNode` into the `openList`, because
  7045. //it's the first node that we're going to check
  7046. openList.push(centerNode);
  7047. //Get the current destination node. The first one will
  7048. //match the path's end position
  7049. var destinationNode = nodeMap[destinationIndex];
  7050. //All the nodes that are surrounding the current map index number
  7051. var surroundingNodes = function surroundingNodes(index, mapArray, mapWidthInTiles, useDiagonalNodes) {
  7052. //Find out what all the surrounding nodes are, including those that
  7053. //might be beyond the borders of the map
  7054. var allSurroundingNodes = [nodeMap[index - mapWidthInTiles - 1], nodeMap[index - mapWidthInTiles], nodeMap[index - mapWidthInTiles + 1], nodeMap[index - 1], nodeMap[index + 1], nodeMap[index + mapWidthInTiles - 1], nodeMap[index + mapWidthInTiles], nodeMap[index + mapWidthInTiles + 1]];
  7055. //Optionaly exlude the diagonal nodes, which is often perferable
  7056. //for 2D maze games
  7057. var crossSurroundingNodes = [nodeMap[index - mapWidthInTiles], nodeMap[index - 1], nodeMap[index + 1], nodeMap[index + mapWidthInTiles]];
  7058. //Use either `allSurroundingNodes` or `crossSurroundingNodes` depending
  7059. //on the the value of `useDiagonalNodes`
  7060. var nodesToCheck = undefined;
  7061. if (useDiagonalNodes) {
  7062. nodesToCheck = allSurroundingNodes;
  7063. } else {
  7064. nodesToCheck = crossSurroundingNodes;
  7065. }
  7066. //Find the valid sourrounding nodes, which are ones inside
  7067. //the map border that don't incldue obstacles. Change `allSurroundingNodes`
  7068. //to `crossSurroundingNodes` to prevent the path from choosing diagonal routes
  7069. var validSurroundingNodes = nodesToCheck.filter(function (node) {
  7070. //The node will be beyond the top and bottom edges of the
  7071. //map if it is `undefined`
  7072. var nodeIsWithinTopAndBottomBounds = node !== undefined;
  7073. //Only return nodes that are within the top and bottom map bounds
  7074. if (nodeIsWithinTopAndBottomBounds) {
  7075. //Some Boolean values that tell us whether the current map index is on
  7076. //the left or right border of the map, and whether any of the nodes
  7077. //surrounding that index extend beyond the left and right borders
  7078. var indexIsOnLeftBorder = index % mapWidthInTiles === 0;
  7079. var indexIsOnRightBorder = (index + 1) % mapWidthInTiles === 0;
  7080. var nodeIsBeyondLeftBorder = node.column % (mapWidthInTiles - 1) === 0 && node.column !== 0;
  7081. var nodeIsBeyondRightBorder = node.column % mapWidthInTiles === 0;
  7082. //Find out whether of not the node contains an obstacle by looping
  7083. //through the obstacle gids and and returning `true` if it
  7084. //finds any at this node's location
  7085. var nodeContainsAnObstacle = obstacleGids.some(function (obstacle) {
  7086. return mapArray[node.index] === obstacle;
  7087. });
  7088. //If the index is on the left border and any nodes surrounding it are beyond the
  7089. //left border, don't return that node
  7090. if (indexIsOnLeftBorder) {
  7091. //console.log("left border")
  7092. return !nodeIsBeyondLeftBorder;
  7093. }
  7094. //If the index is on the right border and any nodes surrounding it are beyond the
  7095. //right border, don't return that node
  7096. else if (indexIsOnRightBorder) {
  7097. //console.log("right border")
  7098. return !nodeIsBeyondRightBorder;
  7099. }
  7100. //Return `true` if the node doesn't contain any obstacles
  7101. else if (nodeContainsAnObstacle) {
  7102. return false;
  7103. }
  7104. //The index must be inside the area defined by the left and right borders,
  7105. //so return the node
  7106. else {
  7107. //console.log("map interior")
  7108. return true;
  7109. }
  7110. }
  7111. });
  7112. //console.log(validSurroundingNodes)
  7113. //Return the array of `validSurroundingNodes`
  7114. return validSurroundingNodes;
  7115. };
  7116. //Diagnostic
  7117. //console.log(nodeMap);
  7118. //console.log(centerNode);
  7119. //console.log(destinationNode);
  7120. //console.log(wallMapArray);
  7121. //console.log(surroundingNodes(86, mapArray, mapWidthInTiles));
  7122. //Heuristic methods
  7123. //1. Manhattan
  7124. var manhattan = function manhattan(testNode, destinationNode) {
  7125. var h = Math.abs(testNode.row - destinationNode.row) * straightCost + Math.abs(testNode.column - destinationNode.column) * straightCost;
  7126. return h;
  7127. };
  7128. //2. Euclidean
  7129. var euclidean = function euclidean(testNode, destinationNode) {
  7130. var vx = destinationNode.column - testNode.column,
  7131. vy = destinationNode.row - testNode.row,
  7132. h = Math.floor(Math.sqrt(vx * vx + vy * vy) * straightCost);
  7133. return h;
  7134. };
  7135. //3. Diagonal
  7136. var diagonal = function diagonal(testNode, destinationNode) {
  7137. var vx = Math.abs(destinationNode.column - testNode.column),
  7138. vy = Math.abs(destinationNode.row - testNode.row),
  7139. h = 0;
  7140. if (vx > vy) {
  7141. h = Math.floor(diagonalCost * vy + straightCost * (vx - vy));
  7142. } else {
  7143. h = Math.floor(diagonalCost * vx + straightCost * (vy - vx));
  7144. }
  7145. return h;
  7146. };
  7147. //Loop through all the nodes until the current `centerNode` matches the
  7148. //`destinationNode`. When they they're the same we know we've reached the
  7149. //end of the path
  7150. while (centerNode !== destinationNode) {
  7151. //Find all the nodes surrounding the current `centerNode`
  7152. var surroundingTestNodes = surroundingNodes(centerNode.index, mapArray, mapWidthInTiles, useDiagonalNodes);
  7153. //Loop through all the `surroundingTestNodes` using a classic `for` loop
  7154. //(A `for` loop gives us a marginal performance boost)
  7155. var _loop = function _loop(i) {
  7156. //Get a reference to the current test node
  7157. var testNode = surroundingTestNodes[i];
  7158. //Find out whether the node is on a straight axis or
  7159. //a diagonal axis, and assign the appropriate cost
  7160. //A. Declare the cost variable
  7161. var cost = 0;
  7162. //B. Do they occupy the same row or column?
  7163. if (centerNode.row === testNode.row || centerNode.column === testNode.column) {
  7164. //If they do, assign a cost of "10"
  7165. cost = straightCost;
  7166. } else {
  7167. //Otherwise, assign a cost of "14"
  7168. cost = diagonalCost;
  7169. }
  7170. //C. Calculate the costs (g, h and f)
  7171. //The node's current cost
  7172. var g = centerNode.g + cost;
  7173. //The cost of travelling from this node to the
  7174. //destination node (the heuristic)
  7175. var h = undefined;
  7176. switch (heuristic) {
  7177. case "manhattan":
  7178. h = manhattan(testNode, destinationNode);
  7179. break;
  7180. case "euclidean":
  7181. h = euclidean(testNode, destinationNode);
  7182. break;
  7183. case "diagonal":
  7184. h = diagonal(testNode, destinationNode);
  7185. break;
  7186. default:
  7187. throw new Error("Oops! It looks like you misspelled the name of the heuristic");
  7188. }
  7189. //The final cost
  7190. var f = g + h;
  7191. //Find out if the testNode is in either
  7192. //the openList or closedList array
  7193. var isOnOpenList = openList.some(function (node) {
  7194. return testNode === node;
  7195. });
  7196. var isOnClosedList = closedList.some(function (node) {
  7197. return testNode === node;
  7198. });
  7199. //If it's on either of these lists, we can check
  7200. //whether this route is a lower-cost alternative
  7201. //to the previous cost calculation. The new G cost
  7202. //will make the difference to the final F cost
  7203. if (isOnOpenList || isOnClosedList) {
  7204. if (testNode.f > f) {
  7205. testNode.f = f;
  7206. testNode.g = g;
  7207. testNode.h = h;
  7208. //Only change the parent if the new cost is lower
  7209. testNode.parent = centerNode;
  7210. }
  7211. }
  7212. //Otherwise, add the testNode to the open list
  7213. else {
  7214. testNode.f = f;
  7215. testNode.g = g;
  7216. testNode.h = h;
  7217. testNode.parent = centerNode;
  7218. openList.push(testNode);
  7219. }
  7220. //The `for` loop ends here
  7221. };
  7222. for (var i = 0; i < surroundingTestNodes.length; i++) {
  7223. _loop(i);
  7224. }
  7225. //Push the current centerNode into the closed list
  7226. closedList.push(centerNode);
  7227. //Quit the loop if there's nothing on the open list.
  7228. //This means that there is no path to the destination or the
  7229. //destination is invalid, like a wall tile
  7230. if (openList.length === 0) {
  7231. return theShortestPath;
  7232. }
  7233. //Sort the open list according to final cost
  7234. openList = openList.sort(function (a, b) {
  7235. return a.f - b.f;
  7236. });
  7237. //Set the node with the lowest final cost as the new centerNode
  7238. centerNode = openList.shift();
  7239. //The `while` loop ends here
  7240. }
  7241. //Now that we have all the candidates, let's find the shortest path!
  7242. if (openList.length !== 0) {
  7243. //Start with the destination node
  7244. var _testNode = destinationNode;
  7245. theShortestPath.push(_testNode);
  7246. //Work backwards through the node parents
  7247. //until the start node is found
  7248. while (_testNode !== startNode) {
  7249. //Step through the parents of each node,
  7250. //starting with the destination node and ending with the start node
  7251. _testNode = _testNode.parent;
  7252. //Add the node to the beginning of the array
  7253. theShortestPath.unshift(_testNode);
  7254. //...and then loop again to the next node's parent till you
  7255. //reach the end of the path
  7256. }
  7257. }
  7258. //Return an array of nodes that link together to form
  7259. //the shortest path
  7260. return theShortestPath;
  7261. }
  7262. /*
  7263. ### tileBasedLineOfSight
  7264. Use the `tileBasedLineOfSight` function to find out whether two sprites
  7265. are visible to each other inside a tile based maze environment.
  7266. */
  7267. }, {
  7268. key: "tileBasedLineOfSight",
  7269. value: function tileBasedLineOfSight(spriteOne, //The first sprite, with `centerX` and `centerY` properties
  7270. spriteTwo, //The second sprite, with `centerX` and `centerY` properties
  7271. mapArray, //The tile map array
  7272. world) //An array of angles to which you want to
  7273. //restrict the line of sight
  7274. {
  7275. var emptyGid = arguments.length <= 4 || arguments[4] === undefined ? 0 : arguments[4];
  7276. var _this6 = this;
  7277. var segment = arguments.length <= 5 || arguments[5] === undefined ? 32 : arguments[5];
  7278. var angles = arguments.length <= 6 || arguments[6] === undefined ? [] : arguments[6];
  7279. //Plot a vector between spriteTwo and spriteOne
  7280. var vx = spriteTwo.centerX - spriteOne.centerX,
  7281. vy = spriteTwo.centerY - spriteOne.centerY;
  7282. //Find the vector's magnitude (its length in pixels)
  7283. var magnitude = Math.sqrt(vx * vx + vy * vy);
  7284. //How many points will we need to test?
  7285. var numberOfPoints = magnitude / segment;
  7286. //Create an array of x/y points that
  7287. //extends from `spriteOne` to `spriteTwo`
  7288. var points = function points() {
  7289. //Initialize an array that is going to store all our points
  7290. //along the vector
  7291. var arrayOfPoints = [];
  7292. //Create a point object for each segment of the vector and
  7293. //store its x/y position as well as its index number on
  7294. //the map array
  7295. for (var i = 1; i <= numberOfPoints; i++) {
  7296. //Calculate the new magnitude for this iteration of the loop
  7297. var newMagnitude = segment * i;
  7298. //Find the unit vector
  7299. var dx = vx / magnitude,
  7300. dy = vy / magnitude;
  7301. //Use the unit vector and newMagnitude to figure out the x/y
  7302. //position of the next point in this loop iteration
  7303. var x = spriteOne.centerX + dx * newMagnitude,
  7304. y = spriteOne.centerY + dy * newMagnitude;
  7305. //The getIndex function converts x/y coordinates into
  7306. //map array index positon numbers
  7307. var getIndex = function getIndex(x, y, tilewidth, tileheight, mapWidthInTiles) {
  7308. //Convert pixel coordinates to map index coordinates
  7309. var index = {};
  7310. index.x = Math.floor(x / tilewidth);
  7311. index.y = Math.floor(y / tileheight);
  7312. //Return the index number
  7313. return index.x + index.y * mapWidthInTiles;
  7314. };
  7315. //Find the map index number that this x and y point corresponds to
  7316. var index = _this6.getIndex(x, y, world.tilewidth, world.tileheight, world.widthInTiles);
  7317. //Push the point into the `arrayOfPoints`
  7318. arrayOfPoints.push({
  7319. x: x, y: y, index: index
  7320. });
  7321. }
  7322. //Return the array
  7323. return arrayOfPoints;
  7324. };
  7325. //The tile-based collision test.
  7326. //The `noObstacles` function will return `true` if all the tile
  7327. //index numbers along the vector are `0`, which means they contain
  7328. //no walls. If any of them aren't 0, then the function returns
  7329. //`false` which means there's a wall in the way
  7330. var noObstacles = points().every(function (point) {
  7331. return mapArray[point.index] === emptyGid;
  7332. });
  7333. //Restrict the line of sight to right angles only (we don't want to
  7334. //use diagonals)
  7335. var validAngle = function validAngle() {
  7336. //Find the angle of the vector between the two sprites
  7337. var angle = Math.atan2(vy, vx) * 180 / Math.PI;
  7338. //If the angle matches one of the valid angles, return
  7339. //`true`, otherwise return `false`
  7340. if (angles.length !== 0) {
  7341. return angles.some(function (x) {
  7342. return x === angle;
  7343. });
  7344. } else {
  7345. return true;
  7346. }
  7347. };
  7348. //Return `true` if there are no obstacles and the line of sight
  7349. //is at a 90 degree angle
  7350. if (noObstacles === true && validAngle() === true) {
  7351. return true;
  7352. } else {
  7353. return false;
  7354. }
  7355. }
  7356. /*
  7357. surroundingCrossCells
  7358. ---------------------
  7359. Returns an array of index numbers matching the cells that are orthogonally
  7360. adjacent to the center `index` cell
  7361. */
  7362. }, {
  7363. key: "surroundingCrossCells",
  7364. value: function surroundingCrossCells(index, widthInTiles) {
  7365. return [index - widthInTiles, index - 1, index + 1, index + widthInTiles];
  7366. }
  7367. /*
  7368. surroundingDiagonalCells
  7369. ---------------------
  7370. Returns an array of index numbers matching the cells that touch the
  7371. 4 corners of the center the center `index` cell
  7372. */
  7373. }, {
  7374. key: "surroundingDiagonalCells",
  7375. value: function surroundingDiagonalCells(index, widthInTiles) {
  7376. return [index - widthInTiles - 1, index - widthInTiles + 1, index + widthInTiles - 1, index + widthInTiles + 1];
  7377. }
  7378. /*
  7379. validDirections
  7380. ---------------
  7381. Returns an array with the values "up", "down", "left" or "right"
  7382. that represent all the valid directions in which a sprite can move
  7383. The `validGid` is the grid index number for the "walkable" part of the world
  7384. (such as, possibly, `0`.)
  7385. */
  7386. }, {
  7387. key: "validDirections",
  7388. value: function validDirections(sprite, mapArray, validGid, world) {
  7389. //Get the sprite's current map index position number
  7390. var index = g.getIndex(sprite.x, sprite.y, world.tilewidth, world.tileheight, world.widthInTiles);
  7391. //An array containing the index numbers of tile cells
  7392. //above, below and to the left and right of the sprite
  7393. var surroundingCrossCells = function surroundingCrossCells(index, widthInTiles) {
  7394. return [index - widthInTiles, index - 1, index + 1, index + widthInTiles];
  7395. };
  7396. //Get the index position numbers of the 4 cells to the top, right, left
  7397. //and bottom of the sprite
  7398. var surroundingIndexNumbers = surroundingCrossCells(index, world.widthInTiles);
  7399. //Find all the tile gid numbers that match the surrounding index numbers
  7400. var surroundingTileGids = surroundingIndexNumbers.map(function (index) {
  7401. return mapArray[index];
  7402. });
  7403. //`directionList` is an array of 4 string values that can be either
  7404. //"up", "left", "right", "down" or "none", depending on
  7405. //whether there is a cell with a valid gid that matches that direction.
  7406. var directionList = surroundingTileGids.map(function (gid, i) {
  7407. //The possible directions
  7408. var possibleDirections = ["up", "left", "right", "down"];
  7409. //If the direction is valid, choose the matching string
  7410. //identifier for that direction. Otherwise, return "none"
  7411. if (gid === validGid) {
  7412. return possibleDirections[i];
  7413. } else {
  7414. return "none";
  7415. }
  7416. });
  7417. //We don't need "none" in the list of directions
  7418. //(it's just a placeholder), so let's filter it out
  7419. var filteredDirectionList = directionList.filter(function (direction) {
  7420. return direction != "none";
  7421. });
  7422. //Return the filtered list of valid directions
  7423. return filteredDirectionList;
  7424. }
  7425. /*
  7426. canChangeDirection
  7427. ------------------
  7428. Returns `true` or `false` depending on whether a sprite in at a map
  7429. array location in which it able to change its direction
  7430. */
  7431. }, {
  7432. key: "canChangeDirection",
  7433. value: function canChangeDirection() {
  7434. var validDirections = arguments.length <= 0 || arguments[0] === undefined ? [] : arguments[0];
  7435. //Is the sprite in a dead-end (cul de sac.) This will be true if there's only
  7436. //one element in the `validDirections` array
  7437. var inCulDeSac = validDirections.length === 1;
  7438. //Is the sprite trapped? This will be true if there are no elements in
  7439. //the `validDirections` array
  7440. var trapped = validDirections.length === 0;
  7441. //Is the sprite in a passage? This will be `true` if the the sprite
  7442. //is at a location that contain the values
  7443. //“left” or “right” and “up” or “down”
  7444. var up = validDirections.find(function (x) {
  7445. return x === "up";
  7446. }),
  7447. down = validDirections.find(function (x) {
  7448. return x === "down";
  7449. }),
  7450. left = validDirections.find(function (x) {
  7451. return x === "left";
  7452. }),
  7453. right = validDirections.find(function (x) {
  7454. return x === "right";
  7455. }),
  7456. atIntersection = (up || down) && (left || right);
  7457. //Return `true` if the sprite can change direction or
  7458. //`false` if it can't
  7459. return trapped || atIntersection || inCulDeSac;
  7460. }
  7461. /*
  7462. randomDirection
  7463. ---------------
  7464. Randomly returns the values "up", "down", "left" or "right" based on
  7465. valid directions supplied. If the are no valid directions, it returns "trapped"
  7466. */
  7467. }, {
  7468. key: "randomDirection",
  7469. value: function randomDirection(sprite) {
  7470. var validDirections = arguments.length <= 1 || arguments[1] === undefined ? [] : arguments[1];
  7471. //The `randomInt` helper function returns a random integer between a minimum
  7472. //and maximum value
  7473. var randomInt = function randomInt(min, max) {
  7474. return Math.floor(Math.random() * (max - min + 1)) + min;
  7475. };
  7476. //Is the sprite trapped?
  7477. var trapped = validDirections.length === 0;
  7478. //If the sprite isn't trapped, randomly choose one of the valid
  7479. //directions. Otherwise, return the string "trapped"
  7480. if (!trapped) {
  7481. return validDirections[randomInt(0, validDirections.length - 1)];
  7482. } else {
  7483. return "trapped";
  7484. }
  7485. }
  7486. /*
  7487. closestDirection
  7488. ----------------
  7489. Tells you the closes direction to `spriteTwo` from `spriteOne` based on
  7490. supplied validDirections. The function returns any of these
  7491. 4 values: "up", "down", "left" or "right"
  7492. */
  7493. }, {
  7494. key: "closestDirection",
  7495. value: function closestDirection(spriteOne, spriteTwo) {
  7496. var validDirections = arguments.length <= 2 || arguments[2] === undefined ? [] : arguments[2];
  7497. //A helper function to find the closest direction
  7498. var closest = function closest() {
  7499. //Plot a vector between spriteTwo and spriteOne
  7500. var vx = spriteTwo.centerX - spriteOne.centerX,
  7501. vy = spriteTwo.centerY - spriteOne.centerY;
  7502. //If the distance is greater on the X axis...
  7503. if (Math.abs(vx) >= Math.abs(vy)) {
  7504. //Try left and right
  7505. if (vx <= 0) {
  7506. return "left";
  7507. } else {
  7508. return "right";
  7509. }
  7510. }
  7511. //If the distance is greater on the Y axis...
  7512. else {
  7513. //Try up and down
  7514. if (vy <= 0) {
  7515. return "up";
  7516. } else {
  7517. return "down";
  7518. }
  7519. }
  7520. };
  7521. }
  7522. }]);
  7523. return TileUtilities;
  7524. })();
  7525. //# sourceMappingURL=tileUtilities.js.map