PageRenderTime 52ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/src/webgame.js

https://github.com/mkrause/webgame.js
JavaScript | 324 lines | 228 code | 55 blank | 41 comment | 42 complexity | 9eedef6403599825bc8e2572b21790a5 MD5 | raw file
  1. /*
  2. * WebGame.js v0.2
  3. * Basic 2D game framework using HTML5 <canvas>
  4. *
  5. * Copyright (c) 2010 Maikel Krause
  6. * Released under the MIT license.
  7. *
  8. * Date: Januari 2010
  9. */
  10. (function() {
  11. var WebGame = window.WebGame = function(canvas)
  12. {
  13. if (typeof(canvas) === 'string') {
  14. canvas = document.getElementById(canvas);
  15. }
  16. if (!canvas || !canvas.getContext) {
  17. throw 'WebGame: could not obtain a rendering context';
  18. }
  19. this.canvas = canvas;
  20. this.width = canvas.width;
  21. this.height = canvas.height;
  22. this.ctx = canvas.getContext('2d');
  23. if (!this.ctx) {
  24. throw 'WebGame: canvas 2D rendering context not supported';
  25. }
  26. //FIXME: don't always want to catch events like mousemove,
  27. // perhaps only when a mousemove handler is defined?
  28. this.catchKeyEvents();
  29. this.catchMouseEvents();
  30. this.catchMouseMove();
  31. };
  32. WebGame.prototype =
  33. {
  34. version: '0.2',
  35. canvas: null,
  36. ctx: null,
  37. width: 0,
  38. height: 0,
  39. loopHandle: null,
  40. controller: null,
  41. frameDelay: 20, // Target delay (ms) between frames
  42. updateDelay: 10, // Target delay (ms) between updates
  43. lastDelay: 0, // Measured delay of the last frame
  44. frames: 0,
  45. updates: 0,
  46. frameTime: 0, // Time stamp as of start of the current frame
  47. accumulator: 0, // Accumulates the time (ms) left to perform updates
  48. //-------------------------------------------------------
  49. // Game loop
  50. //-------------------------------------------------------
  51. setController: function(controller)
  52. {
  53. this.controller = controller;
  54. if (this.controller && typeof(this.controller.wakeUp) === 'function') {
  55. this.controller.wakeUp();
  56. }
  57. },
  58. getController: function()
  59. {
  60. return this.controller;
  61. },
  62. setFrameRate: function(fps)
  63. {
  64. if (fps <= 0) return;
  65. this.frameDelay = 1000 / fps;
  66. if (this.isRunning()) {
  67. // Reset the interval
  68. this.stop();
  69. this.run();
  70. }
  71. },
  72. setUpdateRate: function(updateRate)
  73. {
  74. this.updateDelay = 1000 / updateRate;
  75. },
  76. isRunning: function()
  77. {
  78. return !!this.loopHandle;
  79. },
  80. run: function()
  81. {
  82. var that = this;
  83. if (this.isRunning()) {
  84. return;
  85. }
  86. this.frameTime = (new Date).getTime();
  87. this.loopHandle = window.setInterval(function() { that.frame() }, this.frameDelay);
  88. // Besides the interval, we also want a frame right away (but async, so use a time-out)
  89. window.setTimeout(function() { that.frame() }, 0);
  90. },
  91. stop: function()
  92. {
  93. window.clearInterval(this.loopHandle);
  94. this.loopHandle = null;
  95. },
  96. frame: function()
  97. {
  98. // Get the time since the last frame in ms
  99. var now = (new Date).getTime();
  100. var delay = now - this.frameTime;
  101. this.frameTime = now;
  102. // Save delay (for instance, to calculate the frame rate)
  103. this.lastDelay = delay;
  104. var alpha = false;
  105. if (this.controller && typeof(this.controller.update) === 'function') {
  106. // If we have an abnormally long delay, clamp to prevent overprocessing.
  107. // This means that, during a long delay (low FPS), the game slows down.
  108. delay = Math.min(delay, 2 * this.frameDelay);
  109. var dt = this.updateDelay;
  110. // Accumulate another 'delay' ms worth of updates to perform
  111. this.accumulator += delay;
  112. // Perform as many updates as can fit in the accumulated time
  113. while (this.accumulator >= dt) {
  114. this.controller.update(dt);
  115. this.accumulator -= dt;
  116. this.updates++;
  117. }
  118. // Interpolation alpha (e.g. when we're halfway between updates, alpha is 0.5)
  119. // This allows for predictive drawing to smooth out animations
  120. alpha = this.accumulator / dt;
  121. }
  122. if (this.controller && typeof(this.controller.draw) === 'function') {
  123. this.ctx.save();
  124. this.controller.draw(this.ctx, alpha);
  125. this.ctx.restore();
  126. }
  127. this.frames++;
  128. },
  129. //-------------------------------------------------------
  130. // I/O, events
  131. //-------------------------------------------------------
  132. // NOTE: intentionally ignoring event handling in IE for now
  133. keys: {
  134. 0: 48, 1: 49, 2: 50, 3: 51, 4: 52, 5: 53, 6: 54, 7: 55, 8: 56, 9: 57,
  135. a: 65, b: 66, c: 67, d: 68, e: 69, f: 70, g: 71, h: 72, i: 73, j: 74, k: 75,
  136. l: 76, m: 77, n: 78, o: 79, p: 80, q: 81, r: 82, s: 83, t: 84, u: 85, v: 86,
  137. w: 87, x: 88, y: 89, z: 90, up: 38, left: 37, down: 40, right: 39, enter: 13,
  138. shift: 16, ctrl: 17, alt: 18, escape: 27, space: 32
  139. },
  140. catchingKeyEvents: false,
  141. catchingMouseEvents: false,
  142. catchingMouseMove: false,
  143. keyStates: {},
  144. mouseState: false,
  145. catchKeyEvents: function()
  146. {
  147. var that = this;
  148. if (!this.catchingKeyEvents) {
  149. this.canvas.addEventListener('keydown', function(evt) { that.handleKeyEvent(evt, true); }, false);
  150. this.canvas.addEventListener('keyup', function(evt) { that.handleKeyEvent(evt, false); }, false);
  151. }
  152. this.catchingKeyEvents = true;
  153. },
  154. catchMouseEvents: function()
  155. {
  156. var that = this;
  157. if (!this.catchingMouseEvents) {
  158. this.canvas.addEventListener('mousedown', function(evt) { that.handleMouseEvent(evt, true); }, false);
  159. this.canvas.addEventListener('mouseup', function(evt) { that.handleMouseEvent(evt, false); }, false);
  160. }
  161. this.catchingMouseEvents = true;
  162. },
  163. catchMouseMove: function()
  164. {
  165. var that = this;
  166. if (!this.catchingMouseMove) {
  167. this.canvas.addEventListener('mousemove', function(evt) { that.handleMouseMove(evt, false); }, false);
  168. }
  169. this.catchingMouseMove = true;
  170. },
  171. handleKeyEvent: function(evt, pressed)
  172. {
  173. // Prevent arrow keys scrolling the page
  174. if (evt.keyCode == this.keys.up || evt.keyCode == this.keys.down ||
  175. evt.keyCode == this.keys.left || evt.keyCode == this.keys.right) {
  176. evt.preventDefault();
  177. }
  178. if (pressed) {
  179. this.keyStates[evt.keyCode] = true;
  180. } else {
  181. delete this.keyStates[evt.keyCode];
  182. }
  183. if (this.controller && typeof(this.controller.keyEvent) === 'function') {
  184. this.controller.keyEvent(evt.keyCode, pressed);
  185. }
  186. },
  187. // TODO: track which mouse button was pressed
  188. handleMouseEvent: function(evt, pressed)
  189. {
  190. this.mouseState = !!pressed;
  191. if (this.controller && typeof(this.controller.mouseEvent) === 'function') {
  192. this.controller.mouseEvent(pressed);
  193. }
  194. },
  195. // http://www.quirksmode.org/js/events_properties.html
  196. handleMouseMove: function(evt)
  197. {
  198. var mouseX = 0;
  199. var mouseY = 0;
  200. if (evt.pageX || evt.pageY) {
  201. mouseX = evt.pageX;
  202. mouseY = evt.pageY;
  203. }
  204. else if (evt.clientX || evt.clientY) {
  205. mouseX = evt.clientX + document.body.scrollLeft
  206. + document.documentElement.scrollLeft;
  207. mouseY = evt.clientY + document.body.scrollTop
  208. + document.documentElement.scrollTop;
  209. }
  210. // Find position of this.canvas
  211. // http://www.quirksmode.org/js/findpos.html
  212. var elmt = this.canvas;
  213. var curleft = 0;
  214. var curtop = 0;
  215. if (elmt.offsetParent) {
  216. do {
  217. curleft += elmt.offsetLeft;
  218. curtop += elmt.offsetTop;
  219. } while (elmt = elmt.offsetParent);
  220. }
  221. // Mouse coordinates relative to this.canvas
  222. var relX = mouseX - curleft;
  223. var relY = mouseY - curtop;
  224. if (this.controller && typeof(this.controller.mouseMoveEvent) === 'function') {
  225. this.controller.mouseMoveEvent(relX, relY);
  226. }
  227. },
  228. keyPressed: function(key)
  229. {
  230. return !!this.keyStates[this.keys[key]];
  231. },
  232. mouseDown: function()
  233. {
  234. return this.mouseState;
  235. },
  236. //-------------------------------------------------------
  237. // Resources/preloading
  238. //-------------------------------------------------------
  239. imageCache: {},
  240. audioCache: {},
  241. loadingImage: function()
  242. {
  243. for (var i in this.imageCache) {
  244. if (!this.imageCache[i].complete) {
  245. return true;
  246. }
  247. }
  248. return false;
  249. },
  250. loadImage: function(filePath)
  251. {
  252. var cachedFiles = this.imageCache;
  253. // Did we already load this file?
  254. if (cachedFiles[filePath]) {
  255. return cachedFiles[filePath];
  256. }
  257. var file = new Image();
  258. //file.onload = function() { trace("Resource loaded: " + filePath); };
  259. //file.onerror = function() { trace("No dice: " + filePath); };
  260. file.src = filePath;
  261. cachedFiles[filePath] = file;
  262. return file;
  263. },
  264. loadAudio: function(filePath, loadFunc)
  265. {
  266. //TODO
  267. }
  268. };
  269. })();