|
| 1 | +/* Engine.js |
| 2 | + * This file provides the game loop functionality (update entities and render), |
| 3 | + * draws the initial game board on the screen, and then calls the update and |
| 4 | + * render methods on your player and enemy objects (defined in your app.js). |
| 5 | + * |
| 6 | + * A game engine works by drawing the entire game screen over and over, kind of |
| 7 | + * like a flipbook you may have created as a kid. When your player moves across |
| 8 | + * the screen, it may look like just that image/character is moving or being |
| 9 | + * drawn but that is not the case. What's really happening is the entire "scene" |
| 10 | + * is being drawn over and over, presenting the illusion of animation. |
| 11 | + * |
| 12 | + * This engine is available globally via the Engine variable and it also makes |
| 13 | + * the canvas' context (ctx) object globally available to make writing app.js |
| 14 | + * a little simpler to work with. |
| 15 | + */ |
| 16 | + |
| 17 | +var Engine = (function(global) { |
| 18 | + /* Predefine the variables we'll be using within this scope, |
| 19 | + * create the canvas element, grab the 2D context for that canvas |
| 20 | + * set the canvas elements height/width and add it to the DOM. |
| 21 | + */ |
| 22 | + var doc = global.document, |
| 23 | + win = global.window, |
| 24 | + canvas = doc.createElement('canvas'), |
| 25 | + ctx = canvas.getContext('2d'), |
| 26 | + lastTime; |
| 27 | + |
| 28 | + canvas.width = 505; |
| 29 | + canvas.height = 606; |
| 30 | + doc.body.appendChild(canvas); |
| 31 | + |
| 32 | + /* This function serves as the kickoff point for the game loop itself |
| 33 | + * and handles properly calling the update and render methods. |
| 34 | + */ |
| 35 | + function main() { |
| 36 | + /* Get our time delta information which is required if your game |
| 37 | + * requires smooth animation. Because everyone's computer processes |
| 38 | + * instructions at different speeds we need a constant value that |
| 39 | + * would be the same for everyone (regardless of how fast their |
| 40 | + * computer is) - hurray time! |
| 41 | + */ |
| 42 | + var now = Date.now(), |
| 43 | + dt = (now - lastTime) / 1000.0; |
| 44 | + |
| 45 | + /* Call our update/render functions, pass along the time delta to |
| 46 | + * our update function since it may be used for smooth animation. |
| 47 | + */ |
| 48 | + update(dt); |
| 49 | + render(); |
| 50 | + |
| 51 | + /* Set our lastTime variable which is used to determine the time delta |
| 52 | + * for the next time this function is called. |
| 53 | + */ |
| 54 | + lastTime = now; |
| 55 | + |
| 56 | + /* Use the browser's requestAnimationFrame function to call this |
| 57 | + * function again as soon as the browser is able to draw another frame. |
| 58 | + */ |
| 59 | + win.requestAnimationFrame(main); |
| 60 | + } |
| 61 | + |
| 62 | + /* This function does some initial setup that should only occur once, |
| 63 | + * particularly setting the lastTime variable that is required for the |
| 64 | + * game loop. |
| 65 | + */ |
| 66 | + function init() { |
| 67 | + reset(); |
| 68 | + lastTime = Date.now(); |
| 69 | + main(); |
| 70 | + } |
| 71 | + |
| 72 | + /* This function is called by main (our game loop) and itself calls all |
| 73 | + * of the functions which may need to update entity's data. Based on how |
| 74 | + * you implement your collision detection (when two entities occupy the |
| 75 | + * same space, for instance when your character should die), you may find |
| 76 | + * the need to add an additional function call here. For now, we've left |
| 77 | + * it commented out - you may or may not want to implement this |
| 78 | + * functionality this way (you could just implement collision detection |
| 79 | + * on the entities themselves within your app.js file). |
| 80 | + */ |
| 81 | + function update(dt) { |
| 82 | + updateEntities(dt); |
| 83 | + // checkCollisions(); |
| 84 | + } |
| 85 | + |
| 86 | + /* This is called by the update function and loops through all of the |
| 87 | + * objects within your allEnemies array as defined in app.js and calls |
| 88 | + * their update() methods. It will then call the update function for your |
| 89 | + * player object. These update methods should focus purely on updating |
| 90 | + * the data/properties related to the object. Do your drawing in your |
| 91 | + * render methods. |
| 92 | + */ |
| 93 | + function updateEntities(dt) { |
| 94 | + allEnemies.forEach(function(enemy) { |
| 95 | + enemy.update(dt); |
| 96 | + }); |
| 97 | + player.update(); |
| 98 | + } |
| 99 | + |
| 100 | + /* This function initially draws the "game level", it will then call |
| 101 | + * the renderEntities function. Remember, this function is called every |
| 102 | + * game tick (or loop of the game engine) because that's how games work - |
| 103 | + * they are flipbooks creating the illusion of animation but in reality |
| 104 | + * they are just drawing the entire screen over and over. |
| 105 | + */ |
| 106 | + function render() { |
| 107 | + /* This array holds the relative URL to the image used |
| 108 | + * for that particular row of the game level. |
| 109 | + */ |
| 110 | + var rowImages = [ |
| 111 | + 'images/water-block.png', // Top row is water |
| 112 | + 'images/stone-block.png', // Row 1 of 3 of stone |
| 113 | + 'images/stone-block.png', // Row 2 of 3 of stone |
| 114 | + 'images/stone-block.png', // Row 3 of 3 of stone |
| 115 | + 'images/grass-block.png', // Row 1 of 2 of grass |
| 116 | + 'images/grass-block.png' // Row 2 of 2 of grass |
| 117 | + ], |
| 118 | + numRows = 6, |
| 119 | + numCols = 5, |
| 120 | + row, col; |
| 121 | + |
| 122 | + /* Loop through the number of rows and columns we've defined above |
| 123 | + * and, using the rowImages array, draw the correct image for that |
| 124 | + * portion of the "grid" |
| 125 | + */ |
| 126 | + for (row = 0; row < numRows; row++) { |
| 127 | + for (col = 0; col < numCols; col++) { |
| 128 | + /* The drawImage function of the canvas' context element |
| 129 | + * requires 3 parameters: the image to draw, the x coordinate |
| 130 | + * to start drawing and the y coordinate to start drawing. |
| 131 | + * We're using our Resources helpers to refer to our images |
| 132 | + * so that we get the benefits of caching these images, since |
| 133 | + * we're using them over and over. |
| 134 | + */ |
| 135 | + ctx.drawImage(Resources.get(rowImages[row]), col * 101, row * 83); |
| 136 | + } |
| 137 | + } |
| 138 | + |
| 139 | + renderEntities(); |
| 140 | + } |
| 141 | + |
| 142 | + /* This function is called by the render function and is called on each game |
| 143 | + * tick. Its purpose is to then call the render functions you have defined |
| 144 | + * on your enemy and player entities within app.js |
| 145 | + */ |
| 146 | + function renderEntities() { |
| 147 | + /* Loop through all of the objects within the allEnemies array and call |
| 148 | + * the render function you have defined. |
| 149 | + */ |
| 150 | + allEnemies.forEach(function(enemy) { |
| 151 | + enemy.render(); |
| 152 | + }); |
| 153 | + |
| 154 | + player.render(); |
| 155 | + } |
| 156 | + |
| 157 | + /* This function does nothing but it could have been a good place to |
| 158 | + * handle game reset states - maybe a new game menu or a game over screen |
| 159 | + * those sorts of things. It's only called once by the init() method. |
| 160 | + */ |
| 161 | + function reset() { |
| 162 | + // noop |
| 163 | + } |
| 164 | + |
| 165 | + /* Go ahead and load all of the images we know we're going to need to |
| 166 | + * draw our game level. Then set init as the callback method, so that when |
| 167 | + * all of these images are properly loaded our game will start. |
| 168 | + */ |
| 169 | + Resources.load([ |
| 170 | + 'images/stone-block.png', |
| 171 | + 'images/water-block.png', |
| 172 | + 'images/grass-block.png', |
| 173 | + 'images/enemy-bug.png', |
| 174 | + 'images/char-boy.png' |
| 175 | + ]); |
| 176 | + Resources.onReady(init); |
| 177 | + |
| 178 | + /* Assign the canvas' context object to the global variable (the window |
| 179 | + * object when run in a browser) so that developers can use it more easily |
| 180 | + * from within their app.js files. |
| 181 | + */ |
| 182 | + global.ctx = ctx; |
| 183 | +})(this); |
0 commit comments