GameHandler.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  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. bDrawDebugBufferBounds: false, // Should the secondary buffer's bounds be drawn?
  36. prevMillis: 0, // Milliseconds ellapsed since the begining of the application.
  37. delta: 0, // Milliseconds ellapsed since the last frame.
  38. db: null, // Object to hold the secondary buffer.
  39. dbWidth: 1920, // Width of the secondary buffer.
  40. dbHeight: 1080, // Height of the secondary buffer.
  41. isMobile: null, // True if the device is a mobile device (tablet of phone).
  42. pixelDen: 1, // Pixel density for the canvas on destop devices.
  43. pixelDenMobile: 2, // Pixel denisty for the canvas on mobile devices.
  44. mouseX: 0, // X position of the mouse relative to the secondary buffer.
  45. mouseY: 0, // Y position of the mouse relative to the secondary buffer.
  46. pmouseX: 0, // X position of the mouse relative to the secondary buffer on the previous frame.
  47. pmouseY: 0, // Y position of the mouse relative to the secondary buffer on the previous frame.
  48. backgroundColor: null, // Default color to be drawn to the background.
  49. /**
  50. * Sets the initial game render mode.
  51. *
  52. * @param {RENDER_MODES} mode RENDER_MODES.P2D for default P5Js render or
  53. * RENDER_MODES.WEBGL for webgl (not recomended for mobile).
  54. */
  55. setRenderMode(mode)
  56. {
  57. this.renderMode = mode;
  58. },
  59. /**
  60. * Sets the width and height in pixels to initialize the secondary buffer.
  61. *
  62. * @param {number} w width in pixels to initialize the secondary buffer.
  63. * @param {number} h height in pixels to initialize the secondary buffer.
  64. */
  65. setDoubleBufferSize(w, h)
  66. {
  67. this.dbWidth = w;
  68. this.dbHeight = h;
  69. },
  70. /**
  71. * Sets the pixel density for the canvas to be initialized with on desktop
  72. * devices.
  73. *
  74. * @param {number} val pixel density for the canvas on desktop devices.
  75. */
  76. setPixelDensity(val)
  77. {
  78. this.pixelDen = val;
  79. },
  80. /**
  81. * Sets the pixel density for the canvas to be initialized with on desktop
  82. * devices.
  83. *
  84. * @param {number} val pixel density for the canvas on desktop devices.
  85. */
  86. setPixelDensityMobile(val)
  87. {
  88. this.pixelDenMobile = val;
  89. },
  90. /**
  91. * Sets the default color to be drawn to the main buffer's background.
  92. *
  93. * @param {Color} col new background color;
  94. */
  95. setBackgroundColor(col)
  96. {
  97. this.backgroundColor = col;
  98. },
  99. /**
  100. * Sets the flag to draw the debug fps.
  101. *
  102. * @param {boolean} val true if debug fps should be drawn, false if not.
  103. */
  104. drawDebugFPS(val)
  105. {
  106. this.bDrawDebugFPS = val;
  107. },
  108. /**
  109. * Sets the flag to draw secondary buffer bounds.
  110. *
  111. * @param {boolean} val true if debug secondary buffer bounds should be drawn, false if not.
  112. */
  113. drawDebugBufferBounds(val)
  114. {
  115. this.bDrawDebugBufferBounds = val;
  116. },
  117. /**
  118. * Initializes the game, creating the canvas, secondary buffer, and creates the
  119. * debug fps label if necessary.
  120. *
  121. * @param {number} fps target fps for the game (default if 60).
  122. */
  123. init(fps = 60)
  124. {
  125. // Sets the mobile flag.
  126. this.isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
  127. // Creates the main canvas and the secondary buffer with the specified size and render mode.
  128. switch (this.renderMode)
  129. {
  130. case RENDER_MODES.P2D:
  131. createCanvas(windowWidth, windowHeight);
  132. this.db = createGraphics(this.dbWidth, this.dbHeight);
  133. break;
  134. case RENDER_MODES.WEBGL:
  135. createCanvas(windowWidth, windowHeight, WEBGL);
  136. this.db = createGraphics(this.dbWidth, this.dbHeight, WEBGL);
  137. this.db.smooth();
  138. break;
  139. }
  140. // Sets framerate and pixel density accordingly.
  141. frameRate(fps);
  142. if (this.isMobile)
  143. pixelDensity(this.pixelDenMobile);
  144. else
  145. pixelDensity(this.pixelDen);
  146. smooth();
  147. // Translates the canvas to the middle if render mode is webgl to maintain
  148. // consistency on the coordinate system.
  149. if (this.renderMode == RENDER_MODES.WEBGL)
  150. {
  151. translate(-windowWidth / 2, -windowHeight / 2);
  152. db.translate(-this.dbWidth / 2, -this.dbHeight / 2);
  153. }
  154. // Creates the debug fps label.
  155. if (this.bDrawDebugFPS)
  156. {
  157. this.debugFpsLabel = new Label("debugFps", `FPS: ${frameRate()}`);
  158. this.addRootObject(this.debugFpsLabel);
  159. }
  160. },
  161. /**
  162. * Instances a GameObject, meaning to give it an ID. This function is only called on the
  163. * constructor of GameObject, and probably shouldn't be used for anything else.
  164. *
  165. * @param {GameObject} obj GameObject to be instanced.
  166. */
  167. instanceGameObject(obj)
  168. {
  169. obj.id = this.nextId;
  170. this.nextId++;
  171. },
  172. /**
  173. * Adds a GameObject to the root of the tree. There should be as little root objects as possible.
  174. *
  175. * @param {GameObject} obj GameObject to be added as a root of the tree.
  176. */
  177. addRootObject(obj)
  178. {
  179. this.rootObjects.push(obj);
  180. obj.isRoot = true;
  181. obj.setup();
  182. },
  183. /**
  184. * Removes a GameObject from the root of the tree. This function is called automatically when a root object
  185. * is freed from memory, and probably shoudn't be used for anything else. DOES NOT DELETE THE OBJECT, ONLY
  186. * REMOVES IT FROM THE TREE.
  187. *
  188. * @param {number} id object id of the GameObject that should be removed from the tree.
  189. */
  190. removeRootObjectById(id)
  191. {
  192. for (let i = 0; i < this.rootObjects.length; i++)
  193. {
  194. if (this.rootObjects[i].id == id)
  195. this.rootObjects.splice(i, 1);
  196. }
  197. },
  198. upframecount: 0, // Frame count to be displayed.
  199. upframenum: 20, // Delay in frames to update the frame count.
  200. /**
  201. * Updates all of the GameObjects on the tree.
  202. */
  203. update()
  204. {
  205. // Updates the debug fps label if it existis.
  206. if (this.bDrawDebugFPS)
  207. {
  208. if (frameCount % this.upframenum == 0)
  209. {
  210. this.debugFpsLabel.setText(`FPS: ${
  211. Math.round(this.upframecount * 1000) / 1000
  212. }`);
  213. this.upframecount = 0;
  214. }
  215. else
  216. this.upframecount = max(this.upframecount, frameRate());
  217. }
  218. // Updates the delta.
  219. this.delta = (millis() - this.prevMillis) / 1000;
  220. // Update mouse position relative to the secondary buffer.
  221. this.pmouseX = this.mouseX;
  222. this.pmouseY = this.mouseY;
  223. let ar = this.db.screenWidth / this.db.width;
  224. let offsetx = (windowWidth - this.db.screenWidth) / 2;
  225. let offsety = (windowHeight - this.db.screenHeight) / 2;
  226. this.mouseX = (mouseX - offsetx) / ar;
  227. this.mouseY = (mouseY - offsety) / ar;
  228. // Updates all game objects on the tree.
  229. for (let i = 0; i < this.rootObjects.length; i++)
  230. this.rootObjects[i].update(this.delta);
  231. },
  232. /**
  233. * Draws all of the GameObjects on the tree.
  234. */
  235. draw()
  236. {
  237. // Clear the secondary buffer.
  238. this.db.clear();
  239. if (this.bDrawDebugBufferBounds)
  240. {
  241. // Draw a rectangle to visualize the secondary buffer.
  242. this.db.push();
  243. this.db.strokeWeight(5);
  244. this.db.noFill();
  245. this.db.rect(0, 0, this.dbWidth, this.dbHeight);
  246. this.db.pop();
  247. }
  248. // Centers the image and calculates the dimensions of the secondary
  249. // buffer to best fit the size of the window.
  250. imageMode(CENTER);
  251. if (windowWidth / windowHeight < this.dbWidth / this.dbHeight)
  252. {
  253. this.db.screenWidth = windowWidth;
  254. this.db.screenHeight = windowWidth * (this.dbHeight / this.dbWidth);
  255. }
  256. else
  257. {
  258. this.db.screenHeight = windowHeight;
  259. this.db.screenWidth = windowHeight * (this.dbWidth / this.dbHeight);
  260. }
  261. this.db.ellipse(this.pmouseX, this.pmouseY, 20);
  262. this.db.line(this.pmouseX, this.pmouseY, this.mouseX, this.mouseY);
  263. // Draw all game objects.
  264. for (let i = 0; i < this.rootObjects.length; i++)
  265. this.rootObjects[i].draw(this.delta, this.db);
  266. // Draws the secondary buffer to the main canvas.
  267. image(this.db, windowWidth / 2, windowHeight / 2, this.db.screenWidth, this.db.screenHeight);
  268. // Updates the delta
  269. this.prevMillis = millis();
  270. },
  271. /**
  272. * ! This function should be overriden, it provides no default functionality.
  273. * This function is called once when the page loads, and should be used by the user to load
  274. * assets and other forms of data that need to be already loaded when the prorgam starts.
  275. *
  276. * @callback
  277. */
  278. _preload()
  279. {
  280. },
  281. /**
  282. * ! This function should be overriden, it provides no default functionality.
  283. * This function is called once when the program starts, and should be used by the user to
  284. * initialize any necessary aspects of the game.
  285. *
  286. * @callback
  287. */
  288. _setup()
  289. {
  290. },
  291. }
  292. /**
  293. * This function is called once when the page loads. Serves to load assets and other
  294. * data that needs to be loaded when the program starts.
  295. *
  296. * @callback
  297. */
  298. function preload()
  299. {
  300. GameHandler._preload();
  301. }
  302. /**
  303. * This function is called once at the start of the program. Serves to initialize several
  304. * aspects of the game.
  305. *
  306. * @callback
  307. */
  308. function setup()
  309. {
  310. GameHandler._setup();
  311. GameHandler.init();
  312. }
  313. /**
  314. * This function is called once every frame. Serves to update and draw all GameObjects.
  315. *
  316. * @callback
  317. */
  318. function draw()
  319. {
  320. if (GameHandler.backgroundColor)
  321. background(GameHandler.backgroundColor.getP5Color());
  322. else
  323. background(200);
  324. GameHandler.update();
  325. GameHandler.draw();
  326. }
  327. /**
  328. * This function is called once every time the browser window is resized. Here, its used to make the game
  329. * always ocupy the entire browser window.
  330. *
  331. * @callback
  332. */
  333. function windowResized()
  334. {
  335. resizeCanvas(windowWidth, windowHeight);
  336. }