├── .gitignore
├── LICENSE
├── README.md
├── css
└── app.css
├── img
├── sprites.png
├── sprites.psd
└── terrain.png
├── index.html
├── js
├── app.js
├── input.js
├── resources.js
└── sprite.js
└── tutorial.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .#*
2 | .DS_Store
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012, James Long
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are
6 | met:
7 |
8 | Redistributions of source code must retain the above copyright
9 | notice, this list of conditions and the following disclaimer.
10 |
11 | Redistributions in binary form must reproduce the above copyright
12 | notice, this list of conditions and the following disclaimer in
13 | the documentation and/or other materials provided with the
14 | distribution.
15 |
16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | A simple starting point for writing 2d games. See tutorial.md for more information.
--------------------------------------------------------------------------------
/css/app.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | margin: 0;
3 | padding: 0;
4 | background-color: #151515;
5 | }
6 |
7 | canvas {
8 | display: block;
9 | margin: auto;
10 |
11 | position: absolute;
12 | top: 0;
13 | bottom: 0;
14 | left: 0;
15 | right: 0;
16 | }
17 |
18 | .wrapper {
19 | width: 512px;
20 | margin: 0 auto;
21 | margin-top: 2em;
22 | }
23 |
24 | #instructions {
25 | float: left;
26 | font-family: sans-serif;
27 | color: #757575;
28 | }
29 |
30 | #score {
31 | float: right;
32 | color: white;
33 | font-size: 2em;
34 | }
35 |
36 | .key {
37 | color: #aaffdd;
38 | }
39 |
40 | #game-over, #game-over-overlay {
41 | margin: auto;
42 | width: 512px;
43 | height: 480px;
44 | position: absolute;
45 | top: 0;
46 | left: 0;
47 | right: 0;
48 | bottom: 0;
49 | z-index: 1;
50 | display: none;
51 | }
52 |
53 | #game-over-overlay {
54 | background-color: black;
55 | opacity: .5;
56 | }
57 |
58 | #game-over {
59 | height: 200px;
60 | text-align: center;
61 | color: white;
62 | }
63 |
64 | #game-over h1 {
65 | font-size: 3em;
66 | font-family: sans-serif;
67 | }
68 |
69 | #game-over button {
70 | font-size: 1.5em;
71 | }
--------------------------------------------------------------------------------
/img/sprites.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jlongster/canvas-game-bootstrap/a878158f39a91b19725f726675c752683c9e1c08/img/sprites.png
--------------------------------------------------------------------------------
/img/sprites.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jlongster/canvas-game-bootstrap/a878158f39a91b19725f726675c752683c9e1c08/img/sprites.psd
--------------------------------------------------------------------------------
/img/terrain.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jlongster/canvas-game-bootstrap/a878158f39a91b19725f726675c752683c9e1c08/img/terrain.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
GAME OVER
18 |
19 |
20 |
21 |
22 |
23 |
24 | move with arrows or wasd
25 |
26 |
27 | shoot with space
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/js/app.js:
--------------------------------------------------------------------------------
1 |
2 | // A cross-browser requestAnimationFrame
3 | // See https://hacks.mozilla.org/2011/08/animating-with-javascript-from-setinterval-to-requestanimationframe/
4 | var requestAnimFrame = (function(){
5 | return window.requestAnimationFrame ||
6 | window.webkitRequestAnimationFrame ||
7 | window.mozRequestAnimationFrame ||
8 | window.oRequestAnimationFrame ||
9 | window.msRequestAnimationFrame ||
10 | function(callback){
11 | window.setTimeout(callback, 1000 / 60);
12 | };
13 | })();
14 |
15 | // Create the canvas
16 | var canvas = document.createElement("canvas");
17 | var ctx = canvas.getContext("2d");
18 | canvas.width = 512;
19 | canvas.height = 480;
20 | document.body.appendChild(canvas);
21 |
22 | // The main game loop
23 | var lastTime;
24 | function main() {
25 | var now = Date.now();
26 | var dt = (now - lastTime) / 1000.0;
27 |
28 | update(dt);
29 | render();
30 |
31 | lastTime = now;
32 | requestAnimFrame(main);
33 | };
34 |
35 | function init() {
36 | terrainPattern = ctx.createPattern(resources.get('img/terrain.png'), 'repeat');
37 |
38 | document.getElementById('play-again').addEventListener('click', function() {
39 | reset();
40 | });
41 |
42 | reset();
43 | lastTime = Date.now();
44 | main();
45 | }
46 |
47 | resources.load([
48 | 'img/sprites.png',
49 | 'img/terrain.png'
50 | ]);
51 | resources.onReady(init);
52 |
53 | // Game state
54 | var player = {
55 | pos: [0, 0],
56 | sprite: new Sprite('img/sprites.png', [0, 0], [39, 39], 16, [0, 1])
57 | };
58 |
59 | var bullets = [];
60 | var enemies = [];
61 | var explosions = [];
62 |
63 | var lastFire = Date.now();
64 | var gameTime = 0;
65 | var isGameOver;
66 | var terrainPattern;
67 |
68 | var score = 0;
69 | var scoreEl = document.getElementById('score');
70 |
71 | // Speed in pixels per second
72 | var playerSpeed = 200;
73 | var bulletSpeed = 500;
74 | var enemySpeed = 100;
75 |
76 | // Update game objects
77 | function update(dt) {
78 | gameTime += dt;
79 |
80 | handleInput(dt);
81 | updateEntities(dt);
82 |
83 | // It gets harder over time by adding enemies using this
84 | // equation: 1-.993^gameTime
85 | if(Math.random() < 1 - Math.pow(.993, gameTime)) {
86 | enemies.push({
87 | pos: [canvas.width,
88 | Math.random() * (canvas.height - 39)],
89 | sprite: new Sprite('img/sprites.png', [0, 78], [80, 39],
90 | 6, [0, 1, 2, 3, 2, 1])
91 | });
92 | }
93 |
94 | checkCollisions();
95 |
96 | scoreEl.innerHTML = score;
97 | };
98 |
99 | function handleInput(dt) {
100 | if(input.isDown('DOWN') || input.isDown('s')) {
101 | player.pos[1] += playerSpeed * dt;
102 | }
103 |
104 | if(input.isDown('UP') || input.isDown('w')) {
105 | player.pos[1] -= playerSpeed * dt;
106 | }
107 |
108 | if(input.isDown('LEFT') || input.isDown('a')) {
109 | player.pos[0] -= playerSpeed * dt;
110 | }
111 |
112 | if(input.isDown('RIGHT') || input.isDown('d')) {
113 | player.pos[0] += playerSpeed * dt;
114 | }
115 |
116 | if(input.isDown('SPACE') &&
117 | !isGameOver &&
118 | Date.now() - lastFire > 100) {
119 | var x = player.pos[0] + player.sprite.size[0] / 2;
120 | var y = player.pos[1] + player.sprite.size[1] / 2;
121 |
122 | bullets.push({ pos: [x, y],
123 | dir: 'forward',
124 | sprite: new Sprite('img/sprites.png', [0, 39], [18, 8]) });
125 | bullets.push({ pos: [x, y],
126 | dir: 'up',
127 | sprite: new Sprite('img/sprites.png', [0, 50], [9, 5]) });
128 | bullets.push({ pos: [x, y],
129 | dir: 'down',
130 | sprite: new Sprite('img/sprites.png', [0, 60], [9, 5]) });
131 |
132 | lastFire = Date.now();
133 | }
134 | }
135 |
136 | function updateEntities(dt) {
137 | // Update the player sprite animation
138 | player.sprite.update(dt);
139 |
140 | // Update all the bullets
141 | for(var i=0; i canvas.height ||
153 | bullet.pos[0] > canvas.width) {
154 | bullets.splice(i, 1);
155 | i--;
156 | }
157 | }
158 |
159 | // Update all the enemies
160 | for(var i=0; i r2 ||
187 | b <= y2 || y > b2);
188 | }
189 |
190 | function boxCollides(pos, size, pos2, size2) {
191 | return collides(pos[0], pos[1],
192 | pos[0] + size[0], pos[1] + size[1],
193 | pos2[0], pos2[1],
194 | pos2[0] + size2[0], pos2[1] + size2[1]);
195 | }
196 |
197 | function checkCollisions() {
198 | checkPlayerBounds();
199 |
200 | // Run collision detection for all enemies and bullets
201 | for(var i=0; i canvas.width - player.sprite.size[0]) {
247 | player.pos[0] = canvas.width - player.sprite.size[0];
248 | }
249 |
250 | if(player.pos[1] < 0) {
251 | player.pos[1] = 0;
252 | }
253 | else if(player.pos[1] > canvas.height - player.sprite.size[1]) {
254 | player.pos[1] = canvas.height - player.sprite.size[1];
255 | }
256 | }
257 |
258 | // Draw everything
259 | function render() {
260 | ctx.fillStyle = terrainPattern;
261 | ctx.fillRect(0, 0, canvas.width, canvas.height);
262 |
263 | // Render the player if the game isn't over
264 | if(!isGameOver) {
265 | renderEntity(player);
266 | }
267 |
268 | renderEntities(bullets);
269 | renderEntities(enemies);
270 | renderEntities(explosions);
271 | };
272 |
273 | function renderEntities(list) {
274 | for(var i=0; i 0) {
23 | var max = this.frames.length;
24 | var idx = Math.floor(this._index);
25 | frame = this.frames[idx % max];
26 |
27 | if(this.once && idx >= max) {
28 | this.done = true;
29 | return;
30 | }
31 | }
32 | else {
33 | frame = 0;
34 | }
35 |
36 |
37 | var x = this.pos[0];
38 | var y = this.pos[1];
39 |
40 | if(this.dir == 'vertical') {
41 | y += frame * this.size[1];
42 | }
43 | else {
44 | x += frame * this.size[0];
45 | }
46 |
47 | ctx.drawImage(resources.get(this.url),
48 | x, y,
49 | this.size[0], this.size[1],
50 | 0, 0,
51 | this.size[0], this.size[1]);
52 | }
53 | };
54 |
55 | window.Sprite = Sprite;
56 | })();
--------------------------------------------------------------------------------
/tutorial.md:
--------------------------------------------------------------------------------
1 |
2 | # Making Sprite-based Games with Canvas
3 |
4 | The [canvas element](http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html) was introduced with [HTML5](http://en.wikipedia.org/wiki/HTML5) and provides an API for rendering on the web. The API is simple, but if you've never done graphics work before it might take some getting used to. It has great [cross-browser support](http://caniuse.com/#feat=canvas) at this point, and it makes the web a viable platform for games.
5 |
6 | Using canvas is simple: just create a `