4 |
5 | Effective JavaScript: Frogger
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/js/app.js:
--------------------------------------------------------------------------------
1 | // Enemies our player must avoid
2 | var Enemy = function() {
3 | // Variables applied to each of our instances go here,
4 | // we've provided one for you to get started
5 |
6 | // The image/sprite for our enemies, this uses
7 | // a helper we've provided to easily load images
8 | this.sprite = 'images/enemy-bug.png';
9 | };
10 |
11 | // Update the enemy's position, required method for game
12 | // Parameter: dt, a time delta between ticks
13 | Enemy.prototype.update = function(dt) {
14 | // You should multiply any movement by the dt parameter
15 | // which will ensure the game runs at the same speed for
16 | // all computers.
17 | };
18 |
19 | // Draw the enemy on the screen, required method for game
20 | Enemy.prototype.render = function() {
21 | ctx.drawImage(Resources.get(this.sprite), this.x, this.y);
22 | };
23 |
24 | // Now write your own player class
25 | // This class requires an update(), render() and
26 | // a handleInput() method.
27 |
28 |
29 | // Now instantiate your objects.
30 | // Place all enemy objects in an array called allEnemies
31 | // Place the player object in a variable called player
32 |
33 |
34 |
35 | // This listens for key presses and sends the keys to your
36 | // Player.handleInput() method. You don't need to modify this.
37 | document.addEventListener('keyup', function(e) {
38 | var allowedKeys = {
39 | 37: 'left',
40 | 38: 'up',
41 | 39: 'right',
42 | 40: 'down'
43 | };
44 |
45 | player.handleInput(allowedKeys[e.keyCode]);
46 | });
47 |
--------------------------------------------------------------------------------
/js/engine.js:
--------------------------------------------------------------------------------
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 makes the canvas' context (ctx) object globally available to make
13 | * writing app.js a little simpler to work with.
14 | */
15 |
16 | var Engine = (function(global) {
17 | /* Predefine the variables we'll be using within this scope,
18 | * create the canvas element, grab the 2D context for that canvas
19 | * set the canvas element's height/width and add it to the DOM.
20 | */
21 | var doc = global.document,
22 | win = global.window,
23 | canvas = doc.createElement('canvas'),
24 | ctx = canvas.getContext('2d'),
25 | lastTime;
26 |
27 | canvas.width = 505;
28 | canvas.height = 606;
29 | doc.body.appendChild(canvas);
30 |
31 | /* This function serves as the kickoff point for the game loop itself
32 | * and handles properly calling the update and render methods.
33 | */
34 | function main() {
35 | /* Get our time delta information which is required if your game
36 | * requires smooth animation. Because everyone's computer processes
37 | * instructions at different speeds we need a constant value that
38 | * would be the same for everyone (regardless of how fast their
39 | * computer is) - hurray time!
40 | */
41 | var now = Date.now(),
42 | dt = (now - lastTime) / 1000.0;
43 |
44 | /* Call our update/render functions, pass along the time delta to
45 | * our update function since it may be used for smooth animation.
46 | */
47 | update(dt);
48 | render();
49 |
50 | /* Set our lastTime variable which is used to determine the time delta
51 | * for the next time this function is called.
52 | */
53 | lastTime = now;
54 |
55 | /* Use the browser's requestAnimationFrame function to call this
56 | * function again as soon as the browser is able to draw another frame.
57 | */
58 | win.requestAnimationFrame(main);
59 | }
60 |
61 | /* This function does some initial setup that should only occur once,
62 | * particularly setting the lastTime variable that is required for the
63 | * game loop.
64 | */
65 | function init() {
66 | reset();
67 | lastTime = Date.now();
68 | main();
69 | }
70 |
71 | /* This function is called by main (our game loop) and itself calls all
72 | * of the functions which may need to update entity's data. Based on how
73 | * you implement your collision detection (when two entities occupy the
74 | * same space, for instance when your character should die), you may find
75 | * the need to add an additional function call here. For now, we've left
76 | * it commented out - you may or may not want to implement this
77 | * functionality this way (you could just implement collision detection
78 | * on the entities themselves within your app.js file).
79 | */
80 | function update(dt) {
81 | updateEntities(dt);
82 | // checkCollisions();
83 | }
84 |
85 | /* This is called by the update function and loops through all of the
86 | * objects within your allEnemies array as defined in app.js and calls
87 | * their update() methods. It will then call the update function for your
88 | * player object. These update methods should focus purely on updating
89 | * the data/properties related to the object. Do your drawing in your
90 | * render methods.
91 | */
92 | function updateEntities(dt) {
93 | allEnemies.forEach(function(enemy) {
94 | enemy.update(dt);
95 | });
96 | player.update();
97 | }
98 |
99 | /* This function initially draws the "game level", it will then call
100 | * the renderEntities function. Remember, this function is called every
101 | * game tick (or loop of the game engine) because that's how games work -
102 | * they are flipbooks creating the illusion of animation but in reality
103 | * they are just drawing the entire screen over and over.
104 | */
105 | function render() {
106 | /* This array holds the relative URL to the image used
107 | * for that particular row of the game level.
108 | */
109 | var rowImages = [
110 | 'images/water-block.png', // Top row is water
111 | 'images/stone-block.png', // Row 1 of 3 of stone
112 | 'images/stone-block.png', // Row 2 of 3 of stone
113 | 'images/stone-block.png', // Row 3 of 3 of stone
114 | 'images/grass-block.png', // Row 1 of 2 of grass
115 | 'images/grass-block.png' // Row 2 of 2 of grass
116 | ],
117 | numRows = 6,
118 | numCols = 5,
119 | row, col;
120 |
121 | // Before drawing, clear existing canvas
122 | ctx.clearRect(0,0,canvas.width,canvas.height);
123 |
124 | /* Loop through the number of rows and columns we've defined above
125 | * and, using the rowImages array, draw the correct image for that
126 | * portion of the "grid"
127 | */
128 | for (row = 0; row < numRows; row++) {
129 | for (col = 0; col < numCols; col++) {
130 | /* The drawImage function of the canvas' context element
131 | * requires 3 parameters: the image to draw, the x coordinate
132 | * to start drawing and the y coordinate to start drawing.
133 | * We're using our Resources helpers to refer to our images
134 | * so that we get the benefits of caching these images, since
135 | * we're using them over and over.
136 | */
137 | ctx.drawImage(Resources.get(rowImages[row]), col * 101, row * 83);
138 | }
139 | }
140 |
141 | renderEntities();
142 | }
143 |
144 | /* This function is called by the render function and is called on each game
145 | * tick. Its purpose is to then call the render functions you have defined
146 | * on your enemy and player entities within app.js
147 | */
148 | function renderEntities() {
149 | /* Loop through all of the objects within the allEnemies array and call
150 | * the render function you have defined.
151 | */
152 | allEnemies.forEach(function(enemy) {
153 | enemy.render();
154 | });
155 |
156 | player.render();
157 | }
158 |
159 | /* This function does nothing but it could have been a good place to
160 | * handle game reset states - maybe a new game menu or a game over screen
161 | * those sorts of things. It's only called once by the init() method.
162 | */
163 | function reset() {
164 | // noop
165 | }
166 |
167 | /* Go ahead and load all of the images we know we're going to need to
168 | * draw our game level. Then set init as the callback method, so that when
169 | * all of these images are properly loaded our game will start.
170 | */
171 | Resources.load([
172 | 'images/stone-block.png',
173 | 'images/water-block.png',
174 | 'images/grass-block.png',
175 | 'images/enemy-bug.png',
176 | 'images/char-boy.png'
177 | ]);
178 | Resources.onReady(init);
179 |
180 | /* Assign the canvas' context object to the global variable (the window
181 | * object when run in a browser) so that developers can use it more easily
182 | * from within their app.js files.
183 | */
184 | global.ctx = ctx;
185 | })(this);
186 |
--------------------------------------------------------------------------------
/js/resources.js:
--------------------------------------------------------------------------------
1 | /* Resources.js
2 | * This is simply an image loading utility. It eases the process of loading
3 | * image files so that they can be used within your game. It also includes
4 | * a simple "caching" layer so it will reuse cached images if you attempt
5 | * to load the same image multiple times.
6 | */
7 | (function() {
8 | var resourceCache = {};
9 | var readyCallbacks = [];
10 |
11 | /* This is the publicly accessible image loading function. It accepts
12 | * an array of strings pointing to image files or a string for a single
13 | * image. It will then call our private image loading function accordingly.
14 | */
15 | function load(urlOrArr) {
16 | if(urlOrArr instanceof Array) {
17 | /* If the developer passed in an array of images
18 | * loop through each value and call our image
19 | * loader on that image file
20 | */
21 | urlOrArr.forEach(function(url) {
22 | _load(url);
23 | });
24 | } else {
25 | /* The developer did not pass an array to this function,
26 | * assume the value is a string and call our image loader
27 | * directly.
28 | */
29 | _load(urlOrArr);
30 | }
31 | }
32 |
33 | /* This is our private image loader function, it is
34 | * called by the public image loader function.
35 | */
36 | function _load(url) {
37 | if(resourceCache[url]) {
38 | /* If this URL has been previously loaded it will exist within
39 | * our resourceCache array. Just return that image rather than
40 | * re-loading the image.
41 | */
42 | return resourceCache[url];
43 | } else {
44 | /* This URL has not been previously loaded and is not present
45 | * within our cache; we'll need to load this image.
46 | */
47 | var img = new Image();
48 | img.onload = function() {
49 | /* Once our image has properly loaded, add it to our cache
50 | * so that we can simply return this image if the developer
51 | * attempts to load this file in the future.
52 | */
53 | resourceCache[url] = img;
54 |
55 | /* Once the image is actually loaded and properly cached,
56 | * call all of the onReady() callbacks we have defined.
57 | */
58 | if(isReady()) {
59 | readyCallbacks.forEach(function(func) { func(); });
60 | }
61 | };
62 |
63 | /* Set the initial cache value to false, this will change when
64 | * the image's onload event handler is called. Finally, point
65 | * the image's src attribute to the passed in URL.
66 | */
67 | resourceCache[url] = false;
68 | img.src = url;
69 | }
70 | }
71 |
72 | /* This is used by developers to grab references to images they know
73 | * have been previously loaded. If an image is cached, this functions
74 | * the same as calling load() on that URL.
75 | */
76 | function get(url) {
77 | return resourceCache[url];
78 | }
79 |
80 | /* This function determines if all of the images that have been requested
81 | * for loading have in fact been properly loaded.
82 | */
83 | function isReady() {
84 | var ready = true;
85 | for(var k in resourceCache) {
86 | if(resourceCache.hasOwnProperty(k) &&
87 | !resourceCache[k]) {
88 | ready = false;
89 | }
90 | }
91 | return ready;
92 | }
93 |
94 | /* This function will add a function to the callback stack that is called
95 | * when all requested images are properly loaded.
96 | */
97 | function onReady(func) {
98 | readyCallbacks.push(func);
99 | }
100 |
101 | /* This object defines the publicly accessible functions available to
102 | * developers by creating a global Resources object.
103 | */
104 | window.Resources = {
105 | load: load,
106 | get: get,
107 | onReady: onReady,
108 | isReady: isReady
109 | };
110 | })();
111 |
--------------------------------------------------------------------------------