GameHandler.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. /************************************************************************
  2. * GameHandler.js
  3. ************************************************************************
  4. * Copyright (c) 2021 Pedro Tonini Rosenberg Schneider.
  5. *
  6. * This file is part of Pandora.
  7. *
  8. * Pandora is free software: you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License as published by
  10. * the Free Software Foundation, either version 3 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * Pandora is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with Pandora. If not, see <https://www.gnu.org/licenses/>.
  20. *************************************************************************/
  21. /**
  22. * This {@code GameHandler} singleton provides an interface for the user
  23. * to manipulate various parameters of the game, instance objects, and more.
  24. *
  25. * @author Pedro Schneider
  26. *
  27. * @namespace
  28. */
  29. const GameHandler = {
  30. nextId: 0, // ID to be given to the next object added to the tree.
  31. rootObjects: [], // List of objects on the root of the tree.
  32. renderMode: 1, // Can be RENDER_MODES.P2D or RENDER_MODES.WEBGL.
  33. bDrawDebugFPS: false, // Should fps be drawn (for debug only).
  34. debugFpsLabel: null, // Object that drwas fps.
  35. prevMillis: 0, // Milliseconds ellapsed since the begining of the application.
  36. delta: 0, // Milliseconds ellapsed since the last frame.
  37. db: null, // Object to hold the secondary buffer.
  38. dbWidth: 1920, // Width of the secondary buffer.
  39. dbHeight: 1080, // Height of the secondary buffer.
  40. isMobile: null, // True if the device is a mobile device (tablet of phone).
  41. pixelDen: 1, // Pixel density for the canvas on destop devices.
  42. pixelDenMobile: 2, // Pixel denisty for the canvas on mobile devices.
  43. mouseX: 0, // X position of the mouse relative to the secondary buffer.
  44. mouseY: 0, // Y position of the mouse relative to the secondary buffer.
  45. /**
  46. * Sets the initial game render mode.
  47. *
  48. * @param {RENDER_MODES} mode RENDER_MODES.P2D for default P5Js render or
  49. * RENDER_MODES.WEBGL for webgl (not recomended for mobile).
  50. */
  51. setRenderMode: function(mode)
  52. {
  53. this.renderMode = mode;
  54. },
  55. /**
  56. * Sets the width and height in pixels to initialize the secondary buffer.
  57. *
  58. * @param {number} w width in pixels to initialize the secondary buffer.
  59. * @param {number} h height in pixels to initialize the secondary buffer.
  60. */
  61. setDoubleBufferSize: function(w, h)
  62. {
  63. this.dbWidth = w;
  64. this.dbHeight = h;
  65. },
  66. /**
  67. * Sets the pixel density for the canvas to be initialized with on desktop
  68. * devices.
  69. *
  70. * @param {number} val pixel density for the canvas on desktop devices.
  71. */
  72. setPixelDensity: function(val)
  73. {
  74. this.pixelDen = val;
  75. },
  76. /**
  77. * Sets the pixel density for the canvas to be initialized with on desktop
  78. * devices.
  79. *
  80. * @param {number} val pixel density for the canvas on desktop devices.
  81. */
  82. setPixelDensityMobile: function(val)
  83. {
  84. this.pixelDenMobile = val;
  85. },
  86. /**
  87. * Sets the flag to draw the debug fps.
  88. *
  89. * @param {boolean} val true if debug fps should be drawn, false if not.
  90. */
  91. drawDebugFPS(val)
  92. {
  93. this.bDrawDebugFPS = val;
  94. },
  95. /**
  96. * Initializes the game, creating the canvas, secondary buffer, and creates the
  97. * debug fps label if necessary.
  98. *
  99. * @param {number} fps target fps for the game (default if 60).
  100. */
  101. init: function(fps = 60)
  102. {
  103. // Sets the mobile flag.
  104. this.isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
  105. // Creates the main canvas and the secondary buffer with the specified size and render mode.
  106. switch (this.renderMode)
  107. {
  108. case RENDER_MODES.P2D:
  109. createCanvas(windowWidth, windowHeight);
  110. this.db = createGraphics(this.dbWidth, this.dbHeight);
  111. break;
  112. case RENDER_MODES.WEBGL:
  113. createCanvas(windowWidth, windowHeight, WEBGL);
  114. this.db = createGraphics(this.dbWidth, this.dbHeight, WEBGL);
  115. this.db.smooth();
  116. break;
  117. }
  118. // Sets framerate and pixel density accordingly.
  119. frameRate(fps);
  120. if (this.isMobile)
  121. pixelDensity(this.pixelDenMobile);
  122. else
  123. pixelDensity(this.pixelDen);
  124. smooth();
  125. // Translates the canvas to the middle if render mode is webgl to maintain
  126. // consistency on the coordinate system.
  127. if (this.renderMode == RENDER_MODES.WEBGL)
  128. {
  129. translate(-windowWidth / 2, -windowHeight / 2);
  130. db.translate(-this.dbWidth / 2, -this.dbHeight / 2);
  131. }
  132. // Creates the debug fps label.
  133. if (this.bDrawDebugFPS)
  134. {
  135. this.debugFpsLabel = new Label("debugFps", `FPS: ${frameRate()}`);
  136. this.addRootObject(this.debugFpsLabel);
  137. }
  138. },
  139. /**
  140. * Instances a GameObject, meaning to give it an ID. This function is only called on the
  141. * constructor of GameObject, and probably shouldn't be used for anything else.
  142. *
  143. * @param {GameObject} obj GameObject to be instanced.
  144. */
  145. instanceGameObject: function(obj)
  146. {
  147. obj.id = this.nextId;
  148. this.nextId++;
  149. },
  150. /**
  151. * Adds a GameObject to the root of the tree. There should be as little root objects as possible.
  152. *
  153. * @param {GameObject} obj GameObject to be added as a root of the tree.
  154. */
  155. addRootObject: function(obj)
  156. {
  157. this.rootObjects.push(obj);
  158. obj.isRoot = true;
  159. obj.setup();
  160. },
  161. /**
  162. * Removes a GameObject from the root of the tree. This function is called automatically when a root object
  163. * is freed from memory, and probably shoudn't be used for anything else. DOES NOT DELETE THE OBJECT, ONLY
  164. * REMOVES IT FROM THE TREE.
  165. *
  166. * @param {number} id object id of the GameObject that should be removed from the tree.
  167. */
  168. removeRootObjectById: function(id)
  169. {
  170. for (let i = 0; i < this.rootObjects.length; i++)
  171. {
  172. if (this.rootObjects[i].id == id)
  173. this.rootObjects.splice(i, 1);
  174. }
  175. },
  176. upframecount: 0, // Frame count to be displayed.
  177. upframenum: 20, // Delay in frames to update the frame count.
  178. /**
  179. * Updates all of the GameObjects on the tree.
  180. */
  181. update: function()
  182. {
  183. // Updates the debug fps label if it existis.
  184. if (this.bDrawDebugFPS)
  185. {
  186. if (frameCount % this.upframenum == 0)
  187. {
  188. this.debugFpsLabel.setText(`FPS: ${
  189. Math.round(this.upframecount * 1000) / 1000
  190. }`);
  191. this.upframecount = 0;
  192. }
  193. else
  194. this.upframecount = max(this.upframecount, frameRate());
  195. }
  196. // Updates the delta.
  197. this.delta = (millis() - this.prevMillis) / 1000;
  198. let ar = this.db.screenWidth / this.db.width;
  199. let offsetx = (windowWidth - this.db.screenWidth) / 2;
  200. let offsety = (windowHeight - this.db.screenHeight) / 2;
  201. this.mouseX = (mouseX - offsetx) / ar;
  202. this.mouseY = (mouseY - offsety) / ar;
  203. // Updates all game objects on the tree.
  204. for (let i = 0; i < this.rootObjects.length; i++)
  205. this.rootObjects[i].update(this.delta);
  206. },
  207. /**
  208. * Draws all of the GameObjects on the tree.
  209. */
  210. draw: function()
  211. {
  212. // Clear the secondary buffer.
  213. this.db.clear();
  214. // Draw a rectangle to visualize the secondary buffer.
  215. // TODO: remove this
  216. this.db.push();
  217. this.db.strokeWeight(5);
  218. this.db.noFill();
  219. this.db.rect(0, 0, this.dbWidth, this.dbHeight);
  220. this.db.pop();
  221. // Centers the image and calculates the dimensions of the secondary
  222. // buffer to best fit the size of the window.
  223. imageMode(CENTER);
  224. if (windowWidth / windowHeight < this.dbWidth / this.dbHeight)
  225. {
  226. this.db.screenWidth = windowWidth;
  227. this.db.screenHeight = windowWidth * (this.dbHeight / this.dbWidth);
  228. }
  229. else
  230. {
  231. this.db.screenHeight = windowHeight;
  232. this.db.screenWidth = windowHeight * (this.dbWidth / this.dbHeight);
  233. }
  234. // Draw all game objects.
  235. for (let i = 0; i < this.rootObjects.length; i++)
  236. this.rootObjects[i].draw(this.delta, this.db);
  237. // Draws the secondary buffer to the main canvas.
  238. image(this.db, windowWidth / 2, windowHeight / 2, this.db.screenWidth, this.db.screenHeight);
  239. // Updates the delta
  240. this.prevMillis = millis();
  241. }
  242. }
  243. /**
  244. * This function is called once every time the browser window is resized. Here, its used to make the game
  245. * always ocupy the entire browser window.
  246. *
  247. * @callback
  248. */
  249. function windowResized()
  250. {
  251. resizeCanvas(windowWidth, windowHeight);
  252. }