<title>Raycaster</title>
</head>
<body style='background: #000; margin: 0; padding: 0; width: 100%; height: 100%;'>
- <canvas id='display' width='1' height='1' style='width: 100%; height: 100%;' />
+ <canvas id='display' style='width: 100%; height: 100%;' />
<script>
"use strict";
const TAU = Math.PI * 2;
const WALK_SPEED = 5;
-const TURN_SPEED = Math.PI * 0.5;
+const TURN_SPEED = TAU/4;
const MAX_VIEW_DIST = 32;
const LIGHT_RANGE = 20;
const FOCAL_LENGTH = 0.8;
const VIEW_SCALER = 1.0;
const MAP_SIZE = 32;
+const START_POS = { x: 15.3, y: -1.2, direction: Math.PI * 0.3};
+
+const Keys = { left: 37, right: 39, up: 38, down: 40 };
+const Canvas = display.getContext('2d');
const FrameTimer = (()=>{
let prev = 1, curr = 1;
- const ctx = display.getContext('2d');
return {
update: (now) => (prev = curr, curr = now, (curr - prev) / 1000),
render: () => {
- ctx.fillStyle = "Red";
- ctx.font = "normal 12pt Arial";
- ctx.fillText(Math.round(1/((curr - prev) / 1000)) + " fps", 10, 26);
+ Canvas.fillStyle = "Red";
+ Canvas.font = "normal 12pt Arial";
+ Canvas.fillText(Math.round(1/((curr - prev) / 1000)) + " fps", 10, 26);
}
};
})();
const Controls = (()=>{
- const self = { states: { 'left': false, 'right': false, 'forward': false, 'backward': false } };
- const codes = { 37: 'left', 39: 'right', 38: 'forward', 40: 'backward' };
- const onKey = (val, e)=>{
- const state = codes[e.keyCode];
- if (typeof state === 'undefined') return;
- self.states[state] = val;
+ const self = {};
+ const onKey = (val) => ((e)=>{
+ self[e.keyCode] = val;
e.preventDefault && e.preventDefault();
e.stopPropagation && e.stopPropagation();
- };
- document.addEventListener('keydown', onKey.bind(self, true), false);
- document.addEventListener('keyup', onKey.bind(self, false), false);
+ });
+ document.addEventListener('keydown', onKey(true), false);
+ document.addEventListener('keyup', onKey(false), false);
return self;
})();
-const Player = ((x, y, direction)=>{
- const self = {x: x, y: y, direction: direction};
+const Player = (()=>{
+ const self = START_POS;
- const rotate = (angle)=>{
- self.direction = (self.direction + angle + TAU) % (TAU);
- };
+ const rotate = (angle)=>(self.direction = (self.direction + angle + TAU) % TAU);
const walk = (distance)=>{
const dx = Math.cos(self.direction) * distance;
};
self.update = (seconds)=>{
- if (Controls.states.left) rotate(-TURN_SPEED * seconds);
- if (Controls.states.right) rotate(TURN_SPEED * seconds);
- if (Controls.states.forward) walk(WALK_SPEED * seconds);
- if (Controls.states.backward) walk(-WALK_SPEED * seconds);
+ if (Controls[Keys.left]) rotate(-TURN_SPEED * seconds);
+ if (Controls[Keys.right]) rotate(TURN_SPEED * seconds);
+ if (Controls[Keys.up]) walk(WALK_SPEED * seconds);
+ if (Controls[Keys.down]) walk(-WALK_SPEED * seconds);
};
return self;
-})(15.3, -1.2, Math.PI * 0.3);
+})();
-const Map = ((size)=>{
- let grid = new Uint8Array(size*size);
+const Map = (()=>{
+ let grid = new Uint8Array(MAP_SIZE * MAP_SIZE);
return {
- get: (x, y)=>(grid[Math.floor(y) * size + Math.floor(x)] || -1),
+ get: (x, y)=>(grid[Math.floor(y) * MAP_SIZE + Math.floor(x)] || -1),
randomize: ()=>(grid = grid.map((i) => (Math.random() < 0.3 ? 1 : 0))),
};
-})(MAP_SIZE);
+})();
const Camera = (()=>{
const self = {};
let columns = [], ceiling = null, floor = null;
- const ctx = display.getContext('2d');
const resizeView = ()=>{
if (self.width != window.innerWidth || self.height != window.innerHeight) {
});
/* Generate floor and ceiling gradients */
- ceiling = ctx.createLinearGradient(0, self.height/2, 0, 0);
+ ceiling = Canvas.createLinearGradient(0, self.height/2, 0, 0);
ceiling.addColorStop(0, '#000011');
ceiling.addColorStop(1, '#000055');
- floor = ctx.createLinearGradient(0, self.height/2, 0, self.height);
+ floor = Canvas.createLinearGradient(0, self.height/2, 0, self.height);
floor.addColorStop(0, '#110000');
floor.addColorStop(1, '#550000');
}
};
self.render = ()=>{
- ctx.save();
+ Canvas.save();
const data = {};
resizeView();
- ctx.fillStyle = ceiling;
- ctx.fillRect(0, 0, self.width, self.height/2);
- ctx.fillStyle = floor;
- ctx.fillRect(0, self.height/2, self.width, self.height);
+ Canvas.fillStyle = ceiling;
+ Canvas.fillRect(0, 0, self.width, self.height/2);
+ Canvas.fillStyle = floor;
+ Canvas.fillRect(0, self.height/2, self.width, self.height);
columns.map((col, i)=>{
/* pre-calculate trig functions related to the ray to cast */
wall.top = (self.height / 2 * (1 + 1 / z)) - wall.height;
/* draw wall slice */
- ctx.fillStyle = '#999999';
- ctx.globalAlpha = 1;
- ctx.fillRect(i, wall.top, 1, wall.height);
+ Canvas.fillStyle = '#999999';
+ Canvas.globalAlpha = 1;
+ Canvas.fillRect(i, wall.top, 1, wall.height);
/* draw shadow over wall slice */
- ctx.fillStyle = '#000000';
- ctx.globalAlpha = Math.max((obj.distance) / LIGHT_RANGE, 0);
- ctx.fillRect(i, wall.top, 1, wall.height);
+ Canvas.fillStyle = '#000000';
+ Canvas.globalAlpha = Math.max((obj.distance) / LIGHT_RANGE, 0);
+ Canvas.fillRect(i, wall.top, 1, wall.height);
}
});
- ctx.restore();
+ Canvas.restore();
};
const snapToGrid = (pos, dist) =>