gameMechanics.js 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101
  1. /**
  2. * Variable that handles game mechanics
  3. *
  4. * @namespace
  5. */
  6. const game = {
  7. audio: {}, lang: {}, // Holds cache reference to media - used in code to get audio and dicitonary
  8. image: {}, sprite: {}, // [Not directly used] Holds cache reference to media
  9. mediaTypes: ['lang', 'audio', 'image', 'sprite'], // [Not directly used]
  10. loadedMedia: [], // [Not directly used]
  11. isLoaded: [], // [Not directly used]
  12. /**
  13. * Load URLs to cache <br>
  14. * To be used only inside function preload() if a state needs to load media <br>
  15. * URLs are in globals.js
  16. *
  17. * @namespace
  18. */
  19. load: {
  20. /**
  21. * Loads dictionary to cache
  22. *
  23. * @param {string} url url for the selected language
  24. */
  25. lang: function (url) {
  26. game.isLoaded['lang'] = false;
  27. game.loadedMedia['lang'] = 0;
  28. game.lang = {}; // Clear previously loaded language
  29. const init = { mode: 'same-origin' };
  30. fetch(url, init) // Fetch API
  31. .then(function (response) {
  32. return response.text();
  33. })
  34. .then(function (text) {
  35. let msg = text.split('\n');
  36. msg.forEach(cur => {
  37. try {
  38. let msg = cur.split('=');
  39. game.lang[msg[0].trim()] = msg[1].trim();
  40. } catch (Error) { if (debugMode) console.log('Sintax error fixed'); }
  41. game.load._informMediaIsLoaded(msg.length - 1, 'lang');
  42. });
  43. });
  44. },
  45. /**
  46. * Loads audio files to cache
  47. *
  48. * @param {array} urls audio urls for the current state
  49. */
  50. audio: function (urls) {
  51. game.isLoaded['audio'] = false;
  52. game.loadedMedia['audio'] = 0;
  53. urls = this._getNotLoadedUrls(urls, game.audio);
  54. if (urls.length == 0) {
  55. game.load._informMediaIsLoaded(0, 'audio');
  56. } else {
  57. const init = { mode: 'same-origin' };
  58. urls.forEach(cur => {
  59. fetch(cur[1][1], init) // Fetch API
  60. .then(response => response.blob())
  61. .then(function (myBlob) {
  62. game.audio[cur[0]] = new Audio(URL.createObjectURL(myBlob));
  63. game.load._informMediaIsLoaded(urls.length - 1, 'audio');
  64. });
  65. });
  66. }
  67. },
  68. /**
  69. * Loads images to cache
  70. *
  71. * @param {array} urls image urls for the current state
  72. */
  73. image: function (urls) {
  74. game.isLoaded['image'] = false;
  75. game.loadedMedia['image'] = 0;
  76. urls = this._getNotLoadedUrls(urls, game.image);
  77. if (urls.length == 0) {
  78. game.load._informMediaIsLoaded(0, 'image');
  79. } else {
  80. urls.forEach(cur => {
  81. const img = new Image(); // HTMLImageElement
  82. img.onload = () => {
  83. game.image[cur[0]] = img;
  84. game.load._informMediaIsLoaded(urls.length - 1, 'image');
  85. }
  86. img.src = cur[1];
  87. });
  88. }
  89. },
  90. /**
  91. * Loads spritesheets to cache
  92. *
  93. * @param {array} urls spritesheet urls for the current state
  94. */
  95. sprite: function (urls) {
  96. game.isLoaded['sprite'] = false;
  97. game.loadedMedia['sprite'] = 0;
  98. urls = this._getNotLoadedUrls(urls, game.sprite);
  99. if (urls.length == 0) {
  100. game.load._informMediaIsLoaded(0, 'sprite');
  101. } else {
  102. urls.forEach(cur => {
  103. const img = new Image(); // HTMLImageElement
  104. img.onload = () => {
  105. game.sprite[cur[0]] = img;
  106. game.load._informMediaIsLoaded(urls.length - 1, 'sprite');
  107. }
  108. img.src = cur[1];
  109. img.frames = cur[2];
  110. });
  111. }
  112. },
  113. /**
  114. * Updates list of urls to whats not already in cache
  115. *
  116. * @param {array} urls array of urls
  117. * @param {object} media whats in cache for current media type
  118. * @returns {array} array of urls without what is already in cache
  119. */
  120. _getNotLoadedUrls: function (urls, media) {
  121. const newUrls = [];
  122. urls.forEach(cur => { if (media[cur[0]] == undefined) newUrls.push(cur); });
  123. return newUrls;
  124. },
  125. /**
  126. * Informs that all files of current media type are loaded
  127. *
  128. * @param {number} last last index
  129. * @param {String} type media type
  130. */
  131. _informMediaIsLoaded: function (last, type) {
  132. if (game.loadedMedia[type] == last) {
  133. game.isLoaded[type] = true;
  134. game.load._isPreloadDone(type);
  135. }
  136. game.loadedMedia[type]++;
  137. },
  138. /**
  139. * When all types of media are loaded for the current state, calls function create() through state
  140. *
  141. * @param {String} type type of media fully loaded for current state
  142. */
  143. _isPreloadDone: function (type) {
  144. let flag = true;
  145. for (let i in game.mediaTypes) {
  146. if (game.isLoaded[game.mediaTypes[i]] == false) { // If all media for current state is loaded, flag wont change
  147. flag = false;
  148. break;
  149. }
  150. }
  151. // If flag doesnt change preload is complete
  152. if (flag) {
  153. game.isLoaded = [];
  154. game.loadedMedia[type] = 0;
  155. game.state._create();
  156. }
  157. }
  158. },
  159. /**
  160. * Adds new media to 'media queue' <br>
  161. * All queued media is actually drawn on the canvas using game.render.all()
  162. *
  163. * @namespace
  164. */
  165. add: {
  166. /**
  167. * Adds image to media queue <br><br>
  168. *
  169. * game.add.image(x, y, img) <br>
  170. * game.add.image(x, y, img, scale) <br>
  171. * game.add.image(x, y, img, scale, alpha)
  172. *
  173. * @param {number} x default x coordinate for the figure
  174. * @param {number} y default x coordinate for the figure
  175. * @param {string} img name of the cached image
  176. * @param {number} scale scale for the image (default = 1)
  177. * @param {number} alpha level of transparency, from 0 (invisible) to 1 (completely visible))
  178. * @returns {object}
  179. */
  180. image: function (x, y, img, scale, alpha) {
  181. if (x == undefined || y == undefined || img == undefined) console.error('Game error: missing parameters');
  182. else if (game.image[img] == undefined) console.error('Game error: image not found in cache: ' + img);
  183. else {
  184. const med = {
  185. typeOfMedia: 'image',
  186. name: img,
  187. x: x || game.add._default.x,
  188. y: y || game.add._default.y,
  189. _xWithAnchor: x || game.add._default._xWithAnchor,
  190. _yWithAnchor: y || game.add._default._yWithAnchor,
  191. xAnchor: game.add._default.xAnchor,
  192. yAnchor: game.add._default.yAnchor,
  193. shadow: game.add._default.shadow,
  194. shadowColor: game.add._default.shadowColor,
  195. shadowBlur: game.add._default.shadowBlur,
  196. alpha: (alpha != undefined) ? alpha : game.add._default.alpha,
  197. scale: scale || game.add._default.scale,
  198. width: game.image[img].width,
  199. height: game.image[img].height,
  200. anchor: function (xAnchor, yAnchor) {
  201. this.xAnchor = xAnchor;
  202. this.yAnchor = yAnchor;
  203. },
  204. get xWithAnchor() { return this.x - (this.width * this.scale * this.xAnchor); },
  205. get yWithAnchor() { return this.y - (this.height * this.scale * this.yAnchor); }
  206. };
  207. med.originalScale = med.scale;
  208. game.render.queue.push(med);
  209. return med;
  210. }
  211. },
  212. /**
  213. * Adds spritesheet to media queue <br><br>
  214. *
  215. * game.add.sprite(x, y, img) <br>
  216. * game.add.sprite(x, y, img, curFrame) <br>
  217. * game.add.sprite(x, y, img, curFrame, scale) <br>
  218. * game.add.sprite(x, y, img, curFrame, scale, alpha)
  219. *
  220. * @param {number} x default x coordinate for the figure
  221. * @param {number} y default x coordinate for the figure
  222. * @param {string} img name of the cached spritesheet
  223. * @param {number} curFrame current frame from the spritesheet (default = 0)
  224. * @param {number} scale scale for the spritesheet (default = 1)
  225. * @param {number} alpha level of transparency, from 0 (invisible) to 1 (completely visible))
  226. * @returns {object}
  227. */
  228. sprite: function (x, y, img, curFrame, scale, alpha) {
  229. if (x == undefined || y == undefined || img == undefined) console.error('Game error: missing parameters');
  230. else if (game.sprite[img] == undefined) console.error('Game error: sprite not found in cache: ' + img);
  231. else {
  232. const med = {
  233. typeOfMedia: 'sprite',
  234. name: img,
  235. x: x || game.add._default.x,
  236. y: y || game.add._default.y,
  237. _xWithAnchor: x || game.add._default._xWithAnchor,
  238. _yWithAnchor: y || game.add._default._yWithAnchor,
  239. xAnchor: game.add._default.xAnchor,
  240. yAnchor: game.add._default.yAnchor,
  241. shadow: game.add._default.shadow,
  242. shadowColor: game.add._default.shadowColor,
  243. shadowBlur: game.add._default.shadowBlur,
  244. alpha: (alpha != undefined) ? alpha : game.add._default.alpha,
  245. scale: scale || game.add._default.scale,
  246. width: game.sprite[img].width / game.sprite[img].frames, // Frame width
  247. height: game.sprite[img].height, // Frame height
  248. curFrame: curFrame || 0,
  249. anchor: function (xAnchor, yAnchor) {
  250. this.xAnchor = xAnchor;
  251. this.yAnchor = yAnchor;
  252. },
  253. get xWithAnchor() { return this.x - (this.width * this.scale * this.xAnchor); },
  254. get yWithAnchor() { return this.y - (this.height * this.scale * this.yAnchor); }
  255. };
  256. med.originalScale = med.scale;
  257. game.render.queue.push(med);
  258. return med;
  259. }
  260. },
  261. /**
  262. * Adds text to media queue <br><br>
  263. *
  264. * game.add.text(x, y, text, style) <br>
  265. * game.add.text(x, y, text, style, align)
  266. *
  267. * @param {number} x default x coordinate for the figure
  268. * @param {number} y default x coordinate for the figure
  269. * @param {string} text text to be displayed on screen
  270. * @param {object} style object containing font, text color and align for the text
  271. * @param {string} align text align on screen [left, center or right]
  272. * @returns {object}
  273. */
  274. text: function (x, y, text, style, align) {
  275. if (x == undefined || y == undefined || text == undefined || style == undefined) console.error('Game error: missing parameters');
  276. else {
  277. const med = {
  278. typeOfMedia: 'text',
  279. name: text,
  280. x: x || game.add._default.x,
  281. y: y || game.add._default.y,
  282. _xWithAnchor: x || game.add._default._xWithAnchor,
  283. _yWithAnchor: y || game.add._default._yWithAnchor,
  284. xAnchor: game.add._default.xAnchor,
  285. yAnchor: game.add._default.yAnchor,
  286. shadow: game.add._default.shadow,
  287. shadowColor: game.add._default.shadowColor,
  288. shadowBlur: game.add._default.shadowBlur,
  289. alpha: game.add._default.alpha,
  290. font: style.font || game.add._default.font,
  291. fill: style.fill || game.add._default.fill,
  292. align: align || style.align || game.add._default.align,
  293. anchor: function () { console.error('Game error: there\'s no anchor for text'); },
  294. set style(style) {
  295. this.font = style.font;
  296. this.fill = style.fill;
  297. this.align = style.align;
  298. },
  299. get xWithAnchor() { return this.x; },
  300. get yWithAnchor() { return this.y; },
  301. };
  302. game.render.queue.push(med);
  303. return med;
  304. }
  305. },
  306. /**
  307. * Adds geometric shapes
  308. * @namespace
  309. */
  310. geom: {
  311. /**
  312. * Adds rectangle to media queue <br><br>
  313. *
  314. * game.add.geom.rect(x, y, width, height) <br>
  315. * game.add.geom.rect(x, y, width, height, lineColor) <br>
  316. * game.add.geom.rect(x, y, width, height, lineColor, lineWidth) <br>
  317. * game.add.geom.rect(x, y, width, height, lineColor, lineWidth, fillColor) <br>
  318. * game.add.geom.rect(x, y, width, height, lineColor, lineWidth, fillColor, alpha)
  319. *
  320. * @param {number} x default x coordinate for top left corner of the rectangle
  321. * @param {number} y default y coordinate for top left corner of the rectangle
  322. * @param {number} width rectangle width
  323. * @param {number} height rectangle height
  324. * @param {string} lineColor stroke color
  325. * @param {number} lineWidth stroke width
  326. * @param {string} fillColor fill color
  327. * @param {number} alpha level of transparency, from 0 (invisible) to 1 (completely visible))
  328. * @returns {object}
  329. */
  330. rect: function (x, y, width, height, lineColor, lineWidth, fillColor, alpha) {
  331. if (x == undefined || y == undefined || width == undefined) console.error('Game error: missing parameters');
  332. else {
  333. const med = {
  334. typeOfMedia: 'rect',
  335. x: x || game.add._default.x,
  336. y: y || game.add._default.y,
  337. _xWithAnchor: x || game.add._default._xWithAnchor,
  338. _yWithAnchor: y || game.add._default._yWithAnchor,
  339. xAnchor: game.add._default.xAnchor,
  340. yAnchor: game.add._default.yAnchor,
  341. shadow: game.add._default.shadow,
  342. shadowColor: game.add._default.shadowColor,
  343. shadowBlur: game.add._default.shadowBlur,
  344. alpha: (alpha != undefined) ? alpha : game.add._default.alpha,
  345. scale: game.add._default.scale,
  346. width: 0,
  347. height: 0,
  348. lineColor: lineColor || game.add._default.lineColor,
  349. lineWidth: 0,
  350. fillColor: fillColor || game.add._default.fillColor,
  351. anchor: function (xAnchor, yAnchor) {
  352. this.xAnchor = xAnchor;
  353. this.yAnchor = yAnchor;
  354. },
  355. get xWithAnchor() { return this.x - (this.width * this.scale * this.xAnchor); },
  356. get yWithAnchor() { return this.y - (this.height * this.scale * this.yAnchor); }
  357. };
  358. med.originalScale = med.scale;
  359. if (width != 0) { med.width = width || game.add._default.width; }
  360. if (height != 0) { med.height = height || width || game.add._default.height; }
  361. if (lineWidth != 0) { med.lineWidth = lineWidth || game.add._default.lineWidth; }
  362. game.render.queue.push(med);
  363. return med;
  364. }
  365. },
  366. /**
  367. * Adds circle to media queue <br><br>
  368. *
  369. * game.add.geom.circle(x, y, diameter) <br>
  370. * game.add.geom.circle(x, y, diameter, lineColor) <br>
  371. * game.add.geom.circle(x, y, diameter, lineColor, lineWidth) <br>
  372. * game.add.geom.circle(x, y, diameter, lineColor, lineWidth, fillColor) <br>
  373. * game.add.geom.circle(x, y, diameter, lineColor, lineWidth, fillColor, alpha)
  374. *
  375. * @param {number} x default x coordinate for the circle center
  376. * @param {number} y default y coordinate for the circle center
  377. * @param {number} diameter circle diameter
  378. * @param {string} lineColor stroke color
  379. * @param {number} lineWidth stroke width
  380. * @param {string} fillColor fill color
  381. * @param {number} alpha level of transparency, from 0 (invisible) to 1 (completely visible))
  382. * @returns {object}
  383. */
  384. circle: function (x, y, diameter, lineColor, lineWidth, fillColor, alpha) {
  385. if (x == undefined || y == undefined || diameter == undefined) console.error('Game error: missing parameters');
  386. else {
  387. const med = {
  388. typeOfMedia: 'arc',
  389. x: x || game.add._default.x,
  390. y: y || game.add._default.y,
  391. _xWithAnchor: x || game.add._default._xWithAnchor,
  392. _yWithAnchor: y || game.add._default._yWithAnchor,
  393. xAnchor: game.add._default.xAnchor,
  394. yAnchor: game.add._default.yAnchor,
  395. shadow: game.add._default.shadow,
  396. shadowColor: game.add._default.shadowColor,
  397. shadowBlur: game.add._default.shadowBlur,
  398. alpha: (alpha != undefined) ? alpha : game.add._default.alpha,
  399. scale: game.add._default.scale,
  400. diameter: 0,
  401. width: 0,
  402. height: 0,
  403. angleStart: 0,
  404. angleEnd: 2 * Math.PI,
  405. anticlockwise: game.add._default.anticlockwise,
  406. lineColor: lineColor || game.add._default.lineColor,
  407. lineWidth: 0,
  408. fillColor: fillColor || game.add._default.fillColor,
  409. anchor: function (xAnchor, yAnchor) {
  410. this.xAnchor = xAnchor;
  411. this.yAnchor = yAnchor;
  412. },
  413. get xWithAnchor() { return this.x - (this.width * this.scale * this.xAnchor); },
  414. get yWithAnchor() { return this.y - (this.height * this.scale * this.yAnchor); }
  415. };
  416. med.originalScale = med.scale;
  417. if (diameter != 0) {
  418. med.diameter = diameter || game.add._default.diameter;
  419. med.width = med.height = med.diameter;
  420. }
  421. if (lineWidth != 0) {
  422. med.lineWidth = lineWidth || game.add._default.lineWidth;
  423. }
  424. game.render.queue.push(med);
  425. return med;
  426. }
  427. },
  428. /**
  429. * Adds arc to media queue <br><br>
  430. *
  431. * game.add.geom.arc(x, y, diameter, angleStart, angleEnd) <br>
  432. * game.add.geom.arc(x, y, diameter, angleStart, angleEnd, anticlockWise) <br>
  433. * game.add.geom.arc(x, y, diameter, angleStart, angleEnd, anticlockWise, lineColor) <br>
  434. * game.add.geom.arc(x, y, diameter, angleStart, angleEnd, anticlockWise, lineColor, lineWidth) <br>
  435. * game.add.geom.arc(x, y, diameter, angleStart, angleEnd, anticlockWise, lineColor, lineWidth, fillColor) <br>
  436. * game.add.geom.arc(x, y, diameter, angleStart, angleEnd, anticlockWise, lineColor, lineWidth, fillColor, alpha)
  437. *
  438. * @param {number} x default x coordinate for the arc center
  439. * @param {number} y default y coordinate for the arc center
  440. * @param {number} diameter arc diameter
  441. * @param {number} angleStart angle to start the arc
  442. * @param {number} angleEnd angle to end the arc
  443. * @param {boolean} anticlockwise if true, arc is created anticlockwise [default=false]
  444. * @param {string} lineColor stroke color
  445. * @param {number} lineWidth stroke width
  446. * @param {string} fillColor fill color
  447. * @param {number} alpha level of transparency, from 0 (invisible) to 1 (completely visible))
  448. * @returns {object}
  449. */
  450. arc: function (x, y, diameter, angleStart, angleEnd, anticlockwise, lineColor, lineWidth, fillColor, alpha) {
  451. if (x == undefined || y == undefined || diameter == undefined || angleStart == undefined || angleEnd == undefined) console.error('Game error: missing parameters');
  452. else {
  453. const med = {
  454. typeOfMedia: 'arc',
  455. x: x || game.add._default.x,
  456. y: y || game.add._default.y,
  457. _xWithAnchor: x || game.add._default._xWithAnchor,
  458. _yWithAnchor: y || game.add._default._yWithAnchor,
  459. xAnchor: game.add._default.xAnchor,
  460. yAnchor: game.add._default.yAnchor,
  461. shadow: game.add._default.shadow,
  462. shadowColor: game.add._default.shadowColor,
  463. shadowBlur: game.add._default.shadowBlur,
  464. alpha: (alpha != undefined) ? alpha : game.add._default.alpha,
  465. scale: game.add._default.scale,
  466. diameter: 0,
  467. width: 0,
  468. height: 0,
  469. angleStart: angleStart || 0,
  470. angleEnd: angleEnd || 2 * Math.PI,
  471. anticlockwise: anticlockwise || game.add._default.anticlockwise,
  472. lineColor: lineColor || game.add._default.lineColor,
  473. lineWidth: 0,
  474. fillColor: fillColor || game.add._default.fillColor,
  475. anchor: function (xAnchor, yAnchor) {
  476. this.xAnchor = xAnchor;
  477. this.yAnchor = yAnchor;
  478. },
  479. get xWithAnchor() { return this.x - (this.width * this.scale * this.xAnchor); },
  480. get yWithAnchor() { return this.y - (this.height * this.scale * this.yAnchor); }
  481. };
  482. med.originalScale = med.scale;
  483. if (diameter != 0) {
  484. med.diameter = diameter || game.add._default.diameter;
  485. med.width = med.height = med.diameter;
  486. }
  487. if (lineWidth != 0) { med.lineWidth = lineWidth || game.add._default.lineWidth; }
  488. game.render.queue.push(med);
  489. return med;
  490. }
  491. }
  492. },
  493. // [Not directly used]
  494. _default: {
  495. // All media
  496. x: 0,
  497. y: 0,
  498. _xWithAnchor: 0,
  499. _yWithAnchor: 0,
  500. xAnchor: 0,
  501. yAnchor: 0,
  502. shadow: false,
  503. shadowColor: '#0075c5',
  504. shadowBlur: 20,
  505. alpha: 1,
  506. // Image, sprite, square, circle
  507. scale: 1,
  508. // Text
  509. font: '14px Arial,sans-serif',
  510. fill: '#000',
  511. align: 'center',
  512. // Square, circle (image and sprite have width and height, but do not have default values)
  513. width: 50,
  514. height: 50,
  515. lineColor: '#000',
  516. lineWidth: 1,
  517. fillColor: 0, // Default no fill
  518. // Circle
  519. diameter: 50,
  520. anticlockwise: false,
  521. },
  522. },
  523. /**
  524. * Renders media on current screen <br>
  525. * Uses properties of html canvas to draw media on screen during game loop
  526. *
  527. * @namespace
  528. */
  529. render: {
  530. queue: [], // [Not directly used] Media queue to be rendered by the current state
  531. /** [Not directly used] <br>
  532. * Renders image on canvas
  533. *
  534. * @param {object} cur current media in media queue
  535. */
  536. _image: function (cur) {
  537. const x = cur.xWithAnchor, y = cur.yWithAnchor;
  538. if (cur.rotate && cur.rotate != 0) {
  539. context.save();
  540. context.translate(cur.x, cur.y);
  541. context.rotate(cur.rotate * Math.PI / 180);
  542. context.translate(-cur.x, -cur.y);
  543. }
  544. context.globalAlpha = cur.alpha;
  545. context.shadowBlur = (cur.shadow) ? cur.shadowBlur : 0;
  546. context.shadowColor = cur.shadowColor;
  547. context.drawImage(
  548. game.image[cur.name],
  549. x,
  550. y,
  551. cur.width * cur.scale,
  552. cur.height * cur.scale
  553. );
  554. context.shadowBlur = 0;
  555. context.globalAlpha = 1;
  556. if (cur.rotate && cur.rotate != 0) context.restore();
  557. },
  558. /** [Not directly used] <br>
  559. * Renders spritesheet on canvas
  560. *
  561. * @param {object} cur current media in media queue
  562. */
  563. _sprite: function (cur) {
  564. const x = cur.xWithAnchor, y = cur.yWithAnchor;
  565. if (cur.rotate && cur.rotate != 0) {
  566. context.save();
  567. context.translate(cur.x, cur.y);
  568. context.rotate(cur.rotate * Math.PI / 180);
  569. context.translate(-cur.x, -cur.y);
  570. }
  571. context.globalAlpha = cur.alpha;
  572. context.shadowBlur = (cur.shadow) ? cur.shadowBlur : 0;
  573. context.shadowColor = cur.shadowColor;
  574. context.drawImage(
  575. game.sprite[cur.name],
  576. cur.width * cur.curFrame,
  577. 0,
  578. cur.width,
  579. cur.height,
  580. x,
  581. y,
  582. cur.width * cur.scale,
  583. cur.height * cur.scale
  584. );
  585. context.shadowBlur = 0;
  586. context.globalAlpha = 1;
  587. if (cur.rotate && cur.rotate != 0) context.restore();
  588. },
  589. /** [Not directly used] <br>
  590. * Renders text on canvas
  591. *
  592. * @param {object} cur current media in media queue
  593. */
  594. _text: function (cur) {
  595. const x = cur.xWithAnchor, y = cur.yWithAnchor;
  596. if (cur.rotate && cur.rotate != 0) {
  597. context.save();
  598. context.translate(cur.x, cur.y);
  599. context.rotate(cur.rotate * Math.PI / 180);
  600. context.translate(-cur.x, -cur.y);
  601. }
  602. context.globalAlpha = cur.alpha;
  603. context.shadowBlur = (cur.shadow) ? cur.shadowBlur : 0;
  604. context.shadowColor = cur.shadowColor;
  605. context.font = cur.font;
  606. context.textAlign = cur.align;
  607. context.fillStyle = cur.fill;
  608. context.fillText(cur.name, x, y);
  609. context.shadowBlur = 0;
  610. context.globalAlpha = 1;
  611. if (cur.rotate && cur.rotate != 0) context.restore();
  612. },
  613. /**[Not directly used] <br>
  614. * Renders geometric shapes
  615. *
  616. * @namespace
  617. */
  618. _geom: {
  619. /**
  620. * Renders rectangle on canvas
  621. *
  622. * @param {object} cur current media in media queue
  623. */
  624. _rect: function (cur) {
  625. const x = cur.xWithAnchor, y = cur.yWithAnchor;
  626. // Rotation
  627. if (cur.rotate && cur.rotate != 0) {
  628. context.save();
  629. context.translate(cur.x, cur.y);
  630. context.rotate(cur.rotate * Math.PI / 180);
  631. context.translate(-cur.x, -cur.y);
  632. }
  633. // Alpha
  634. context.globalAlpha = cur.alpha;
  635. // Shadow
  636. context.shadowBlur = (cur.shadow) ? cur.shadowBlur : 0;
  637. context.shadowColor = cur.shadowColor;
  638. // Fill
  639. if (cur.fillColor != 0) {
  640. context.fillStyle = cur.fillColor;
  641. context.fillRect(x, y, cur.width * cur.scale, cur.height * cur.scale);
  642. }
  643. // Stroke
  644. if (cur.lineWidth != 0) {
  645. context.strokeStyle = cur.lineColor;
  646. context.lineWidth = cur.lineWidth;
  647. context.strokeRect(x, y, cur.width * cur.scale, cur.height * cur.scale);
  648. }
  649. // End
  650. context.shadowBlur = 0;
  651. context.globalAlpha = 1;
  652. if (cur.rotate && cur.rotate != 0) context.restore();
  653. },
  654. /**
  655. * Renders arc on canvas (circle or incomplete arc)
  656. *
  657. * @param {object} cur current media in media queue
  658. */
  659. _arc: function (cur) {
  660. const x = cur.xWithAnchor, y = cur.yWithAnchor;
  661. // Rotation
  662. if (cur.rotate && cur.rotate != 0) {
  663. context.save();
  664. context.translate(cur.x, cur.y);
  665. context.rotate(cur.rotate * Math.PI / 180);
  666. context.translate(-cur.x, -cur.y);
  667. }
  668. // Alpha
  669. context.globalAlpha = cur.alpha;
  670. // Shadow
  671. context.shadowBlur = (cur.shadow) ? cur.shadowBlur : 0;
  672. context.shadowColor = cur.shadowColor;
  673. // Fill info
  674. if (cur.fillColor != 0) context.fillStyle = cur.fillColor;
  675. // Stroke info
  676. if (cur.lineWidth != 0) {
  677. context.strokeStyle = cur.lineColor;
  678. context.lineWidth = cur.lineWidth;
  679. }
  680. // Path
  681. context.beginPath();
  682. if (cur.angleEnd != 2 * Math.PI) context.lineTo(x, y);
  683. context.arc(x, y, (cur.diameter / 2) * cur.scale, cur.angleStart, cur.angleEnd, cur.anticlockwise);
  684. if (cur.angleEnd != 2 * Math.PI) context.lineTo(x, y);
  685. // End
  686. if (cur.fillColor != 0) context.fill();
  687. if (cur.lineWidth != 0) context.stroke();
  688. context.shadowBlur = 0;
  689. context.globalAlpha = 1;
  690. if (cur.rotate && cur.rotate != 0) context.restore();
  691. },
  692. },
  693. /**
  694. * Renders all queued media on screen (called during game loop) <br><br>
  695. */
  696. all: function () {
  697. game.render.queue.forEach(cur => {
  698. switch (cur.typeOfMedia) {
  699. case 'image': this._image(cur); break;
  700. case 'sprite': this._sprite(cur); break;
  701. case 'text': this._text(cur); break;
  702. case 'rect': this._geom._rect(cur); break;
  703. case 'arc': this._geom._arc(cur); break;
  704. }
  705. });
  706. },
  707. /**
  708. * Clears all queued media (used when changing states) <br><br>
  709. */
  710. clear: function () {
  711. game.render.queue = [];
  712. }
  713. },
  714. /**
  715. * Math functions
  716. *
  717. * @namespace
  718. */
  719. math: {
  720. /**
  721. * Returns a random integer in a range
  722. *
  723. * @param {number} min smaller number
  724. * @param {number} max larger number
  725. * @returns {number} random integer in range
  726. */
  727. randomInRange: function (min, max) {
  728. min = Math.ceil(min);
  729. max = Math.floor(max);
  730. return Math.floor(Math.random() * (max - min + 1) + min); // The maximum is inclusive and the minimum is also inclusive
  731. },
  732. /**
  733. * Returns a random divisor for a given number
  734. *
  735. * @param {number} number number
  736. * @returns {number} random divisor for number
  737. */
  738. randomDivisor: function (number) {
  739. const validDivisors = []; // Divisors found
  740. for (let i = 2; i < number; i++) {
  741. // If 'number' can be divided by 'i', add to list of 'validDivisors'
  742. if (number % i == 0) validDivisors.push(i);
  743. }
  744. const randIndex = game.math.randomInRange(0, validDivisors.length - 1);
  745. return validDivisors[randIndex];
  746. },
  747. /**
  748. * Converts degree to radian
  749. *
  750. * @param {number} degree number in degrees
  751. * @returns {number} its radian equivalent
  752. */
  753. degreeToRad: function (degree) {
  754. return degree * Math.PI / 180;
  755. },
  756. /**
  757. * Returns distance from center of an icon to pointer (radius)
  758. *
  759. * @param {number} xMouse contains the mouse x coordinate
  760. * @param {number} xIcon contains the icon x coordinate
  761. * @param {number} yMouse contains the mouse y coordinate
  762. * @param {number} yIcon contains the icon y coordinate
  763. * @returns {number} distance between the two icons
  764. */
  765. distanceToPointer: function (xMouse, xIcon, yMouse, yIcon) {
  766. const a = Math.max(xMouse, xIcon) - Math.min(xMouse, xIcon);
  767. const b = Math.max(yMouse, yIcon) - Math.min(yMouse, yIcon);
  768. return Math.sqrt(a * a + b * b);
  769. },
  770. /**
  771. * Checks if mouse is over icon (rectangle)
  772. *
  773. * @param {number} xMouse contains the mouse x coordinate
  774. * @param {number} yMouse contains the mouse y coordinate
  775. * @param {object} icon icon
  776. * @returns {boolean} true if cursor is over icon
  777. */
  778. isOverIcon: function (xMouse, yMouse, icon) {
  779. const x = xMouse, y = yMouse, cur = icon;
  780. return y >= cur.yWithAnchor && y <= (cur.yWithAnchor + cur.height * cur.scale) &&
  781. (x >= cur.xWithAnchor && x <= (cur.xWithAnchor + cur.width * cur.scale));
  782. }
  783. },
  784. /**
  785. * Timer used to get the time spent to complete a game
  786. *
  787. * @namespace
  788. */
  789. timer: {
  790. _start: 0, // [Not directly used] Start time
  791. _end: 0, // [Not directly used] End time
  792. elapsed: 0, // [Not directly used] Elapsed time
  793. /**
  794. * Start timer
  795. */
  796. start: function () {
  797. game.timer._start = game.timer._end = game.timer._elapsed = 0; // Clear
  798. game.timer._start = new Date().getTime(); // Set start time
  799. },
  800. /**
  801. * Stop timer
  802. */
  803. stop: function () {
  804. if (game.timer._start != 0 && game.timer._end == 0) { // If timer has started but not finished
  805. game.timer._end = new Date().getTime(); // Set end time
  806. game.timer._elapsed = Math.floor((game.timer._end - game.timer._start) / 1000); // Set elapsed time
  807. }
  808. },
  809. get elapsed() { return game.timer._elapsed; }
  810. },
  811. /**
  812. * Handles (mouse) pointer events
  813. *
  814. * @namespace
  815. */
  816. event: {
  817. _list: [], // [Not directly used] List of events in current state
  818. /**
  819. * Create events
  820. *
  821. * @param {string} name name of event : 'click' or 'mousemove'
  822. * @param {function} func function to be called by triggered event
  823. */
  824. add: function (name, func) {
  825. canvas.addEventListener(name, func);
  826. game.event._list.push([name, func]);
  827. },
  828. /** [Not directly used] <br>
  829. * Clear list of events
  830. */
  831. clear: function () {
  832. game.event._list.forEach(cur => {
  833. canvas.removeEventListener(cur[0], cur[1]);
  834. });
  835. game.event._list = [];
  836. },
  837. },
  838. /** [Not directly used] <br>
  839. * Game loop - Handles repetition of function update() and sprite animation <br>
  840. * After the media queue is filled in create(), the game loop starts <br>
  841. * It calls update() iteratively and rerenders the screen
  842. *
  843. * @namespace
  844. */
  845. loop: {
  846. id: undefined, // [Not directly used] Holds animation event
  847. curState: undefined, // [Not directly used] State that called the loop
  848. status: 'off', // [Not directly used] Loop status can be : 'on', 'ending' or 'off'
  849. waitingToStart: undefined, // [Not directly used]
  850. startTime: 0, // [Not directly used]
  851. duration: 1000 / 60, // [Not directly used] 1000: 1 second | 60: expected frames per second
  852. /** [Not directly used] <br>
  853. * Start game loop
  854. *
  855. * @param {object} state current state
  856. */
  857. start: function (state) {
  858. if (game.loop.status == 'off') {
  859. game.loop.curState = state;
  860. game.loop.startTime = new Date().getTime();
  861. game.loop.status = 'on';
  862. game.loop.id = requestAnimationFrame(game.loop._run);
  863. } else { // If 'game.loop.status' is either 'on' or 'ending'
  864. game.loop.waitingToStart = state;
  865. if (game.loop.status == 'on') game.loop.stop();
  866. }
  867. },
  868. /**
  869. * [Not directly used] <br>
  870. * Stop game loop
  871. */
  872. stop: function () {
  873. if (game.loop.status == 'on') game.loop.status = 'ending';
  874. },
  875. /**
  876. * [Not directly used] <br>
  877. * Execute game loop
  878. */
  879. _run: function () {
  880. if (game.loop.status != 'on') {
  881. game.loop._clear();
  882. } else {
  883. const timestamp = new Date().getTime();
  884. const runtime = timestamp - game.loop.startTime;
  885. if (runtime >= game.loop.duration) {
  886. if (debugMode) {
  887. let fps = runtime / 1000;
  888. fps = Math.round(1 / fps);
  889. displayFps.innerHTML = 'Fps: ' + fps; // Show fps
  890. }
  891. // Update state
  892. game.loop.curState.update();
  893. // Animate state
  894. game.animation._run();
  895. }
  896. game.loop.id = requestAnimationFrame(game.loop._run);
  897. }
  898. },
  899. /**
  900. * [Not directly used] <br>
  901. * Resets game loop values
  902. */
  903. _clear: function () {
  904. if (game.loop.id != undefined) {
  905. cancelAnimationFrame(game.loop.id); // Cancel animation event
  906. game.loop.id = undefined; // Clear object that holds animation event
  907. game.loop.curState = undefined; // Clear object that holds current state
  908. game.loop.status = 'off'; // Inform animation must end (read in _run())
  909. displayFps.innerHTML = ''; // Stop showing fps
  910. }
  911. if (game.loop.waitingToStart != undefined) {
  912. const temp = game.loop.waitingToStart;
  913. game.loop.waitingToStart = undefined;
  914. game.loop.start(temp);
  915. }
  916. },
  917. },
  918. /**
  919. * Handles spritesheet animation <br>
  920. * Called by game loop <br>
  921. * Changes through frames in queued spritesheets
  922. *
  923. * @namespace
  924. */
  925. animation: {
  926. queue: [], // [Not directly used] Animation queue for current level
  927. count: 0, // [Not directly used]
  928. /**
  929. * Play animation
  930. *
  931. * @param {string} name animation name
  932. */
  933. play: function (name) {
  934. let newAnimation;
  935. // Gets first object that has that animation name (name) in game.render.queue
  936. for (let i in game.render.queue) {
  937. if (game.render.queue[i].animation != undefined && game.render.queue[i].animation[0] == name) {
  938. newAnimation = game.render.queue[i];
  939. break;
  940. }
  941. }
  942. // If found, saves object in game.animation.queue
  943. if (newAnimation != undefined) game.animation.queue.push(newAnimation);
  944. },
  945. /**
  946. * Stop animation
  947. *
  948. * @param {string} name animation name
  949. */
  950. stop: function (name) {
  951. // Remove all with that name is game.animation.queue
  952. game.animation.queue.forEach(cur => {
  953. if (cur.animation[0] == name) {
  954. game.animation.queue.splice(cur, 1);
  955. }
  956. });
  957. },
  958. /**
  959. * [Not directly used] <br>
  960. * Executes animation
  961. */
  962. _run: function () {
  963. game.animation.queue.forEach(character => {
  964. if (!character.animation[2] || game.animation.count % character.animation[2] == 0) {
  965. const i = character.animation[1].indexOf(character.curFrame);
  966. if (i == -1) { // Frame not found
  967. if (debugMode) console.error('Game error: animation frame not found');
  968. } else if (i < character.animation[1].length - 1) { // Go to next frame
  969. character.curFrame = character.animation[1][i + 1];
  970. } else {
  971. character.curFrame = character.animation[1][0]; // If last frame, restart
  972. }
  973. }
  974. });
  975. game.animation.count++;
  976. },
  977. /**
  978. * [Not directly used] <br>
  979. * Clear animation queue <br><br>
  980. *
  981. * game.animation.clear()
  982. */
  983. clear: function () {
  984. // Resets animation count
  985. game.animation.count = 0;
  986. // Clears property 'animation' from objects in game.render.queue
  987. game.render.queue.forEach(cur => {
  988. if (cur.animation != undefined) {
  989. delete cur.animation;
  990. }
  991. });
  992. // Clears game.animation.queue
  993. game.animation.queue = [];
  994. },
  995. },
  996. /**
  997. * Handles game states <br>
  998. * When an state is associated with an object, its preload(), create() and update() functions <br>
  999. * will be executed acconrding to these rules: <br>
  1000. * * preload() : first function to be called when state is called. Loads media. Runs only once.(optional) <br>
  1001. * * create() : called right after preload(). Where the main code goes. Runs only once. <br>
  1002. * * update() : called right after create(). Is iteratively called by game loop until end of the state. (optional)
  1003. *
  1004. * @namespace
  1005. */
  1006. state: {
  1007. list: [], // [Not directly used]
  1008. name: undefined, // [Not directly used]
  1009. /**
  1010. * Create new state
  1011. *
  1012. * @param {string} name state name
  1013. * @param {object} obj object that should be called when accessing the state
  1014. */
  1015. add: function (name, obj) {
  1016. game.state.list[name] = obj;
  1017. },
  1018. /**
  1019. * Start new state
  1020. *
  1021. * @param {string} name state name
  1022. */
  1023. start: function (name) {
  1024. document.body.style.cursor = 'auto';
  1025. game.loop.stop();
  1026. game.event.clear();
  1027. game.animation.clear();
  1028. game.state.name = name;
  1029. self = game.state.list[name];
  1030. if (self.preload) self.preload();
  1031. else game.state._create();
  1032. },
  1033. /**
  1034. * [Not directly used] <br>
  1035. * Encapsulate create() function in the current state
  1036. */
  1037. _create: function () {
  1038. game.render.clear();
  1039. self.create();
  1040. game.render.all();
  1041. if (self.restart && self.restart == true) {
  1042. game.state.start(game.state.name);
  1043. } else {
  1044. if (self.update) game.loop.start(self);
  1045. }
  1046. },
  1047. },
  1048. };