Last active
November 13, 2018 23:04
-
-
Save Cacodaimon/4432192 to your computer and use it in GitHub Desktop.
JavaScript Canvas textured raycaster, used @ cacodaemon.de.
This file contains hidden or 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
/* | |
* Simple JavaScript game manager. | |
* | |
* By Guido Krömer <[email protected]> 2013 | |
* | |
*/ | |
function GameManager () { | |
var canvas = null; | |
var ctx = null; | |
var delta = 0; | |
var lastTimeStamp = null; | |
var idCounter = 0; | |
var freePos = -1; | |
var gameObjects = new Array(); | |
var tmpFps = 0; | |
var lastSecond = -1; | |
this.width = 0; | |
this.height = 0; | |
this.init = function (canvas) { | |
lastTimeStamp = new Date().getTime(); | |
canvas = canvas; | |
ctx = canvas.getContext('2d'); | |
this.width = canvas.width; | |
this.height = canvas.height; | |
window.requestAnimFrame = (function(){ | |
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) { | |
window.setTimeout(callback, 1000 / 120); | |
}; | |
})(); | |
(function animloop(){ | |
requestAnimFrame(animloop); | |
timerStart(); | |
update(); | |
draw(); | |
timerEnd(); | |
})(); | |
} | |
this.addGameObject = function(gameObject) { | |
gameObject.init(this); | |
if (freePos != -1) { | |
gameObjects[this.freePos] = gameObject; | |
freePos = -1; | |
} | |
else { | |
gameObjects.push(gameObject); | |
} | |
}; | |
this.addGameObjects = function(gameObjects) { | |
for (var i = gameObjects.length - 1; i >= 0; i--) { | |
this.addGameObject(gameObjects[i]); | |
} | |
}; | |
this.removeGameObject = function(gameObject) { | |
for (var i = gameObjects.length - 1; i >= 0; i--) { | |
if (gameObjects[i] == gameObject) { | |
gameObjects.splice(i, 1); | |
freePos = i; | |
return; | |
} | |
} | |
}; | |
this.forEachGameObject = function(callBack) { | |
for (var i = gameObjects.length - 1; i >= 0; i--) { | |
callBack(gameObject[i]); | |
} | |
}; | |
this.getCtx = function() { | |
return ctx; | |
} | |
var update = function() { | |
for (var i = gameObjects.length - 1; i >= 0; i--) { | |
gameObjects[i].update(delta); | |
} | |
}; | |
var draw = function() { | |
for (var i = gameObjects.length - 1; i >= 0; i--) { | |
gameObjects[i].draw(ctx); | |
} | |
}; | |
var timerStart = function() { | |
var date = new Date(); | |
delta = date.getTime() - this.lastTimeStamp; | |
delta *= 0.01; | |
var seconds = new Date().getSeconds(); | |
}; | |
var timerEnd = function() { | |
this.lastTimeStamp = new Date().getTime(); | |
}; | |
} |
This file contains hidden or 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
/* | |
* Abstract game object class. | |
* | |
* By Guido Krömer <[email protected]> 2013 | |
* | |
*/ | |
function GameObject () { | |
this.gameManager = null; | |
this.init = function (gameManager) { | |
this.gameManager = gameManager; | |
}; | |
this.update = function (delta) { }; | |
this.draw = function (ctx) { }; | |
} |
This file contains hidden or 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
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<title>Textured Raycaster Demo from cacodaemon.de</title> | |
<style type="text/css"> | |
* { | |
padding: 0; | |
margin: 0; | |
image-rendering:optimizeSpeed; /* Legal fallback */ | |
image-rendering:-moz-crisp-edges; /* Firefox */ | |
image-rendering:-o-crisp-edges; /* Opera */ | |
image-rendering:-webkit-optimize-contrast; /* Chrome (and eventually Safari) */ | |
image-rendering:crisp-edges; /* CSS3 Proposed */ | |
image-rendering: optimize-contrast; /* Possible future browsers. */ | |
-ms-interpolation-mode:bicubic; /* IE8+ */ | |
-ms-interpolation-mode: nearest-neighbor; | |
} | |
#GameCanvas { | |
transform-origin: 0% 0%; | |
-ms-transform-origin: 0% 0%; | |
-webkit-transform-origin: 0% 0%; | |
-o-transform-origin: 0% 0%; | |
-moz-transform-origin: 0% 0%; | |
transform: scale(3, 3); | |
-ms-transform: scale(3, 3); | |
-webkit-transform: scale(3, 3); | |
-o-transform: scale(3, 3); | |
-moz-transform: scale(3, 3); | |
} | |
</style> | |
<script type="text/javascript" src="GameManager.js"></script> | |
<script type="text/javascript" src="GameObject.js"></script> | |
<script type="text/javascript" src="Keyboard.js"></script> | |
<script type="text/javascript" src="Raycaster.js"></script> | |
</head> | |
<body> | |
<canvas id="GameCanvas" /> | |
</body> | |
<script> | |
var scale = 3 | |
var gameCanvas = document.getElementById("GameCanvas"); | |
gameCanvas.width = window.innerWidth / scale; | |
gameCanvas.height = window.innerHeight / scale; | |
var gameManager = new GameManager(); | |
gameManager.init(gameCanvas); | |
gameManager.addGameObject(new Raycaster()); | |
</script> | |
</html> |
This file contains hidden or 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
/* | |
* Simple JavaScipt keyboard helper. | |
* Based upon http://nokarma.org/2011/02/27/javascript-game-development-keyboard-input/index.html | |
* | |
*/ | |
var Key = { | |
pressed: [], | |
LEFT: 37, | |
UP: 38, | |
RIGHT: 39, | |
DOWN: 40, | |
ENTER: 13, | |
SPACE: 32, | |
PAGE_UP: 33, | |
PAGE_DOWN: 34, | |
W: 87, | |
A: 65, | |
S: 83, | |
D: 68, | |
isDown: function(keyCode) { | |
return this.pressed[keyCode]; | |
}, | |
reset: function(keyCode) { | |
this.pressed[keyCode] = false; | |
}, | |
onKeydown: function(event) { | |
this.pressed[event.keyCode] = true; | |
}, | |
onKeyup: function(event) { | |
delete this.pressed[event.keyCode]; | |
} | |
}; | |
window.addEventListener('keyup', function(event) { | |
Key.onKeyup(event); | |
}, false); | |
window.addEventListener('keydown', function(event) { | |
Key.onKeydown(event); | |
}, false); |
This file contains hidden or 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
/* | |
* Simple JavaScript Canvas raycaster (Textured). | |
* | |
* By Guido Krömer <[email protected]> | |
* | |
*/ | |
function Vector2() { | |
this.x; | |
this.y; | |
}; | |
var TILE = 64; | |
function Player(x, y, raycaster) { | |
this.x = x; | |
this.y = y; | |
this.angle = Math.PI / 3; | |
this.moveableForward = true; | |
this.moveableBackward = true; | |
this.raycaster = raycaster; | |
this.update = function(delta) { | |
if (Key.isDown(Key.UP) && this.moveableForward) { | |
this.x += Math.cos(this.angle) * (TILE / 2) * delta; | |
this.y += Math.sin(this.angle) * (TILE / 2) * delta; | |
} | |
if (Key.isDown(Key.DOWN) && this.moveableBackward) { | |
this.x -= Math.cos(this.angle) * (TILE / 2) * delta; | |
this.y -= Math.sin(this.angle) * (TILE / 2) * delta; | |
} | |
if (Key.isDown(Key.LEFT)) { | |
this.angle -= 0.05; | |
} | |
if (Key.isDown(Key.RIGHT)) { | |
this.angle += 0.05; | |
} | |
if (this.angle < 0) { | |
this.angle += Math.PI * 2; | |
} | |
this.angle %= Math.PI * 2; | |
} | |
}; | |
Player.prototype = new Vector2(); | |
function Raycaster() { | |
this.map = null; | |
var COLORS = ['', 'rgb(255, 0, 0)', 'rgb(0, 255, 0)', 'rgb(0, 0, 255)', 'rgb(255, 255, 0)', 'rgb(0, 255, 255)', 'rgb(255, 0, 255)', 'rgb(255, 255, 255)']; | |
var INFINITY = 1 / 0; | |
var SCR_W = 0; | |
var SCR_H = 0; | |
var SCR_W_HALF = 0; | |
var SCR_H_HALF = 0; | |
var WALL_H_FACTOR = 90; | |
var WALL_H = 0; | |
var RAY_ANGLE = 0; | |
var PI_TWO = Math.PI * 2; | |
var PI_HALF = Math.PI / 2; | |
var VOF = 60 * (Math.PI / 180); | |
var VOF_HALF = VOF / 2; | |
var TILE_QUATER = TILE / 2; | |
this.preciseDistance = true | |
this.depthShading = true | |
this.player = new Player(TILE * 4.5, TILE * 2, this); | |
var textures = new Array(); | |
this.init = function (gameManager) { | |
this.gameManager = gameManager; | |
SCR_W_HALF = (SCR_W = this.gameManager.width) / 2; | |
SCR_H_HALF = (SCR_H = this.gameManager.height) / 2; | |
RAY_ANGLE = VOF / SCR_W; | |
WALL_H = SCR_H * WALL_H_FACTOR; | |
this.loadMap(); | |
this.loadTextures(); | |
var ctx = gameManager.getCtx(); | |
ctx.webkitImageSmoothingEnabled = false; | |
ctx.mozImageSmoothingEnabled = false; | |
ctx.operaImageSmoothingEnabled = false; | |
} | |
this.draw = function(ctx) { | |
this.drawBackgound(ctx); | |
var lineElement = { | |
y: 0, | |
x: 0, | |
texture: null, | |
north: false, | |
dist: 0, | |
part: 0 | |
}; | |
var i = 0; | |
for (var rayAngle = -VOF_HALF; rayAngle < VOF_HALF; rayAngle += RAY_ANGLE) { | |
var dx = this.player.x + Math.cos(this.player.angle + rayAngle) * 100; | |
var dy = this.player.y + Math.sin(this.player.angle + rayAngle) * 100; | |
this.getLine(this.player.x, this.player.y, dx, dy, lineElement); | |
var vX = this.player.x - lineElement.x; | |
var vY = this.player.y - lineElement.y; | |
lineElement.dist = Math.sqrt(vX * vX + vY * vY) * Math.cos(rayAngle); | |
var wallFactor = (SCR_H_HALF / lineElement.dist * TILE_QUATER) * 2 | |
var texture = lineElement.texture; | |
if (texture) | |
ctx.drawImage(texture, Math.floor(lineElement.part * (texture.width / TILE)), 0, 1, texture.height, i, SCR_H_HALF - wallFactor, 1, wallFactor * 2) | |
ctx.globalAlpha = lineElement.dist / 1000 * (lineElement.north ? 1 : 1.5); | |
ctx.fillStyle = "black"; | |
ctx.beginPath(); | |
ctx.moveTo(i, SCR_H_HALF - wallFactor); | |
ctx.lineTo(i, SCR_H_HALF + wallFactor); | |
ctx.closePath(); | |
ctx.stroke(); | |
ctx.globalAlpha = 1; | |
if (i == SCR_W_HALF) { | |
this.player.moveableForward = lineElement.dist > 10; | |
} | |
i++; | |
} | |
this.drawMap(ctx); | |
} | |
this.drawBackgound = function(ctx) { | |
var grd = ctx.createLinearGradient(0,SCR_H_HALF,0,0); | |
grd.addColorStop(0,"black"); | |
grd.addColorStop(1,"grey"); | |
ctx.fillStyle = grd; | |
ctx.fillRect(0, 0, SCR_W, SCR_H_HALF); | |
grd = ctx.createLinearGradient(0,SCR_H_HALF,0,SCR_H); | |
grd.addColorStop(0,"black"); | |
grd.addColorStop(1,"grey"); | |
ctx.fillStyle = grd; | |
ctx.fillRect(0, SCR_H_HALF, SCR_W, SCR_H); | |
} | |
this.getLine = function(x1, y1, x2, y2, lineElement) { | |
var dx = Math.abs(x2 - x1); | |
var dy = Math.abs(y2 - y1); | |
var sx = (x1 < x2) ? 1 : -1; | |
var sy = (y1 < y2) ? 1 : -1; | |
var err = dx - dy; | |
var e2; | |
var perviousTileX = 0; | |
var perviousTileY = 0; | |
while (!((x1 == x2) && (y1 == y2))) { | |
e2 = err << 1; | |
if (e2 > -dy) { | |
err -= dy; | |
x1 += sx; | |
} | |
else if (e2 < dx) { | |
err += dx; | |
y1 += sy; | |
} | |
var mapX = Math.floor(x1 / TILE); | |
var mapY = Math.floor(y1 / TILE); | |
if (this.map[mapY][mapX]) { | |
lineElement.y = y1; | |
lineElement.x = x1; | |
lineElement.texture = textures[this.map[mapY][mapX]]; | |
lineElement.north = perviousTileX == mapX; | |
lineElement.part = lineElement.north ? x1 - (mapX * TILE) : y1 - (mapY * TILE) | |
return; | |
} | |
perviousTileX = mapX; | |
perviousTileY = mapY; | |
} | |
} | |
this.update = function(delta) { | |
this.player.update(delta); | |
} | |
this.loadMap = function() { | |
this.map = [ | |
[ 5, 5, 5, 5, 5, 5, 5, 4, 0, 0, 0, 4, 5, 6, 5, 6, 5, 6, 5, 6], | |
[ 6, 0, 0, 0, 0, 0, 0, 4, 4, 4, 4, 5, 0, 0, 0, 0, 0, 0, 0, 4], | |
[ 5, 0, 0, 0, 0, 0, 0, 0, 5, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 5], | |
[ 6, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 7, 8, 0, 6], | |
[ 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 7, 0, 5], | |
[ 6, 0, 0,10, 0,10, 0, 0, 0, 2, 1, 2, 0, 0, 0, 0, 7, 8, 0, 6], | |
[ 5, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 8, 7, 0, 5], | |
[ 6, 0, 0,10, 0,10, 0, 0, 0, 2, 1, 2, 0, 0, 0, 0, 7, 8, 0, 6], | |
[ 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5], | |
[ 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6], | |
[ 5, 0, 0,11,11,11,11,11,11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5], | |
[ 6, 0, 0,11, 0, 0, 0, 0,11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6], | |
[ 5, 0, 0,11,11,11,11,11,11, 0, 0, 0, 3, 3, 3, 3, 3, 3, 0, 5], | |
[ 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6], | |
[ 5, 7, 8, 9, 8, 7, 7, 8, 9, 8, 7, 8, 3, 3, 3, 3, 3, 3, 8, 7] | |
]; | |
} | |
this.loadTextures = function() { | |
var maxTexture = 0; | |
for (var y = 0; y < this.map.length; y++) { | |
for (var x = 0; x < this.map[y].length; x++) { | |
maxTexture = Math.max(maxTexture, this.map[y][x]); | |
} | |
} | |
for (var i = 0; i <= maxTexture; i++) { | |
var texture = new Image(); | |
texture.src = 'Textures/' + i + '.png'; | |
textures.push(texture) | |
} | |
} | |
this.drawMap = function (ctx) { | |
ctx.fillStyle = 'BLACK' | |
ctx.fillRect(0, 0, this.map[0].length * 2, this.map.length * 2); | |
for (var y = 0; y < this.map.length; y++) { | |
for (var x = 0; x < this.map[y].length; x++) { | |
if (!this.map[y][x]) { | |
continue; | |
} | |
ctx.fillStyle = COLORS[this.map[y][x]]; | |
ctx.fillRect(x*2, y*2, 2, 2); | |
} | |
} | |
ctx.fillStyle = 'WHITE' | |
ctx.fillRect((this.player.x / TILE) * 2, (this.player.y / TILE) * 2, 4 ,4) | |
} | |
}; | |
Raycaster.prototype = new GameObject(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment