├── 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 | 38 |
39 | Sorry, this example cannot be run because your browser does not support the <canvas> element 40 |
41 |
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 | --------------------------------------------------------------------------------