gameMechanics.js 37 KB

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