made with requirebin
Created
June 3, 2014 21:25
-
-
Save morganherlocker/1003fda9aba7ba734bae to your computer and use it in GitHub Desktop.
requirebin sketch
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var domify = require('domify') | |
var html = "<canvas id='display' width='1' height='1' style='width: 100%; height: 100%;' />" | |
document.body.appendChild(domify(html)) | |
var CIRCLE = Math.PI * 2; | |
var MOBILE = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent) | |
function Controls() { | |
this.codes = { 37: 'left', 39: 'right', 38: 'forward', 40: 'backward' }; | |
this.states = { 'left': false, 'right': false, 'forward': false, 'backward': false }; | |
document.addEventListener('keydown', this.onKey.bind(this, true), false); | |
document.addEventListener('keyup', this.onKey.bind(this, false), false); | |
document.addEventListener('touchstart', this.onTouch.bind(this), false); | |
document.addEventListener('touchmove', this.onTouch.bind(this), false); | |
document.addEventListener('touchend', this.onTouchEnd.bind(this), false); | |
} | |
Controls.prototype.onTouch = function(e) { | |
var t = e.touches[0]; | |
this.onTouchEnd(e); | |
if (t.pageY < window.innerHeight * 0.5) this.onKey(true, { keyCode: 38 }); | |
else if (t.pageX < window.innerWidth * 0.5) this.onKey(true, { keyCode: 37 }); | |
else if (t.pageY > window.innerWidth * 0.5) this.onKey(true, { keyCode: 39 }); | |
}; | |
Controls.prototype.onTouchEnd = function(e) { | |
this.states = { 'left': false, 'right': false, 'forward': false, 'backward': false }; | |
e.preventDefault(); | |
e.stopPropagation(); | |
}; | |
Controls.prototype.onKey = function(val, e) { | |
var state = this.codes[e.keyCode]; | |
if (typeof state === 'undefined') return; | |
this.states[state] = val; | |
e.preventDefault && e.preventDefault(); | |
e.stopPropagation && e.stopPropagation(); | |
}; | |
function Bitmap(src, width, height) { | |
this.image = new Image(); | |
this.image.src = src; | |
this.width = width; | |
this.height = height; | |
} | |
function Player(x, y, direction) { | |
this.x = x; | |
this.y = y; | |
this.direction = direction; | |
this.weapon = new Bitmap('http://www.staceyreid.com/news/wp-content/uploads/2011/12/bone.png', -259, 320); | |
this.paces = 0; | |
} | |
Player.prototype.rotate = function(angle) { | |
this.direction = (this.direction + angle + CIRCLE) % (CIRCLE); | |
}; | |
Player.prototype.walk = function(distance, map) { | |
var dx = Math.cos(this.direction) * distance; | |
var dy = Math.sin(this.direction) * distance; | |
if (map.get(this.x + dx, this.y) <= 0) this.x += dx; | |
if (map.get(this.x, this.y + dy) <= 0) this.y += dy; | |
this.paces += distance; | |
}; | |
Player.prototype.update = function(controls, map, seconds) { | |
if (controls.left) this.rotate(-Math.PI * seconds); | |
if (controls.right) this.rotate(Math.PI * seconds); | |
if (controls.forward) this.walk(3 * seconds, map); | |
if (controls.backward) this.walk(-3 * seconds, map); | |
}; | |
function Map(size) { | |
this.size = size; | |
this.wallGrid = new Uint8Array(size * size); | |
this.skybox = new Bitmap('http://www.fanaru.com/doge/image/26622-doge-saturn-doge.jpg', 4000, 1900); | |
this.wallTexture = new Bitmap('http://doge2048.com/meta/doge-600.png', 524, 524); | |
this.light = 0; | |
} | |
Map.prototype.get = function(x, y) { | |
x = Math.floor(x); | |
y = Math.floor(y); | |
if (x < 0 || x > this.size - 1 || y < 0 || y > this.size - 1) return -1; | |
return this.wallGrid[y * this.size + x]; | |
}; | |
Map.prototype.randomize = function() { | |
for (var i = 0; i < this.size * this.size; i++) { | |
this.wallGrid[i] = Math.random() < 0.3 ? 1 : 0; | |
} | |
}; | |
Map.prototype.cast = function(point, angle, range) { | |
var self = this; | |
var sin = Math.sin(angle); | |
var cos = Math.cos(angle); | |
var noWall = { length2: Infinity }; | |
return ray({ x: point.x, y: point.y, height: 0, distance: 0 }); | |
function ray(origin) { | |
var stepX = step(sin, cos, origin.x, origin.y); | |
var stepY = step(cos, sin, origin.y, origin.x, true); | |
var nextStep = stepX.length2 < stepY.length2 | |
? inspect(stepX, 1, 0, origin.distance, stepX.y) | |
: inspect(stepY, 0, 1, origin.distance, stepY.x); | |
if (nextStep.distance > range) return [origin]; | |
return [origin].concat(ray(nextStep)); | |
} | |
function step(rise, run, x, y, inverted) { | |
if (run === 0) return noWall; | |
var dx = run > 0 ? Math.floor(x + 1) - x : Math.ceil(x - 1) - x; | |
var dy = dx * (rise / run); | |
return { | |
x: inverted ? y + dy : x + dx, | |
y: inverted ? x + dx : y + dy, | |
length2: dx * dx + dy * dy | |
}; | |
} | |
function inspect(step, shiftX, shiftY, distance, offset) { | |
var dx = cos < 0 ? shiftX : 0; | |
var dy = sin < 0 ? shiftY : 0; | |
step.height = self.get(step.x - dx, step.y - dy); | |
step.distance = distance + Math.sqrt(step.length2); | |
if (shiftX) step.shading = cos < 0 ? 2 : 0; | |
else step.shading = sin < 0 ? 2 : 1; | |
step.offset = offset - Math.floor(offset); | |
return step; | |
} | |
}; | |
Map.prototype.update = function(seconds) { | |
if (this.light > 0) this.light = Math.max(this.light - 10 * seconds, 0); | |
else if (Math.random() * 5 < seconds) this.light = 2; | |
}; | |
function Camera(canvas, resolution, fov) { | |
this.ctx = canvas.getContext('2d'); | |
this.width = canvas.width = window.innerWidth * 0.5; | |
this.height = canvas.height = window.innerHeight * 0.5; | |
this.resolution = resolution; | |
this.spacing = this.width / resolution; | |
this.fov = fov; | |
this.range = MOBILE ? 8 : 14; | |
this.lightRange = 5; | |
this.scale = (this.width + this.height) / 1200; | |
} | |
Camera.prototype.render = function(player, map) { | |
this.drawSky(player.direction, map.skybox, map.light); | |
this.drawColumns(player, map); | |
this.drawWeapon(player.weapon, player.paces); | |
}; | |
Camera.prototype.drawSky = function(direction, sky, ambient) { | |
var width = this.width * (CIRCLE / this.fov); | |
var left = -width * direction / CIRCLE; | |
this.ctx.save(); | |
this.ctx.drawImage(sky.image, left, 0, width, this.height); | |
if (left < width - this.width) { | |
this.ctx.drawImage(sky.image, left + width, 0, width, this.height); | |
} | |
if (ambient > 0) { | |
this.ctx.fillStyle = '#ffffff'; | |
this.ctx.globalAlpha = ambient * 0.1; | |
this.ctx.fillRect(0, this.height * 0.5, this.width, this.height * 0.5); | |
} | |
this.ctx.restore(); | |
}; | |
Camera.prototype.drawColumns = function(player, map) { | |
this.ctx.save(); | |
for (var column = 0; column < this.resolution; column++) { | |
var angle = this.fov * (column / this.resolution - 0.5); | |
var ray = map.cast(player, player.direction + angle, this.range); | |
this.drawColumn(column, ray, angle, map); | |
} | |
this.ctx.restore(); | |
}; | |
Camera.prototype.drawWeapon = function(weapon, paces) { | |
var bobX = Math.cos(paces * 2) * this.scale * 6; | |
var bobY = Math.sin(paces * 4) * this.scale * 6; | |
var left = this.width * 0.66 + bobX; | |
var top = this.height * 0.6 + bobY; | |
this.ctx.drawImage(weapon.image, left, top, weapon.width * this.scale, weapon.height * this.scale); | |
}; | |
Camera.prototype.drawColumn = function(column, ray, angle, map) { | |
var ctx = this.ctx; | |
var texture = map.wallTexture; | |
var left = Math.floor(column * this.spacing); | |
var width = Math.ceil(this.spacing); | |
var hit = -1; | |
while (++hit < ray.length && ray[hit].height <= 0); | |
for (var s = ray.length - 1; s >= 0; s--) { | |
var step = ray[s]; | |
var rainDrops = Math.pow(Math.random(), 3) * s; | |
var rain = (rainDrops > 0) && this.project(0.1, angle, step.distance); | |
if (s === hit) { | |
var textureX = Math.floor(texture.width * step.offset); | |
var wall = this.project(step.height, angle, step.distance); | |
ctx.globalAlpha = 1; | |
ctx.drawImage(texture.image, textureX, 0, 1, texture.height, left, wall.top, width, wall.height); | |
ctx.fillStyle = '#000000'; | |
ctx.globalAlpha = Math.max((step.distance + step.shading) / this.lightRange - map.light, 0); | |
ctx.fillRect(left, wall.top, width, wall.height); | |
} | |
ctx.fillStyle = '#ffffff'; | |
ctx.globalAlpha = 0.15; | |
while (--rainDrops > 0) ctx.fillRect(left, Math.random() * rain.top, .3, rain.height); | |
} | |
}; | |
Camera.prototype.project = function(height, angle, distance) { | |
var z = distance * Math.cos(angle); | |
var wallHeight = this.height * height / z; | |
var bottom = this.height / 2 * (1 + 1 / z); | |
return { | |
top: bottom - wallHeight, | |
height: wallHeight | |
}; | |
}; | |
function GameLoop() { | |
this.frame = this.frame.bind(this); | |
this.lastTime = 0; | |
this.callback = function() {}; | |
} | |
GameLoop.prototype.start = function(callback) { | |
this.callback = callback; | |
requestAnimationFrame(this.frame); | |
}; | |
GameLoop.prototype.frame = function(time) { | |
var seconds = (time - this.lastTime) / 1000; | |
this.lastTime = time; | |
if (seconds < 0.2) this.callback(seconds); | |
requestAnimationFrame(this.frame); | |
}; | |
var display = document.getElementById('display'); | |
var player = new Player(15.3, -1.2, Math.PI * 0.3); | |
var map = new Map(32); | |
var controls = new Controls(); | |
var camera = new Camera(display, MOBILE ? 160 : 320, Math.PI * 0.4); | |
var loop = new GameLoop(); | |
map.randomize(); | |
loop.start(function frame(seconds) { | |
map.update(seconds); | |
player.update(controls.states, map, seconds); | |
camera.render(player, map); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require=function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s}({"6cIyZX":[function(require,module,exports){module.exports=parse;var map={legend:[1,"<fieldset>","</fieldset>"],tr:[2,"<table><tbody>","</tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],_default:[0,"",""]};map.td=map.th=[3,"<table><tbody><tr>","</tr></tbody></table>"];map.option=map.optgroup=[1,'<select multiple="multiple">',"</select>"];map.thead=map.tbody=map.colgroup=map.caption=map.tfoot=[1,"<table>","</table>"];map.text=map.circle=map.ellipse=map.line=map.path=map.polygon=map.polyline=map.rect=[1,'<svg xmlns="http://www.w3.org/2000/svg" version="1.1">',"</svg>"];function parse(html){if("string"!=typeof html)throw new TypeError("String expected");var m=/<([\w:]+)/.exec(html);if(!m)return document.createTextNode(html);html=html.replace(/^\s+|\s+$/g,"");var tag=m[1];if(tag=="body"){var el=document.createElement("html");el.innerHTML=html;return el.removeChild(el.lastChild)}var wrap=map[tag]||map._default;var depth=wrap[0];var prefix=wrap[1];var suffix=wrap[2];var el=document.createElement("div");el.innerHTML=prefix+html+suffix;while(depth--)el=el.lastChild;if(el.firstChild==el.lastChild){return el.removeChild(el.firstChild)}var fragment=document.createDocumentFragment();while(el.firstChild){fragment.appendChild(el.removeChild(el.firstChild))}return fragment}},{}],domify:[function(require,module,exports){module.exports=require("6cIyZX")},{}]},{},[]);var domify=require("domify");var html="<canvas id='display' width='1' height='1' style='width: 100%; height: 100%;' />";document.body.appendChild(domify(html));var CIRCLE=Math.PI*2;var MOBILE=/Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent);function Controls(){this.codes={37:"left",39:"right",38:"forward",40:"backward"};this.states={left:false,right:false,forward:false,backward:false};document.addEventListener("keydown",this.onKey.bind(this,true),false);document.addEventListener("keyup",this.onKey.bind(this,false),false);document.addEventListener("touchstart",this.onTouch.bind(this),false);document.addEventListener("touchmove",this.onTouch.bind(this),false);document.addEventListener("touchend",this.onTouchEnd.bind(this),false)}Controls.prototype.onTouch=function(e){var t=e.touches[0];this.onTouchEnd(e);if(t.pageY<window.innerHeight*.5)this.onKey(true,{keyCode:38});else if(t.pageX<window.innerWidth*.5)this.onKey(true,{keyCode:37});else if(t.pageY>window.innerWidth*.5)this.onKey(true,{keyCode:39})};Controls.prototype.onTouchEnd=function(e){this.states={left:false,right:false,forward:false,backward:false};e.preventDefault();e.stopPropagation()};Controls.prototype.onKey=function(val,e){var state=this.codes[e.keyCode];if(typeof state==="undefined")return;this.states[state]=val;e.preventDefault&&e.preventDefault();e.stopPropagation&&e.stopPropagation()};function Bitmap(src,width,height){this.image=new Image;this.image.src=src;this.width=width;this.height=height}function Player(x,y,direction){this.x=x;this.y=y;this.direction=direction;this.weapon=new Bitmap("http://www.staceyreid.com/news/wp-content/uploads/2011/12/bone.png",-259,320);this.paces=0}Player.prototype.rotate=function(angle){this.direction=(this.direction+angle+CIRCLE)%CIRCLE};Player.prototype.walk=function(distance,map){var dx=Math.cos(this.direction)*distance;var dy=Math.sin(this.direction)*distance;if(map.get(this.x+dx,this.y)<=0)this.x+=dx;if(map.get(this.x,this.y+dy)<=0)this.y+=dy;this.paces+=distance};Player.prototype.update=function(controls,map,seconds){if(controls.left)this.rotate(-Math.PI*seconds);if(controls.right)this.rotate(Math.PI*seconds);if(controls.forward)this.walk(3*seconds,map);if(controls.backward)this.walk(-3*seconds,map)};function Map(size){this.size=size;this.wallGrid=new Uint8Array(size*size);this.skybox=new Bitmap("http://www.fanaru.com/doge/image/26622-doge-saturn-doge.jpg",4e3,1900);this.wallTexture=new Bitmap("http://doge2048.com/meta/doge-600.png",524,524);this.light=0}Map.prototype.get=function(x,y){x=Math.floor(x);y=Math.floor(y);if(x<0||x>this.size-1||y<0||y>this.size-1)return-1;return this.wallGrid[y*this.size+x]};Map.prototype.randomize=function(){for(var i=0;i<this.size*this.size;i++){this.wallGrid[i]=Math.random()<.3?1:0}};Map.prototype.cast=function(point,angle,range){var self=this;var sin=Math.sin(angle);var cos=Math.cos(angle);var noWall={length2:Infinity};return ray({x:point.x,y:point.y,height:0,distance:0});function ray(origin){var stepX=step(sin,cos,origin.x,origin.y);var stepY=step(cos,sin,origin.y,origin.x,true);var nextStep=stepX.length2<stepY.length2?inspect(stepX,1,0,origin.distance,stepX.y):inspect(stepY,0,1,origin.distance,stepY.x);if(nextStep.distance>range)return[origin];return[origin].concat(ray(nextStep))}function step(rise,run,x,y,inverted){if(run===0)return noWall;var dx=run>0?Math.floor(x+1)-x:Math.ceil(x-1)-x;var dy=dx*(rise/run);return{x:inverted?y+dy:x+dx,y:inverted?x+dx:y+dy,length2:dx*dx+dy*dy}}function inspect(step,shiftX,shiftY,distance,offset){var dx=cos<0?shiftX:0;var dy=sin<0?shiftY:0;step.height=self.get(step.x-dx,step.y-dy);step.distance=distance+Math.sqrt(step.length2);if(shiftX)step.shading=cos<0?2:0;else step.shading=sin<0?2:1;step.offset=offset-Math.floor(offset);return step}};Map.prototype.update=function(seconds){if(this.light>0)this.light=Math.max(this.light-10*seconds,0);else if(Math.random()*5<seconds)this.light=2};function Camera(canvas,resolution,fov){this.ctx=canvas.getContext("2d");this.width=canvas.width=window.innerWidth*.5;this.height=canvas.height=window.innerHeight*.5;this.resolution=resolution;this.spacing=this.width/resolution;this.fov=fov;this.range=MOBILE?8:14;this.lightRange=5;this.scale=(this.width+this.height)/1200}Camera.prototype.render=function(player,map){this.drawSky(player.direction,map.skybox,map.light);this.drawColumns(player,map);this.drawWeapon(player.weapon,player.paces)};Camera.prototype.drawSky=function(direction,sky,ambient){var width=this.width*(CIRCLE/this.fov);var left=-width*direction/CIRCLE;this.ctx.save();this.ctx.drawImage(sky.image,left,0,width,this.height);if(left<width-this.width){this.ctx.drawImage(sky.image,left+width,0,width,this.height)}if(ambient>0){this.ctx.fillStyle="#ffffff";this.ctx.globalAlpha=ambient*.1;this.ctx.fillRect(0,this.height*.5,this.width,this.height*.5)}this.ctx.restore()};Camera.prototype.drawColumns=function(player,map){this.ctx.save();for(var column=0;column<this.resolution;column++){var angle=this.fov*(column/this.resolution-.5);var ray=map.cast(player,player.direction+angle,this.range);this.drawColumn(column,ray,angle,map)}this.ctx.restore()};Camera.prototype.drawWeapon=function(weapon,paces){var bobX=Math.cos(paces*2)*this.scale*6;var bobY=Math.sin(paces*4)*this.scale*6;var left=this.width*.66+bobX;var top=this.height*.6+bobY;this.ctx.drawImage(weapon.image,left,top,weapon.width*this.scale,weapon.height*this.scale)};Camera.prototype.drawColumn=function(column,ray,angle,map){var ctx=this.ctx;var texture=map.wallTexture;var left=Math.floor(column*this.spacing);var width=Math.ceil(this.spacing);var hit=-1;while(++hit<ray.length&&ray[hit].height<=0);for(var s=ray.length-1;s>=0;s--){var step=ray[s];var rainDrops=Math.pow(Math.random(),3)*s;var rain=rainDrops>0&&this.project(.1,angle,step.distance);if(s===hit){var textureX=Math.floor(texture.width*step.offset);var wall=this.project(step.height,angle,step.distance);ctx.globalAlpha=1;ctx.drawImage(texture.image,textureX,0,1,texture.height,left,wall.top,width,wall.height);ctx.fillStyle="#000000";ctx.globalAlpha=Math.max((step.distance+step.shading)/this.lightRange-map.light,0);ctx.fillRect(left,wall.top,width,wall.height)}ctx.fillStyle="#ffffff";ctx.globalAlpha=.15;while(--rainDrops>0)ctx.fillRect(left,Math.random()*rain.top,.3,rain.height)}};Camera.prototype.project=function(height,angle,distance){var z=distance*Math.cos(angle);var wallHeight=this.height*height/z;var bottom=this.height/2*(1+1/z);return{top:bottom-wallHeight,height:wallHeight}};function GameLoop(){this.frame=this.frame.bind(this);this.lastTime=0;this.callback=function(){}}GameLoop.prototype.start=function(callback){this.callback=callback;requestAnimationFrame(this.frame)};GameLoop.prototype.frame=function(time){var seconds=(time-this.lastTime)/1e3;this.lastTime=time;if(seconds<.2)this.callback(seconds);requestAnimationFrame(this.frame)};var display=document.getElementById("display");var player=new Player(15.3,-1.2,Math.PI*.3);var map=new Map(32);var controls=new Controls;var camera=new Camera(display,MOBILE?160:320,Math.PI*.4);var loop=new GameLoop;map.randomize();loop.start(function frame(seconds){map.update(seconds);player.update(controls.states,map,seconds);camera.render(player,map)}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name": "requirebin-sketch", | |
"version": "1.0.0", | |
"dependencies": { | |
"domify": "1.2.2" | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<style type='text/css'>html, body { margin: 0; padding: 0; border: 0; } | |
body, html { height: 100%; width: 100%; }</style> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment