├── images
├── coin.png
├── coins.png
├── ground.png
├── ladder.png
├── monster.png
├── monster
│ ├── blocker1.png
│ ├── blocker2.png
│ ├── flyleft1.png
│ ├── flyleft2.png
│ ├── flyright1.png
│ ├── flyright2.png
│ ├── slimeleft1.png
│ ├── slimeleft2.png
│ ├── slimeright1.png
│ ├── slimeright2.png
│ ├── snailleft1.png
│ ├── snailleft2.png
│ ├── snailright1.png
│ └── snailright2.png
├── player.png
└── player
│ ├── 00_right.png
│ ├── 01_right.png
│ ├── 02_right.png
│ ├── 03_right.png
│ ├── 04_right.png
│ ├── 05_right.png
│ ├── 06_right.png
│ ├── 07_right.png
│ ├── 08_right.png
│ ├── 09_right.png
│ ├── 10_right.png
│ ├── 11_front.png
│ ├── 12_front_right.png
│ ├── 13_front_left.png
│ ├── 14_jump.png
│ ├── 15_hurt_left.png
│ ├── 16_hurt_right.png
│ ├── 17_left.png
│ ├── 18_left.png
│ ├── 19_left.png
│ ├── 20_left.png
│ ├── 21_left.png
│ ├── 22_left.png
│ ├── 23_left.png
│ ├── 24_left.png
│ ├── 25_left.png
│ ├── 26_left.png
│ ├── 27_left.png
│ ├── 28_climb.png
│ ├── 29_climb.png
│ ├── 30_climb.png
│ ├── 31_climb.png
│ ├── 32_climb.png
│ ├── 33_climb.png
│ ├── 34_climb.png
│ ├── 35_climb.png
│ ├── 36_climb.png
│ ├── 37_climb.png
│ └── 38_climb.png
├── index.html
├── js
├── common.js
├── fpsmeter.min.js
└── tower.js
├── levels
└── demo.json
├── license
└── readme.md
/images/coin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/coin.png
--------------------------------------------------------------------------------
/images/coins.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/coins.png
--------------------------------------------------------------------------------
/images/ground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/ground.png
--------------------------------------------------------------------------------
/images/ladder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/ladder.png
--------------------------------------------------------------------------------
/images/monster.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/monster.png
--------------------------------------------------------------------------------
/images/monster/blocker1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/monster/blocker1.png
--------------------------------------------------------------------------------
/images/monster/blocker2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/monster/blocker2.png
--------------------------------------------------------------------------------
/images/monster/flyleft1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/monster/flyleft1.png
--------------------------------------------------------------------------------
/images/monster/flyleft2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/monster/flyleft2.png
--------------------------------------------------------------------------------
/images/monster/flyright1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/monster/flyright1.png
--------------------------------------------------------------------------------
/images/monster/flyright2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/monster/flyright2.png
--------------------------------------------------------------------------------
/images/monster/slimeleft1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/monster/slimeleft1.png
--------------------------------------------------------------------------------
/images/monster/slimeleft2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/monster/slimeleft2.png
--------------------------------------------------------------------------------
/images/monster/slimeright1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/monster/slimeright1.png
--------------------------------------------------------------------------------
/images/monster/slimeright2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/monster/slimeright2.png
--------------------------------------------------------------------------------
/images/monster/snailleft1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/monster/snailleft1.png
--------------------------------------------------------------------------------
/images/monster/snailleft2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/monster/snailleft2.png
--------------------------------------------------------------------------------
/images/monster/snailright1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/monster/snailright1.png
--------------------------------------------------------------------------------
/images/monster/snailright2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/monster/snailright2.png
--------------------------------------------------------------------------------
/images/player.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/player.png
--------------------------------------------------------------------------------
/images/player/00_right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/player/00_right.png
--------------------------------------------------------------------------------
/images/player/01_right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/player/01_right.png
--------------------------------------------------------------------------------
/images/player/02_right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/player/02_right.png
--------------------------------------------------------------------------------
/images/player/03_right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/player/03_right.png
--------------------------------------------------------------------------------
/images/player/04_right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/player/04_right.png
--------------------------------------------------------------------------------
/images/player/05_right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/player/05_right.png
--------------------------------------------------------------------------------
/images/player/06_right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/player/06_right.png
--------------------------------------------------------------------------------
/images/player/07_right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/player/07_right.png
--------------------------------------------------------------------------------
/images/player/08_right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/player/08_right.png
--------------------------------------------------------------------------------
/images/player/09_right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/player/09_right.png
--------------------------------------------------------------------------------
/images/player/10_right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/player/10_right.png
--------------------------------------------------------------------------------
/images/player/11_front.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/player/11_front.png
--------------------------------------------------------------------------------
/images/player/12_front_right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/player/12_front_right.png
--------------------------------------------------------------------------------
/images/player/13_front_left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/player/13_front_left.png
--------------------------------------------------------------------------------
/images/player/14_jump.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/player/14_jump.png
--------------------------------------------------------------------------------
/images/player/15_hurt_left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/player/15_hurt_left.png
--------------------------------------------------------------------------------
/images/player/16_hurt_right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/player/16_hurt_right.png
--------------------------------------------------------------------------------
/images/player/17_left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/player/17_left.png
--------------------------------------------------------------------------------
/images/player/18_left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/player/18_left.png
--------------------------------------------------------------------------------
/images/player/19_left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/player/19_left.png
--------------------------------------------------------------------------------
/images/player/20_left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/player/20_left.png
--------------------------------------------------------------------------------
/images/player/21_left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/player/21_left.png
--------------------------------------------------------------------------------
/images/player/22_left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/player/22_left.png
--------------------------------------------------------------------------------
/images/player/23_left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/player/23_left.png
--------------------------------------------------------------------------------
/images/player/24_left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/player/24_left.png
--------------------------------------------------------------------------------
/images/player/25_left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/player/25_left.png
--------------------------------------------------------------------------------
/images/player/26_left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/player/26_left.png
--------------------------------------------------------------------------------
/images/player/27_left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/player/27_left.png
--------------------------------------------------------------------------------
/images/player/28_climb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/player/28_climb.png
--------------------------------------------------------------------------------
/images/player/29_climb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/player/29_climb.png
--------------------------------------------------------------------------------
/images/player/30_climb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/player/30_climb.png
--------------------------------------------------------------------------------
/images/player/31_climb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/player/31_climb.png
--------------------------------------------------------------------------------
/images/player/32_climb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/player/32_climb.png
--------------------------------------------------------------------------------
/images/player/33_climb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/player/33_climb.png
--------------------------------------------------------------------------------
/images/player/34_climb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/player/34_climb.png
--------------------------------------------------------------------------------
/images/player/35_climb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/player/35_climb.png
--------------------------------------------------------------------------------
/images/player/36_climb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/player/36_climb.png
--------------------------------------------------------------------------------
/images/player/37_climb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/player/37_climb.png
--------------------------------------------------------------------------------
/images/player/38_climb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jakesgordon/javascript-tower-platformer/d242450d05af9e43c5901f8797935228d5f8ee6b/images/player/38_climb.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Tower Platformer
7 |
8 |
9 |
10 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |  | |
34 |
35 |
36 |
37 |
42 |
43 |
44 | LEFT/RIGHT to move, SPACE to jump
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/js/common.js:
--------------------------------------------------------------------------------
1 | //-------------------------------------------------------------------------
2 | // POLYFILLS
3 | //-------------------------------------------------------------------------
4 |
5 | if (!window.requestAnimationFrame) { // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
6 | window.requestAnimationFrame = window.webkitRequestAnimationFrame ||
7 | window.mozRequestAnimationFrame ||
8 | window.oRequestAnimationFrame ||
9 | window.msRequestAnimationFrame ||
10 | function(callback, element) {
11 | window.setTimeout(callback, 1000 / 60);
12 | }
13 | }
14 |
15 | //-------------------------------------------------------------------------
16 | // SIMPLE CLASS CREATION
17 | //-------------------------------------------------------------------------
18 |
19 | var Class = {
20 | create: function(prototype) { // create a simple javascript 'class' (a constructor function with a prototype)
21 | var ctor = function() { if (this.initialize) return this.initialize.apply(this, arguments); }
22 | ctor.prototype = prototype || {}; // instance methods
23 | return ctor;
24 | }
25 | }
26 |
27 | //-------------------------------------------------------------------------
28 | // SIMPLE DOM UTILITIES
29 | //-------------------------------------------------------------------------
30 |
31 | var Dom = {
32 | get: function(id) { return ((id instanceof HTMLElement) || (id === document)) ? id : document.getElementById(id); },
33 | set: function(id, html) { Dom.get(id).innerHTML = html; },
34 | on: function(ele, type, fn, capture) { Dom.get(ele).addEventListener(type, fn, capture); },
35 | un: function(ele, type, fn, capture) { Dom.get(ele).removeEventListener(type, fn, capture); },
36 | show: function(ele, type) { Dom.get(ele).style.display = (type || 'block'); },
37 | }
38 |
39 | //-------------------------------------------------------------------------
40 | // GAME LOOP
41 | //-------------------------------------------------------------------------
42 |
43 | var Game = {
44 |
45 | run: function(options) {
46 |
47 | var now,
48 | dt = 0,
49 | last = Game.Math.timestamp(),
50 | step = 1/options.fps,
51 | update = options.update,
52 | render = options.render,
53 | fpsmeter = new FPSMeter(options.fpsmeter || { decimals: 0, graph: true, theme: 'dark', left: '5px' });
54 |
55 | function frame() {
56 | fpsmeter.tickStart();
57 | now = Game.Math.timestamp();
58 | dt = dt + Math.min(1, (now - last) / 1000);
59 | while(dt > step) {
60 | dt = dt - step;
61 | update(step);
62 | }
63 | render(dt);
64 | last = now;
65 | fpsmeter.tick();
66 | requestAnimationFrame(frame, options.canvas);
67 | }
68 |
69 | frame();
70 | },
71 |
72 | animate: function(fps, entity, animation) {
73 | animation = animation || entity.animation;
74 | entity.animationFrame = entity.animationFrame || 0;
75 | entity.animationCounter = entity.animationCounter || 0;
76 | if (entity.animation != animation) {
77 | entity.animation = animation;
78 | entity.animationFrame = 0;
79 | entity.animationCounter = 0;
80 | }
81 | else if (++(entity.animationCounter) == Math.round(fps/animation.fps)) {
82 | entity.animationFrame = Game.Math.normalize(entity.animationFrame + 1, 0, entity.animation.frames);
83 | entity.animationCounter = 0;
84 | }
85 | }
86 |
87 | };
88 |
89 | //-------------------------------------------------------------------------
90 | // CANVAS UTILITIES
91 | //-------------------------------------------------------------------------
92 |
93 | Game.Canvas = {
94 |
95 | create: function(width, height) {
96 | return this.init(document.createElement('canvas'), width, height);
97 | },
98 |
99 | init: function(canvas, width, height) {
100 | canvas.width = width;
101 | canvas.height = height;
102 | return canvas;
103 | },
104 |
105 | render: function(width, height, render, canvas) { // http://kaioa.com/node/103
106 | canvas = canvas || this.create(width, height);
107 | render(canvas.getContext('2d'), width, height);
108 | return canvas;
109 | }
110 |
111 | };
112 |
113 | //-------------------------------------------------------------------------
114 | // ASSET LOADING UTILITIES
115 | //-------------------------------------------------------------------------
116 |
117 | Game.Load = {
118 |
119 | images: function(names, callback) { // load multiple images and callback when ALL images have loaded
120 |
121 | var n,name,
122 | result = {},
123 | count = names.length,
124 | onload = function() { if (--count == 0) callback(result); };
125 |
126 | for(n = 0 ; n < names.length ; n++) {
127 | name = names[n];
128 | result[name] = document.createElement('img');
129 | Dom.on(result[name], 'load', onload);
130 | result[name].src = "images/" + name + ".png";
131 | }
132 |
133 | },
134 |
135 | json: function(url, onsuccess) {
136 | var request = new XMLHttpRequest();
137 | request.onreadystatechange = function() {
138 | if ((request.readyState == 4) && (request.status == 200))
139 | onsuccess(JSON.parse(request.responseText));
140 | }
141 | request.open("GET", url + ".json", true);
142 | request.send();
143 | }
144 |
145 | };
146 |
147 | //-------------------------------------------------------------------------
148 | // MATH UTILITIES
149 | //-------------------------------------------------------------------------
150 |
151 | Game.Math = {
152 |
153 | lerp: function(n, dn, dt) {
154 | return n + (dn * dt);
155 | },
156 |
157 | timestamp: function() {
158 | return window.performance && window.performance.now ? window.performance.now() : new Date().getTime();
159 | },
160 |
161 | bound: function(x, min, max) {
162 | return Math.max(min, Math.min(max, x));
163 | },
164 |
165 | between: function(n, min, max) {
166 | return ((n >= min) && (n <= max));
167 | },
168 |
169 | brighten: function(hex, percent) {
170 |
171 | var a = Math.round(255 * percent/100),
172 | r = a + parseInt(hex.substr(1, 2), 16),
173 | g = a + parseInt(hex.substr(3, 2), 16),
174 | b = a + parseInt(hex.substr(5, 2), 16);
175 |
176 | r = r<255?(r<1?0:r):255;
177 | g = g<255?(g<1?0:g):255;
178 | b = b<255?(b<1?0:b):255;
179 |
180 | return '#' + (0x1000000 + (r * 0x10000) + (g * 0x100) + b).toString(16).slice(1);
181 | },
182 |
183 | darken: function(hex, percent) {
184 | return this.brighten(hex, -percent);
185 | },
186 |
187 | normalize: function(n, min, max) {
188 | while (n < min)
189 | n += (max-min);
190 | while (n >= max)
191 | n -= (max-min);
192 | return n;
193 | },
194 |
195 | normalizeAngle180: function(angle) { return this.normalize(angle, -180, 180); },
196 | normalizeAngle360: function(angle) { return this.normalize(angle, 0, 360); },
197 |
198 | random: function(min, max) { return (min + (Math.random() * (max - min))); },
199 | randomInt: function(min, max) { return Math.round(this.random(min, max)); },
200 | randomChoice: function(choices) { return choices[this.randomInt(0, choices.length-1)]; }
201 |
202 | };
203 |
204 | //-------------------------------------------------------------------------
205 |
206 |
--------------------------------------------------------------------------------
/js/fpsmeter.min.js:
--------------------------------------------------------------------------------
1 | /*! FPSMeter 0.3.1 - 9th May 2013 | https://github.com/Darsain/fpsmeter */
2 | (function(m,j){function s(a,e){for(var g in e)try{a.style[g]=e[g]}catch(j){}return a}function H(a){return null==a?String(a):"object"===typeof a||"function"===typeof a?Object.prototype.toString.call(a).match(/\s([a-z]+)/i)[1].toLowerCase()||"object":typeof a}function R(a,e){if("array"!==H(e))return-1;if(e.indexOf)return e.indexOf(a);for(var g=0,j=e.length;gd.interval?(x=M(k),m()):(x=setTimeout(k,d.interval),P=M(m))}function G(a){a=a||window.event;a.preventDefault?(a.preventDefault(),a.stopPropagation()):(a.returnValue=
6 | !1,a.cancelBubble=!0);b.toggle()}function U(){d.toggleOn&&S(f.container,d.toggleOn,G,1);a.removeChild(f.container)}function V(){f.container&&U();h=D.theme[d.theme];y=h.compiledHeatmaps||[];if(!y.length&&h.heatmaps.length){for(p=0;p=m?m*(1+j):m+j-m*j;0===l?g="#000":(t=2*m-l,k=(l-t)/l,g*=6,n=Math.floor(g),
7 | v=g-n,v*=l*k,0===n||6===n?(n=l,k=t+v,l=t):1===n?(n=l-v,k=l,l=t):2===n?(n=t,k=l,l=t+v):3===n?(n=t,k=l-v):4===n?(n=t+v,k=t):(n=l,k=t,l-=v),g="#"+N(n)+N(k)+N(l));b[e]=g}}h.compiledHeatmaps=y}f.container=s(document.createElement("div"),h.container);f.count=f.container.appendChild(s(document.createElement("div"),h.count));f.legend=f.container.appendChild(s(document.createElement("div"),h.legend));f.graph=d.graph?f.container.appendChild(s(document.createElement("div"),h.graph)):0;w.length=0;for(var q in f)f[q]&&
8 | h[q].heatOn&&w.push({name:q,el:f[q]});u.length=0;if(f.graph){f.graph.style.width=d.history*h.column.width+(d.history-1)*h.column.spacing+"px";for(c=0;c Math.abs(x - col2x(col + 0.5))/(COL_WIDTH/2); } // is x-coord "near" the center of a tower column
74 | function nearRowSurface(y,row) { return y > (row2y(row+1) - ROW_SURFACE); } // is y-coord "near" the surface of a tower row
75 |
76 | //===========================================================================
77 | // GAME - SETUP/UPDATE/RENDER
78 | //===========================================================================
79 |
80 | function run() {
81 | Game.Load.images(IMAGES, function(images) {
82 | Game.Load.json("levels/demo", function(level) {
83 | setup(images, level);
84 | Game.run({
85 | fps: FPS,
86 | update: update,
87 | render: render
88 | });
89 | Dom.on(document, 'keydown', function(ev) { return onkey(ev, ev.keyCode, true); }, false);
90 | Dom.on(document, 'keyup', function(ev) { return onkey(ev, ev.keyCode, false); }, false);
91 | });
92 | });
93 | }
94 |
95 | function setup(images, level) {
96 | tower = new Tower(level);
97 | monsters = new Monsters(level);
98 | player = new Player();
99 | camera = new Camera();
100 | renderer = new Renderer(images);
101 | }
102 |
103 | function update(dt) {
104 | player.update(dt);
105 | monsters.update(dt);
106 | camera.update(dt);
107 | }
108 |
109 | function render(dt) {
110 | renderer.render(dt);
111 | }
112 |
113 | function onkey(ev, key, pressed) {
114 | switch(key) {
115 | case KEY.LEFT: player.input.left = pressed; ev.preventDefault(); return false;
116 | case KEY.RIGHT: player.input.right = pressed; ev.preventDefault(); return false;
117 | case KEY.UP: player.input.up = pressed; ev.preventDefault(); return false;
118 | case KEY.DOWN: player.input.down = pressed; ev.preventDefault(); return false;
119 |
120 | case KEY.SPACE:
121 | player.input.jump = pressed && player.input.jumpAvailable;
122 | player.input.jumpAvailable = !pressed;
123 | break;
124 | }
125 | }
126 |
127 | //===========================================================================
128 | // TOWER
129 | //===========================================================================
130 |
131 | var Tower = Class.create({
132 |
133 | //-------------------------------------------------------------------------
134 |
135 | initialize: function(level) {
136 |
137 | var row, col;
138 |
139 | level.map.reverse(); // make 0 index the ground, increasing towards the sky
140 |
141 | this.name = level.name;
142 | this.color = level.color;
143 | this.rows = level.map.length;
144 | this.cols = level.map[0].length;
145 | this.ir = WIDTH/4; // inner radius (walls)
146 | this.or = this.ir * 1.2; // outer radius (walls plus platforms)
147 | this.w = this.cols * COL_WIDTH;
148 | this.h = this.rows * ROW_HEIGHT;
149 | this.map = this.createMap(level.map);
150 | this.ground = { platform: true };
151 | this.air = { platform: false };
152 |
153 | },
154 |
155 | //-------------------------------------------------------------------------
156 |
157 | getCell: function(row, col) {
158 | if (row < 0)
159 | return this.ground;
160 | else if (row >= this.rows)
161 | return this.air;
162 | else
163 | return this.map[row][normalizeColumn(col)];
164 | },
165 |
166 | //-------------------------------------------------------------------------
167 |
168 | createMap: function(source) {
169 | var row, col, cell, map = [];
170 | for(row = 0 ; row < this.rows ; row++) {
171 | map[row] = [];
172 | for(col = 0 ; col < this.cols ; col++) {
173 | cell = source[row][col];
174 | map[row][col] = {
175 | platform: (cell == 'X'),
176 | ladder: (cell == 'H'),
177 | coin: (cell == 'o')
178 | };
179 | }
180 | }
181 | return map;
182 | }
183 |
184 | });
185 |
186 | //===========================================================================
187 | // PLAYER
188 | //===========================================================================
189 |
190 | var Player = Class.create({
191 |
192 | initialize: function() {
193 |
194 | this.x = col2x(0.5);
195 | this.y = row2y(0);
196 | this.w = PLAYER_WIDTH;
197 | this.h = PLAYER_HEIGHT;
198 | this.dx = 0;
199 | this.dy = 0;
200 | this.gravity = METER * GRAVITY;
201 | this.maxdx = METER * MAXDX;
202 | this.maxdy = METER * MAXDY;
203 | this.climbdy = METER * CLIMBDY;
204 | this.impulse = METER * IMPULSE;
205 | this.accel = this.maxdx / ACCEL;
206 | this.friction = this.maxdx / FRICTION;
207 | this.input = { left: false, right: false, up: false, down: false, jump: false, jumpAvailable: true };
208 | this.collision = this.createCollisionPoints();
209 | this.animation = PLAYER.STAND;
210 | this.score = 0;
211 |
212 | },
213 |
214 | createCollisionPoints: function() {
215 | return {
216 | topLeft: { x: -this.w/4, y: this.h-2 },
217 | topRight: { x: this.w/4, y: this.h-2 },
218 | middleLeft: { x: -this.w/2, y: this.h/2 },
219 | middleRight: { x: this.w/2, y: this.h/2 },
220 | bottomLeft: { x: -this.w/4, y: 0 },
221 | bottomRight: { x: this.w/4, y: 0 },
222 | underLeft: { x: -this.w/4, y: -1 },
223 | underRight: { x: this.w/4, y: -1 },
224 | ladderUp: { x: 0, y: this.h/2 },
225 | ladderDown: { x: 0, y: -1 }
226 | }
227 | },
228 |
229 | update: function(dt) {
230 |
231 | this.animate();
232 |
233 | var wasleft = this.dx < 0,
234 | wasright = this.dx > 0,
235 | falling = this.falling,
236 | friction = this.friction * (this.falling ? 0.5 : 1),
237 | accel = this.accel * (this.falling || this.climbing ? 0.5 : 1);
238 |
239 | if (this.stepping)
240 | return this.stepUp();
241 | else if (this.hurting)
242 | return this.hurt(dt);
243 |
244 | this.ddx = 0;
245 | this.ddy = falling ? -this.gravity : 0;
246 |
247 | if (this.climbing) {
248 | this.ddy = 0;
249 | if (this.input.up)
250 | this.dy = this.climbdy;
251 | else if (this.input.down)
252 | this.dy = -this.climbdy;
253 | else
254 | this.dy = 0;
255 | }
256 |
257 | if (this.input.left)
258 | this.ddx = this.ddx - accel;
259 | else if (wasleft)
260 | this.ddx = this.ddx + friction;
261 |
262 | if (this.input.right)
263 | this.ddx = this.ddx + accel;
264 | else if (wasright)
265 | this.ddx = this.ddx - friction;
266 |
267 | if (this.input.jump && (!falling || this.fallingJump))
268 | this.performJump();
269 |
270 | this.updatePosition(dt);
271 |
272 | while (this.checkCollision()) {
273 | // iterate until no more collisions
274 | }
275 |
276 | // clamp dx at zero to prevent friction from making us jiggle side to side
277 | if ((wasleft && (this.dx > 0)) ||
278 | (wasright && (this.dx < 0))) {
279 | this.dx = 0;
280 | }
281 |
282 | // if falling, track short period of time during which we're falling but can still jump
283 | if (this.falling && (this.fallingJump > 0))
284 | this.fallingJump = this.fallingJump - 1;
285 |
286 | // debug information
287 | this.debug = Math.floor(this.x) + ", " + Math.floor(this.y) + ", " + Math.floor(this.dx) + ", " + Math.floor(this.dy) + (this.falling ? " FALLING " : "");
288 |
289 | },
290 |
291 | updatePosition: function(dt) {
292 | this.x = normalizex(this.x + (dt * this.dx));
293 | this.y = this.y + (dt * this.dy);
294 | this.dx = Game.Math.bound(this.dx + (dt * this.ddx), -this.maxdx, this.maxdx);
295 | this.dy = Game.Math.bound(this.dy + (dt * this.ddy), -this.maxdy, this.maxdy);
296 | },
297 |
298 | hurt: function(dt) {
299 | if (this.hurting === true) {
300 | this.dx = -this.dx/2;
301 | this.ddx = 0;
302 | this.ddy = this.impulse/2;
303 | this.hurting = FPS;
304 | this.hurtLeft = this.input.left;
305 | }
306 | else {
307 | this.ddy = -this.gravity;
308 | this.hurting = this.hurting - 1;
309 | }
310 | this.updatePosition(dt);
311 | if (this.y <= 0) {
312 | this.hurting = false;
313 | this.falling = false;
314 | this.y = 0;
315 | this.dy = 0;
316 | }
317 | },
318 |
319 | animate: function() {
320 | if (this.hurting)
321 | Game.animate(FPS, this, this.hurtLeft ? PLAYER.HURTL : PLAYER.HURTR);
322 | else if (this.climbing && (this.input.up || this.input.down || this.input.left || this.input.right))
323 | Game.animate(FPS, this, PLAYER.CLIMB);
324 | else if (this.climbing)
325 | Game.animate(FPS, this, PLAYER.BACK);
326 | else if (this.input.left || (this.stepping == DIR.LEFT))
327 | Game.animate(FPS, this, PLAYER.LEFT);
328 | else if (this.input.right || (this.stepping == DIR.RIGHT))
329 | Game.animate(FPS, this, PLAYER.RIGHT);
330 | else
331 | Game.animate(FPS, this, PLAYER.STAND);
332 | },
333 |
334 | checkCollision: function() {
335 |
336 | var falling = this.falling,
337 | fallingUp = this.falling && (this.dy > 0),
338 | fallingDown = this.falling && (this.dy <= 0),
339 | climbing = this.climbing,
340 | climbingUp = this.climbing && this.input.up,
341 | climbingDown = this.climbing && this.input.down,
342 | runningLeft = this.dx < 0,
343 | runningRight = this.dx > 0,
344 | tl = this.collision.topLeft,
345 | tr = this.collision.topRight,
346 | ml = this.collision.middleLeft,
347 | mr = this.collision.middleRight,
348 | bl = this.collision.bottomLeft,
349 | br = this.collision.bottomRight,
350 | ul = this.collision.underLeft,
351 | ur = this.collision.underRight,
352 | ld = this.collision.ladderDown,
353 | lu = this.collision.ladderUp;
354 |
355 | this.updateCollisionPoint(tl);
356 | this.updateCollisionPoint(tr);
357 | this.updateCollisionPoint(ml);
358 | this.updateCollisionPoint(mr);
359 | this.updateCollisionPoint(bl);
360 | this.updateCollisionPoint(br);
361 | this.updateCollisionPoint(ul);
362 | this.updateCollisionPoint(ur);
363 | this.updateCollisionPoint(ld);
364 | this.updateCollisionPoint(lu);
365 |
366 | if (tl.coin) return this.collectCoin(tl);
367 | else if (tr.coin) return this.collectCoin(tr);
368 | else if (ml.coin) return this.collectCoin(ml);
369 | else if (mr.coin) return this.collectCoin(mr);
370 | else if (bl.coin) return this.collectCoin(bl);
371 | else if (br.coin) return this.collectCoin(br);
372 |
373 | if (fallingDown && bl.blocked && !ml.blocked && !tl.blocked && nearRowSurface(this.y + bl.y, bl.row))
374 | return this.collideDown(bl);
375 |
376 | if (fallingDown && br.blocked && !mr.blocked && !tr.blocked && nearRowSurface(this.y + br.y, br.row))
377 | return this.collideDown(br);
378 |
379 | if (fallingDown && ld.ladder && !lu.ladder)
380 | return this.collideDown(ld);
381 |
382 | if (fallingUp && tl.blocked && !ml.blocked && !bl.blocked)
383 | return this.collideUp(tl);
384 |
385 | if (fallingUp && tr.blocked && !mr.blocked && !br.blocked)
386 | return this.collideUp(tr);
387 |
388 | if (climbingDown && ld.blocked)
389 | return this.stopClimbing(ld);
390 |
391 | if (runningRight && tr.blocked && !tl.blocked)
392 | return this.collide(tr);
393 |
394 | if (runningRight && mr.blocked && !ml.blocked)
395 | return this.collide(mr);
396 |
397 | if (runningRight && br.blocked && !bl.blocked) {
398 | if (falling)
399 | return this.collide(br);
400 | else
401 | return this.startSteppingUp(DIR.RIGHT);
402 | }
403 |
404 | if (runningLeft && tl.blocked && !tr.blocked)
405 | return this.collide(tl, true);
406 |
407 | if (runningLeft && ml.blocked && !mr.blocked)
408 | return this.collide(ml, true);
409 |
410 | if (runningLeft && bl.blocked && !br.blocked) {
411 | if (falling)
412 | return this.collide(bl, true);
413 | else
414 | return this.startSteppingUp(DIR.LEFT);
415 | }
416 |
417 | var onLadder = (lu.ladder || ld.ladder) && nearColCenter(this.x, lu.col, LADDER_EDGE);
418 |
419 | // check to see if we are now falling or climbing
420 | if (!climbing && onLadder && ((lu.ladder && this.input.up) || (ld.ladder && this.input.down)))
421 | return this.startClimbing();
422 | else if (!climbing && !falling && !ul.blocked && !ur.blocked && !onLadder)
423 | return this.startFalling(true);
424 |
425 | // check to see if we have fallen off a ladder
426 | if (climbing && !onLadder)
427 | return this.stopClimbing();
428 |
429 | if (!this.hurting && (tl.monster || tr.monster || ml.monster || mr.monster || bl.monster || br.monster || lu.monster || ld.monster))
430 | return this.hitMonster();
431 |
432 | return false; // done, we didn't collide with anything
433 |
434 | },
435 |
436 | updateCollisionPoint: function(point) {
437 | point.row = y2row(this.y + point.y);
438 | point.col = x2col(this.x + point.x);
439 | point.cell = tower.getCell(point.row, point.col);
440 | point.blocked = point.cell.platform;
441 | point.platform = point.cell.platform;
442 | point.ladder = point.cell.ladder;
443 | point.monster = false;
444 | point.coin = false;
445 | if (point.cell.monster) {
446 | var monster = point.cell.monster;
447 | if (Game.Math.between(this.x + point.x, monster.x + monster.nx, monster.x + monster.nx + monster.w) &&
448 | Game.Math.between(this.y + point.y, monster.y + monster.ny, monster.y + monster.ny + monster.h)) {
449 | point.monster = point.cell.monster;
450 | }
451 | }
452 | if (point.cell.coin) {
453 | if (Game.Math.between(this.x + point.x, col2x(point.col+0.5) - COIN.W/2, col2x(point.col+0.5) + COIN.W/2) && // center point of column +/- COIN.W/2
454 | Game.Math.between(this.y + point.y, row2y(point.row), row2y(point.row+1))) {
455 | point.coin = true;
456 | }
457 | }
458 | },
459 |
460 | collectCoin: function(point) {
461 | point.cell.coin = false;
462 | this.score = this.score + 50;
463 | },
464 |
465 | startFalling: function(allowFallingJump) {
466 | this.falling = true;
467 | this.fallingJump = allowFallingJump ? FALLING_JUMP : 0;
468 | },
469 |
470 | collide: function(point, left) {
471 | this.x = normalizex(col2x(point.col + (left ? 1 : 0)) - point.x);
472 | this.dx = 0;
473 | return true;
474 | },
475 |
476 | collideUp: function(point) {
477 | this.y = row2y(point.row) - point.y;
478 | this.dy = 0;
479 | return true;
480 | },
481 |
482 | collideDown: function(point) {
483 | this.y = row2y(point.row + 1);
484 | this.dy = 0;
485 | this.falling = false;
486 | return true;
487 | },
488 |
489 | performJump: function() {
490 | if (this.climbing)
491 | this.stopClimbing();
492 | this.dy = 0;
493 | this.ddy = this.impulse; // an instant big force impulse
494 | this.startFalling(false);
495 | this.input.jump = false;
496 | },
497 |
498 | startSteppingUp: function(dir) {
499 | this.stepping = dir;
500 | this.stepCount = STEP.FRAMES;
501 | return false; // NOT considered a collision
502 | },
503 |
504 | stepUp: function() {
505 |
506 | var left = (this.stepping == DIR.LEFT),
507 | dx = STEP.W / STEP.FRAMES,
508 | dy = STEP.H / STEP.FRAMES;
509 |
510 | this.dx = 0;
511 | this.dy = 0;
512 | this.x = normalizex(this.x + (left ? -dx : dx));
513 | this.y = this.y + dy;
514 |
515 | if (--(this.stepCount) == 0)
516 | this.stepping = DIR.NONE;
517 | },
518 |
519 | startClimbing: function() {
520 | this.climbing = true;
521 | this.dx = 0;
522 | },
523 |
524 | stopClimbing: function(point) {
525 | this.climbing = false;
526 | this.dy = 0;
527 | this.y = point ? row2y(point.row + 1) : this.y;
528 | return true;
529 | },
530 |
531 | hitMonster: function() {
532 | this.hurting = true;
533 | return true;
534 | }
535 |
536 | });
537 |
538 | //===========================================================================
539 | // MONSTERS
540 | //===========================================================================
541 |
542 | var Monsters = Class.create({
543 |
544 | initialize: function(level) {
545 | this.all = this.createMonsters(level.map);
546 | },
547 |
548 | //-------------------------------------------------------------------------
549 |
550 | update: function(dt) {
551 | var n, max, all = this.all;
552 | for(n = 0, max = all.length ; n < max ; n++)
553 | all[n].update(dt);
554 | },
555 |
556 | //-------------------------------------------------------------------------
557 |
558 | createMonsters: function(source) {
559 | var row, col, type, monster, all = [];
560 | for(row = 0 ; row < tower.rows ; row++) {
561 | for(col = 0 ; col < tower.cols ; col++) {
562 | type = parseInt(source[row][col], 10);
563 | if (!isNaN(type)) {
564 | monster = new Monster(row, col, MONSTERS[type]);
565 | all.push(monster);
566 | tower.map[row][col].monster = monster;
567 | }
568 | }
569 | }
570 | return all;
571 | }
572 |
573 | });
574 |
575 | //===========================================================================
576 | // MONSTER
577 | //===========================================================================
578 |
579 | var Monster = Class.create({
580 |
581 | initialize: function(row, col, type) {
582 |
583 | this.row = row;
584 | this.col = col;
585 | this.x = col2x(col+0.5);
586 | this.y = row2y(row)
587 | this.dx = 0;
588 | this.dy = 0;
589 | this.w = type.w;
590 | this.h = type.h;
591 | this.nx = type.nx * type.w;
592 | this.ny = type.ny * type.h;
593 | this.type = type;
594 | this[type.dir] = true;
595 | this.animation = type.animation[type.dir];
596 |
597 | if (type.vertical) {
598 | this.minrow = row;
599 | this.maxrow = row;
600 | while ((this.minrow > 0) && !tower.map[this.minrow-1][col].platform && !tower.map[this.minrow-1][col].ladder)
601 | this.minrow--;
602 | while ((this.maxrow < tower.rows-1) && !tower.map[this.maxrow+1][col].platform && !tower.map[this.maxrow+1][col].ladder)
603 | this.maxrow++;
604 | this.miny = row2y(this.minrow) + this.ny;
605 | this.maxy = row2y(this.maxrow + 1) + this.ny - this.h;
606 | }
607 |
608 | if (type.horizontal) {
609 | this.mincol = col;
610 | this.maxcol = col;
611 | while ((this.mincol != normalizeColumn(col+1)) && !tower.getCell(row, this.mincol-1).platform && !tower.getCell(row, this.mincol-1).ladder && tower.getCell(row-1, this.mincol-1).platform)
612 | this.mincol = normalizeColumn(this.mincol - 1);
613 | while ((this.maxcol != normalizeColumn(col-1)) && !tower.getCell(row, this.maxcol+1).platform && !tower.getCell(row, this.maxcol+1).ladder && tower.getCell(row-1, this.maxcol+1).platform)
614 | this.maxcol = normalizeColumn(this.maxcol + 1);
615 | this.minx = col2x(this.mincol) - this.nx;
616 | this.maxx = col2x(this.maxcol + 1) - this.nx - this.w;
617 | this.wrapx = this.minx > this.maxx;
618 | }
619 |
620 | },
621 |
622 | //-------------------------------------------------------------------------
623 |
624 | update: function(dt) {
625 |
626 | if (this.left)
627 | this.dx = -this.type.speed;
628 | else if (this.right)
629 | this.dx = this.type.speed;
630 | else
631 | this.dx = 0;
632 |
633 | if (this.up)
634 | this.dy = this.type.speed;
635 | else if (this.down)
636 | this.dy = -this.type.speed;
637 | else
638 | this.dy = 0;
639 |
640 | this.x = normalizex(this.x + (dt * this.dx));
641 | this.y = this.y + (dt * this.dy);
642 |
643 | if (this.up && (this.y > this.maxy)) {
644 | this.y = this.maxy;
645 | this.up = false;
646 | this.down = true;
647 | this.animation = this.type.animation.down;
648 | }
649 | else if (this.down && (this.y < this.miny)) {
650 | this.y = this.miny;
651 | this.down = false;
652 | this.up = true;
653 | this.animation = this.type.animation.up;
654 | }
655 |
656 | if (this.left && (this.x < this.minx) && (!this.wrapx || this.x > this.maxx)) {
657 | this.x = this.minx;
658 | this.left = false;
659 | this.right = true;
660 | this.animation = this.type.animation.right;
661 | }
662 | else if (this.right && (this.x > this.maxx) && (!this.wrapx || this.x < this.minx)) {
663 | this.x = this.maxx;
664 | this.right = false;
665 | this.left = true;
666 | this.animation = this.type.animation.left;
667 | }
668 |
669 | var row = y2row(this.y - this.ny),
670 | col = x2col(this.x - this.nx);
671 |
672 | if ((row != this.row) || (col != this.col)) {
673 | tower.map[this.row][this.col].monster = null;
674 | tower.map[row][col].monster = this;
675 | this.row = row;
676 | this.col = col;
677 | }
678 |
679 | Game.animate(FPS, this);
680 | }
681 |
682 | });
683 |
684 | //===========================================================================
685 | // CAMERA
686 | //===========================================================================
687 |
688 | var Camera = Class.create({
689 |
690 | initialize: function() {
691 | this.x = player.x;
692 | this.y = player.y;
693 | this.dx = 0;
694 | this.dy = 0;
695 | this.miny = 0;
696 | this.maxy = tower.h;
697 | },
698 |
699 | update: function(dt) {
700 | this.x = player.x;
701 | this.y = player.y;
702 | this.dx = player.dx;
703 | this.dy = player.dy;
704 | }
705 |
706 | });
707 |
708 | //===========================================================================
709 | // RENDERER
710 | //===========================================================================
711 |
712 | var Renderer = Class.create({
713 |
714 | initialize: function(images) {
715 | this.images = images;
716 | this.canvas = Game.Canvas.init(Dom.get('canvas'), WIDTH, HEIGHT);
717 | this.ctx = this.canvas.getContext('2d');
718 | this.stars = this.createStars();
719 | this.gradient = this.createGradient();
720 | this.ground = this.createGround();
721 | this.debug = Dom.get('debug');
722 | this.score = Dom.get('score');
723 | this.vscore = 0;
724 | this.platformWidth = 2 * tower.or * Math.tan((360/tower.cols) * Math.PI / 360);
725 | },
726 |
727 | //-------------------------------------------------------------------------
728 |
729 | render: function(dt) {
730 |
731 | player.rx = normalizex(Game.Math.lerp(player.x, player.dx, dt));
732 | player.ry = Game.Math.lerp(player.y, player.dy, dt);
733 | camera.rx = normalizex(Game.Math.lerp(camera.x, camera.dx, dt));
734 | camera.ry = Game.Math.lerp(camera.y, camera.dy, dt);
735 |
736 | player.ry = Math.max(0, player.ry); // dont let sub-frame interpolation take the player below the horizon
737 | camera.ry = Math.max(0, camera.ry); // dont let sub-frame interpolation take the camera below the horizon
738 |
739 | this.ctx.clearRect(0, 0, WIDTH, HEIGHT);
740 | this.renderStars(this.ctx);
741 | this.ctx.save();
742 | this.ctx.translate(WIDTH/2, 0);
743 | this.renderBack(this.ctx);
744 | this.renderTower(this.ctx);
745 | this.renderFront(this.ctx);
746 | this.renderGround(this.ctx);
747 | this.renderPlayer(this.ctx);
748 | this.renderScore(this.ctx);
749 | this.ctx.restore();
750 |
751 | // Dom.set(debug, player.debug);
752 |
753 | },
754 |
755 | //-------------------------------------------------------------------------
756 |
757 | renderStars: function(ctx) {
758 |
759 | var x = Game.Math.normalize(WIDTH * camera.x/tower.w, 0, WIDTH),
760 | y = Game.Math.normalize(HEIGHT * camera.y/tower.h, 0, HEIGHT),
761 | nx = WIDTH - x,
762 | ny = HEIGHT - y;
763 |
764 | ctx.drawImage(this.stars, 0, 0, nx, ny, x, y, nx, ny);
765 | if (x > 0)
766 | ctx.drawImage(this.stars, nx, 0, x, ny, 0, y, x, ny);
767 | if (y > 0)
768 | ctx.drawImage(this.stars, 0, ny, nx, y, x, 0, nx, y);
769 | if ((x > 0) && (y > 0))
770 | ctx.drawImage(this.stars, nx, ny, x, y, 0, 0, x, y);
771 |
772 | },
773 |
774 | //-------------------------------------------------------------------------
775 |
776 | renderGround: function(ctx) {
777 | var ground = this.ground,
778 | x = ground.w * (camera.rx/tower.w),
779 | y = ty(0),
780 | w = Math.min(WIDTH, ground.w-x),
781 | w2 = WIDTH - w;
782 | ctx.drawImage(ground.image, x, 0, w, ground.h, -WIDTH/2, y, w, ground.h);
783 | if (w2 > 0)
784 | ctx.drawImage(ground.image, 0, 0, w2, ground.h, -WIDTH/2 + w, y, w2, ground.h);
785 | },
786 |
787 | //-------------------------------------------------------------------------
788 |
789 | renderTower: function(ctx) {
790 |
791 | var offsets = [0, 0.5],
792 | top = Math.max(ty(tower.h), 0),
793 | bottom = Math.min(ty(0), HEIGHT);
794 |
795 | ctx.fillStyle = this.gradient;
796 | ctx.fillRect(-tower.ir, top, tower.ir * 2, bottom - top);
797 | ctx.strokeStyle = tower.color.stroke;
798 | ctx.lineWidth=1;
799 |
800 | ctx.beginPath();
801 | var n, y, offset = 0;
802 | for(n = 0 ; n < tower.rows ; n++) {
803 | y = ty(n*ROW_HEIGHT);
804 | if (Game.Math.between(y, -ROW_HEIGHT, HEIGHT + ROW_HEIGHT)) {
805 | ctx.moveTo(-tower.ir, y);
806 | ctx.lineTo( tower.ir, y);
807 | this.renderBricks(ctx, y, offsets[offset]);
808 | }
809 | offset = (offset < offsets.length-1 ? offset + 1 : 0);
810 | }
811 |
812 | ctx.moveTo(-tower.ir, top);
813 | ctx.lineTo( tower.ir, top);
814 | ctx.moveTo(-tower.ir, top);
815 | ctx.lineTo(-tower.ir, bottom);
816 | ctx.moveTo( tower.ir, top);
817 | ctx.lineTo( tower.ir, bottom);
818 |
819 | ctx.stroke();
820 | },
821 |
822 | //-------------------------------------------------------------------------
823 |
824 | renderBricks: function(ctx, y, offset) {
825 | var n, x, a;
826 | for(n = 0 ; n < tower.cols ; n++) {
827 | x = (n+offset) * COL_WIDTH;
828 | a = Game.Math.normalizeAngle180(x2a(x) - x2a(camera.rx));
829 | if (Game.Math.between(a, -90, 90)) {
830 | x = tx(x, tower.ir);
831 | ctx.moveTo(x, y);
832 | ctx.lineTo(x, y - ROW_HEIGHT);
833 | }
834 | }
835 | },
836 |
837 | //-------------------------------------------------------------------------
838 |
839 | renderBack: function(ctx) {
840 |
841 | ctx.strokeStyle = tower.color.stroke;
842 | ctx.lineWidth = 2;
843 |
844 | var left = x2col(camera.rx - tower.w/4),
845 | right = x2col(camera.rx + tower.w/4);
846 |
847 | this.renderQuadrant(ctx, normalizeColumn(left - 3), left, +1);
848 | this.renderQuadrant(ctx, normalizeColumn(right + 3), right, -1);
849 |
850 | },
851 |
852 | //-------------------------------------------------------------------------
853 |
854 | renderFront: function(ctx) {
855 |
856 | ctx.strokeStyle = tower.color.stroke;
857 | ctx.lineWidth = 2;
858 |
859 | var left = x2col(camera.rx - tower.w/4),
860 | center = x2col(camera.rx),
861 | right = x2col(camera.rx + tower.w/4);
862 |
863 | this.renderQuadrant(ctx, left, normalizeColumn(center + 0), +1);
864 | this.renderQuadrant(ctx, right, normalizeColumn(center - 1), -1);
865 |
866 | },
867 |
868 | //-------------------------------------------------------------------------
869 |
870 | renderQuadrant: function(ctx, min, max, dir) {
871 | var r, y, cell,
872 | rmin = Math.max(0, y2row(camera.ry - HORIZON) - 1),
873 | rmax = Math.min(tower.rows - 1, rmin + (HEIGHT / ROW_HEIGHT + 1)),
874 | c = min;
875 | while (c != max) {
876 | for(r = rmin ; r <= rmax ; r++) {
877 | y = ty(r * ROW_HEIGHT);
878 | cell = tower.getCell(r, c);
879 | if (cell.platform)
880 | this.renderPlatform(ctx, c, y);
881 | else if (cell.ladder)
882 | this.renderLadder(ctx, c, y);
883 | else if (cell.coin)
884 | this.renderCoin(ctx, c, y);
885 | if (cell.monster)
886 | this.renderMonster(ctx, c, y, cell.monster);
887 | }
888 | c = normalizeColumn(c + dir);
889 | }
890 | },
891 |
892 | //-------------------------------------------------------------------------
893 |
894 | renderPlatform: function(ctx, col, y) {
895 |
896 | var x = col2x(col+0.5),
897 | a = Game.Math.normalizeAngle180(x2a(x) - x2a(camera.rx)),
898 | x0 = tx(x, tower.or),
899 | x1 = x0 - this.platformWidth/2,
900 | x2 = x0 + this.platformWidth/2;
901 |
902 | ctx.fillStyle = Game.Math.darken(tower.color.platform, 60 * Math.min(1, Math.abs(a/90)));
903 | ctx.fillRect( x1, y - ROW_HEIGHT, x2 - x1, ROW_HEIGHT);
904 | ctx.strokeRect(x1, y - ROW_HEIGHT, x2 - x1, ROW_HEIGHT);
905 |
906 | },
907 |
908 | //-------------------------------------------------------------------------
909 |
910 | renderLadder: function(ctx, col, y) {
911 |
912 | var ladder = this.images.ladder,
913 | x = col2x(col+0.5),
914 | a = Game.Math.normalizeAngle180(x2a(x) - x2a(camera.rx)),
915 | d = Math.floor(12 * Math.min(1, Math.abs(a/90))),
916 | x0 = tx(x, tower.or),
917 | x1 = x0 - ladder.width/2 + 10,
918 | x2 = x0 + ladder.width/2 - 10,
919 | w = x2 - x1,
920 | ny = 4, // overdraw the ladders
921 | h = ROW_HEIGHT + ny;
922 |
923 | ctx.drawImage(ladder, 0, d*30, ladder.width, 30, x1, y-h, w, h);
924 |
925 | },
926 |
927 | //-------------------------------------------------------------------------
928 |
929 | renderCoin: function(ctx, col, y) {
930 |
931 | var coins = this.images.coins,
932 | x = col2x(col+0.5),
933 | a = Game.Math.normalizeAngle180(x2a(x) - x2a(camera.rx)),
934 | d = Math.floor(12 * Math.min(1, Math.abs(a/90))),
935 | w = COIN.W,
936 | h = COIN.H,
937 | x0 = tx(x, tower.or),
938 | x1 = x0 - w/2,
939 | x2 = x0 + w/2;
940 |
941 | ctx.drawImage(coins, 0, d*36, coins.width, 36, x1, y-h, w, h);
942 |
943 | },
944 |
945 | //-------------------------------------------------------------------------
946 |
947 | renderMonster: function(ctx, col, y, monster) {
948 |
949 | var a = monster.animation,
950 | x = tx(monster.x, tower.or) + monster.nx,
951 | y = ty(monster.y) + monster.ny,
952 | w = monster.w,
953 | h = monster.h;
954 |
955 | ctx.drawImage(this.images.monster, a.x + (monster.animationFrame*a.w), a.y, a.w, a.h, x, y - h - 1, w, h);
956 |
957 | },
958 |
959 | //-------------------------------------------------------------------------
960 |
961 | renderPlayer: function(ctx) {
962 | ctx.drawImage(this.images.player, player.animation.x + (player.animationFrame * player.animation.w), player.animation.y, player.animation.w, player.animation.h, tx(player.rx, tower.ir) - player.w/2, ty(player.ry) - player.h, player.w, player.h);
963 | if (PLAYER.DEBUG) {
964 | ctx.strokeStyle = "#000000";
965 | ctx.lineWidth = 1;
966 | ctx.strokeRect(tx(player.rx, tower.ir) - player.w/2, ty(player.ry + player.h), player.w, player.h);
967 | ctx.fillStyle = "#800000";
968 | ctx.fillRect(tx(player.rx, tower.ir) + player.collision.topLeft.x, ty(player.ry + player.collision.topLeft.y), 5, 5);
969 | ctx.fillRect(tx(player.rx, tower.ir) + player.collision.topRight.x, ty(player.ry + player.collision.topRight.y), -5, 5);
970 | ctx.fillRect(tx(player.rx, tower.ir) + player.collision.middleLeft.x, ty(player.ry + player.collision.middleLeft.y), 5, 5);
971 | ctx.fillRect(tx(player.rx, tower.ir) + player.collision.middleRight.x, ty(player.ry + player.collision.middleRight.y), -5, 5);
972 | ctx.fillRect(tx(player.rx, tower.ir) + player.collision.bottomLeft.x, ty(player.ry + player.collision.bottomLeft.y), 5, -5);
973 | ctx.fillRect(tx(player.rx, tower.ir) + player.collision.bottomRight.x, ty(player.ry + player.collision.bottomRight.y), -5, -5);
974 | ctx.fillRect(tx(player.rx, tower.ir) + player.collision.ladderUp.x - 2.5, ty(player.ry + player.collision.ladderUp.y), 5, 5);
975 | ctx.fillRect(tx(player.rx, tower.ir) + player.collision.ladderDown.x - 2.5, ty(player.ry + player.collision.ladderDown.y), 5, -5);
976 | }
977 | },
978 |
979 | //-------------------------------------------------------------------------
980 |
981 | renderScore: function(ctx) {
982 | if (player.score > this.vscore) {
983 | this.vscore = this.vscore + 2;
984 | Dom.set(score, this.vscore);
985 | }
986 | },
987 |
988 | //-------------------------------------------------------------------------
989 |
990 | createStars: function() {
991 | return Game.Canvas.render(WIDTH, HEIGHT, function(ctx) {
992 | var n, x, y, r, max = 500,
993 | colors = ["#FFFFFF", "#FFFFFF", "#FFFFFF", "#FFFFFF", "#FFFFFF", "#FFFFFF", "#FFFFFF", "#800000", "#808000"],
994 | sizes = [0.25, 0.25, 0.25, 0.25, 0.5, 0.5, 0.5, 0.5, 1, 1, 1, 1, 2, 2];
995 | for(n = 0 ; n < max ; n++) {
996 | ctx.fillStyle = Game.Math.darken(Game.Math.randomChoice(colors), Game.Math.random(1,100));
997 | x = Game.Math.randomInt(2, WIDTH-4);
998 | y = Game.Math.randomInt(2, HEIGHT-4);
999 | r = Game.Math.randomChoice(sizes);
1000 | ctx.fillRect(x,y,r,r);
1001 | }
1002 | });
1003 | },
1004 |
1005 | //-------------------------------------------------------------------------
1006 |
1007 | createGradient: function() {
1008 |
1009 | var radius = tower.ir,
1010 | color = tower.color.wall,
1011 | gradient = this.ctx.createLinearGradient(-radius, 0, radius, 0);
1012 |
1013 | gradient.addColorStop(0, Game.Math.darken(color, 20));
1014 | gradient.addColorStop(0.3, Game.Math.brighten(color, 10));
1015 | gradient.addColorStop(0.5, Game.Math.brighten(color, 15));
1016 | gradient.addColorStop(0.7, Game.Math.brighten(color, 10));
1017 | gradient.addColorStop(1, Game.Math.darken(color, 20));
1018 |
1019 | return gradient;
1020 |
1021 | },
1022 |
1023 | //-------------------------------------------------------------------------
1024 |
1025 | createGround: function() {
1026 | var w = WIDTH*GROUND_SPEED,
1027 | h = HORIZON,
1028 | tile = this.images.ground,
1029 | tw = tile.width,
1030 | th = tile.height,
1031 | max = Math.floor(w/tile.width),
1032 | dw = w/max,
1033 | image = Game.Canvas.render(w, h, function(ctx) {
1034 | var n;
1035 | for(n = 0 ; n < max ; n++)
1036 | ctx.drawImage(tile, 0, 0, tw, th, n * dw, 0, dw, h);
1037 | });
1038 | return { w: w, h: h, image: image };
1039 | }
1040 |
1041 | });
1042 |
1043 | //===========================================================================
1044 | // LETS PLAY!
1045 | //===========================================================================
1046 |
1047 | run();
1048 |
1049 | //---------------------------------------------------------------------------
1050 |
1051 | })();
1052 |
--------------------------------------------------------------------------------
/levels/demo.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "demo level",
3 | "color": {
4 | "wall": "#70482C",
5 | "platform": "#C0C0C0",
6 | "stroke": "#000000"
7 | },
8 | "map": [
9 | "XXXXXXXXXXXXXXXXXHXX",
10 | " H ",
11 | " H ",
12 | " H ",
13 | " o H ",
14 | " o o H ",
15 | " o oooooooo H ",
16 | " o XXXXXXXH X ",
17 | " o H ",
18 | " o H ",
19 | " o H ",
20 | " o H ",
21 | " o H ",
22 | " o H ",
23 | " o H ",
24 | " o H ",
25 | " o H ",
26 | " o H ",
27 | " o H ",
28 | " o H ",
29 | " o H ",
30 | " o H ",
31 | " o H ",
32 | " o H ",
33 | " o H ",
34 | " o H ",
35 | " o H ",
36 | " o H ",
37 | " o H ",
38 | " o H ",
39 | " o H ",
40 | " o H ",
41 | " o H ",
42 | " o H ",
43 | " o H ",
44 | " o H ",
45 | " o H ",
46 | " o H ",
47 | " o H ",
48 | " o H ",
49 | " o H ",
50 | " o H ",
51 | " o H ",
52 | " o H ",
53 | " o H ",
54 | " o H ",
55 | " o H ",
56 | " o H ",
57 | " o H ",
58 | " o H ",
59 | " o H ",
60 | " o H ",
61 | " o H ",
62 | " H ",
63 | " H ",
64 | " H ",
65 | " H ",
66 | " oooooooooooH ",
67 | " HXXXXXXXXXXX ",
68 | " H ",
69 | " H 0 ",
70 | " H 0 ",
71 | " H 0 ",
72 | " H 0 ",
73 | " H 0 ",
74 | " H 0 ",
75 | " H 0 ",
76 | " Ho0ooooooooooo ",
77 | " XXXXXXXXXXXXXH ",
78 | " H ",
79 | " H ",
80 | " H ",
81 | " H ",
82 | " X ",
83 | " X ",
84 | " 1 X ",
85 | "XXX 2 X",
86 | " XXXX 3 ",
87 | " XXXX 2 ",
88 | " XXXX 1 ",
89 | " XXXXH",
90 | " H",
91 | " H",
92 | " H",
93 | " XX",
94 | " 0 X ",
95 | " o HX ",
96 | " o H ",
97 | " o H ",
98 | " o H ",
99 | " o H ",
100 | " o H ",
101 | " o H ",
102 | " o H ",
103 | " o H ",
104 | " o 0H ",
105 | " o H ",
106 | " o H ",
107 | " o H ",
108 | " o H ",
109 | " o H ",
110 | " o H ",
111 | " o0 H 1 ",
112 | "XXXXXX XXHXXXXXXXX",
113 | "o o H ",
114 | "o o H ",
115 | "o o H ",
116 | "o X H ",
117 | "o o H ",
118 | "X o 0 H ",
119 | " o H ",
120 | " X H ",
121 | " H ",
122 | "oooo H ",
123 | "XXXX H ",
124 | " H ",
125 | " H ",
126 | " H ",
127 | " H ",
128 | " ooooo H 1ooooo",
129 | " XXXXX X XXXXXX",
130 | " ",
131 | " ",
132 | " XX ",
133 | "X X ",
134 | " X X",
135 | " X ",
136 | "oooooooo 0X ",
137 | "HXXXXXXX X ",
138 | "H XX XX ",
139 | "H ",
140 | "H 0 ",
141 | "H 3 XX ",
142 | "XXHXXXX XXXX2 ",
143 | " H X XXX ",
144 | " H X ",
145 | " H X X ",
146 | " H ",
147 | " H ooooooooo"
148 | ]
149 | }
150 |
--------------------------------------------------------------------------------
/license:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013, 2014, 2015, 2016 Jake Gordon and contributors
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
21 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | Javascript Tower Platformer
2 | ===========================
3 |
4 | An HTML5 rotating-tower platform game inspired by the old c64 game "Nebulus"
5 |
6 | * [play the game](https://jakesgordon.com/games/tower-platformer/)
7 | * [read more](https://jakesgordon.com/writing/rotating-tower-platformer/)
8 | * [view the source](https://github.com/jakesgordon/javascript-tower-platformer)
9 |
10 | SUPPORTED BROWSERS
11 | ==================
12 |
13 | Should work in any modern browser with canvas support
14 |
15 | DEVELOPMENT
16 | ===========
17 |
18 | The game is 100% client side javascript, html and css. It should run when served up by any web server.
19 |
20 | FUTURE FEATURES
21 | ===============
22 |
23 | * level exit
24 | * game menu
25 | * multiple levels
26 | * dissolving platforms
27 | * elevators
28 | * shortcut doors
29 | * countdown timer
30 | * mobile touch support
31 | * sound fx and music
32 |
33 | TECH DEBT
34 | =========
35 |
36 | * should use an FSM to manage player state (standing/left/right/falling/climbing/hurt/etc)
37 | * allow monsters to overlap (make cell.monster an array instead of single object)
38 | * use images for tower gradient and platforms (instead of raw ctx stroke/fill calls)
39 | * direction-agnostic monster sprites (abstract)
40 | * tiny gap in ground rendering in FF/IE where image wraps (need to render image on (0.5, 0.5) boundaries)
41 |
42 | License
43 | =======
44 |
45 | [MIT](http://en.wikipedia.org/wiki/MIT_License) license.
46 |
47 |
--------------------------------------------------------------------------------