a low friction slippery canvas space game component.
started by porting the guts of skidracer's How to make Asteroids in an hour or so from blitzbasic to js.
//.controls | |
b Controls | |
ul | |
li Forward/back thrusters: Up/Down or W/S | |
li Rotation thrusters: Left/Right arrows | |
li Lateral thrusters: A/D |
a low friction slippery canvas space game component.
started by porting the guts of skidracer's How to make Asteroids in an hour or so from blitzbasic to js.
/** event listeners */ | |
window.addEventListener("load", function() { | |
Sim.init(); | |
Sim.update(); | |
}, false); | |
window.addEventListener("keyup", function(event) { | |
Key.onKeyup(event); | |
}, false); | |
window.addEventListener("keydown", function(event) { | |
Key.onKeydown(event); | |
//console.log(event.keyCode) | |
}, false); | |
/** utilities */ | |
var Util = | |
{ | |
degToRad: function(degrees) | |
{ | |
return degrees * (pi/180); | |
}, | |
radToDeg: function(radians) | |
{ | |
return radians * (180/pi); | |
} | |
} | |
// cache pi for speed | |
const pi = Math.PI; | |
/** keyboard events */ | |
var Key = | |
{ | |
// index of recognized keys | |
BACKSPACE: 8, | |
LEFT: 37, | |
UP: 38, | |
RIGHT: 39, | |
DOWN: 40, | |
A: 65, | |
D: 68, | |
W: 87, | |
S: 83, | |
// cache of keys currently pressed | |
pressed: {}, | |
// methods for tracking key state | |
isDown: function(keyCode) | |
{ | |
return this.pressed[keyCode]; | |
}, | |
onKeydown: function(event) | |
{ | |
this.pressed[event.keyCode] = true; | |
}, | |
onKeyup: function(event) | |
{ | |
delete this.pressed[event.keyCode]; | |
} | |
} | |
/** canvas manipulation */ | |
var Canvas = {} | |
Canvas.make = function(target, id) | |
{ | |
let canvas = document.createElement("canvas"), | |
base = target; | |
canvas.id = id; | |
canvas.width = 320; | |
canvas.height = 200; | |
base.appendChild(canvas); | |
} | |
/** the simulation */ | |
var Sim = { | |
}; | |
Sim.init = function() | |
{ | |
// start the clock | |
this.time = 0; | |
// set up canvas | |
Canvas.make(document.getElementsByTagName("body")[0], "display"); | |
this.canvas = document.getElementById("display"); | |
this.context = this.canvas.getContext("2d"); | |
// experiment for later: do stuff with clicks | |
this.canvas.addEventListener("click", function() { | |
let x = event.pageX - this.offsetLeft; | |
let y = event.pageY - this.offsetTop; | |
console.log(x, y) | |
//show where clicked, needs to go into a layer queue | |
//Sim.context.fillRect(x-1, y-1, 3, 3); | |
}, false); | |
// declare entities to simulate | |
this.ship = new Ship(this.canvas); | |
} | |
Sim.update = function() | |
{ | |
// start the next frame | |
requestAnimationFrame(this.update.bind(this)); | |
// update clock | |
let now = new Date().getTime(); | |
this.delta = now - (this.time || now); | |
this.time = now; | |
// update entities | |
//this.ship.update(this.delta); | |
// render scene | |
this.render(); | |
} | |
Sim.render = function() | |
{ | |
// clear frame | |
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); | |
this.context.beginPath() | |
// add some debug text | |
let debug = /*"T " + this.time + " Δ " + */(1000 / this.delta).toFixed(0) + "fps"; | |
this.context.font = "12px monospace"; | |
this.context.fillText(debug, 10, 20); | |
// render entities | |
//this.ship.draw(this.context); | |
} | |
/** ship actor */ | |
function Ship(domain) | |
{ | |
this.domain = domain; | |
this.x = domain.width / 2; | |
this.y = domain.height / 2; | |
this.xSpeed = 0; | |
this.ySpeed = 0; | |
this.TurnSpeed = 0; | |
this.Direction = pi * 1.5; | |
} | |
Ship.prototype.update = function(increment) | |
{ | |
// physics variables | |
var delta = increment / 1000, | |
friction = 0.000001, | |
thrustMain = 1.1, | |
thrustLateral = 0.7, | |
maxSpeed = 4, | |
maxTurn = 0.08; | |
// movement controls | |
if (Key.isDown(Key.UP) || Key.isDown(Key.W)) | |
{ | |
this.xSpeed += Math.cos(this.Direction) * (thrustMain * delta); | |
this.ySpeed += Math.sin(this.Direction) * (thrustMain * delta); | |
} | |
if (Key.isDown(Key.DOWN) || Key.isDown(Key.S)) | |
{ | |
this.xSpeed -= Math.cos(this.Direction) * (thrustLateral * delta); | |
this.ySpeed -= Math.sin(this.Direction) * (thrustLateral * delta); | |
} | |
if (Key.isDown(Key.A)) | |
{ | |
this.xSpeed += Math.cos(this.Direction - (pi / 2)) * (thrustLateral * delta); | |
this.ySpeed += Math.sin(this.Direction - (pi / 2)) * (thrustLateral * delta); | |
} | |
if (Key.isDown(Key.D)) | |
{ | |
this.xSpeed -= Math.cos(this.Direction - (pi / 2)) * (thrustLateral * delta); | |
this.ySpeed -= Math.sin(this.Direction - (pi / 2)) * (thrustLateral * delta); | |
} | |
// calculate length of the speed vector using pythagoras | |
var SpeedVectorLength = Math.sqrt((this.xSpeed * this.xSpeed) + (this.ySpeed * this.ySpeed)); | |
// if moving, decrease speed with friction | |
if (SpeedVectorLength > 0) | |
{ | |
this.xSpeed -= (this.xSpeed / SpeedVectorLength) * friction; | |
this.ySpeed -= (this.ySpeed / SpeedVectorLength) * friction; | |
} | |
// if speed is faster than max speed, decrease speed | |
if (SpeedVectorLength > maxSpeed) | |
{ | |
this.xSpeed += (this.xSpeed / SpeedVectorLength) * (maxSpeed - SpeedVectorLength); | |
this.ySpeed += (this.ySpeed / SpeedVectorLength) * (maxSpeed - SpeedVectorLength); | |
} | |
this.x += this.xSpeed; | |
this.y += this.ySpeed; | |
// Rotation Movement (Keys Left, Right) | |
// controls | |
if (Key.isDown(Key.LEFT)) { | |
let thrust = maxTurn + this.TurnSpeed; | |
this.TurnSpeed -= thrust * delta; | |
} | |
if (Key.isDown(Key.RIGHT)) { | |
let thrust = maxTurn - this.TurnSpeed; | |
this.TurnSpeed += thrust * delta; | |
} | |
// limit turn speed | |
if (this.TurnSpeed > maxTurn) this.TurnSpeed = maxTurn; | |
if (this.TurnSpeed < -maxTurn) this.TurnSpeed = -maxTurn; | |
this.Direction += this.TurnSpeed; | |
// bound maximum direction to one rotation | |
if (this.Direction > pi * 2) { this.Direction -= pi * 2; } | |
if (this.Direction < 0) { this.Direction += pi * 2; } | |
// apply friction to rotation | |
if (this.TurnSpeed > friction) this.TurnSpeed -= friction; | |
if (this.TurnSpeed < -friction) this.TurnSpeed += friction; | |
// if friction is greater than speed, stop | |
if (this.TurnSpeed < friction && this.TurnSpeed > -friction) this.TurnSpeed = 0; | |
// reset ship to other side of canvas if it leaves domain, asteroids style | |
if (this.x > this.domain.width) this.x = 0; | |
if (this.x < 0) this.x = this.domain.width; | |
if (this.y > this.domain.height) this.y = 0; | |
if (this.y < 0) this.y = this.domain.height; | |
/* | |
if (this.x > this.domain.width) this.x = this.domain.width; | |
if (this.x < 0) this.x = 0; | |
if (this.y > this.domain.height) this.y = this.domain.height; | |
if (this.y < 0) this.y = 0; | |
*/ | |
} | |
Ship.prototype.draw = function(context) | |
{ | |
let shipAngle = 0.8, | |
shipSize = 8; | |
context.lineWidth = 2; | |
context.arc(this.x, this.y, shipSize, (this.Direction-pi-shipAngle), (this.Direction-pi+shipAngle), false); | |
context.arc(this.x, this.y, shipSize, this.Direction, this.Direction, false); | |
context.arc(this.x, this.y, shipSize, (this.Direction-pi-shipAngle), (this.Direction-pi+shipAngle), false); | |
//context.fillRect(this.x - 2, this.y - 2, 4, 4); | |
//context.arc(this.x, this.y, 4, 0, 2 * pi, false); | |
//context.fillText("X"+this.x.toFixed(0) +", Y"+ this.y.toFixed(0), this.x+16, this.y+4); | |
//context.fillText(Util.radToDeg(this.Direction).toFixed(0), this.x-10, this.y+20); | |
//context.fillText(Util.radToDeg(this.Direction).toFixed(0), 10, 40); | |
context.fill(); | |
context.stroke(); | |
context.closePath(); | |
} |
body { | |
background: silver; | |
margin: 0; | |
} | |
canvas { | |
background: white; | |
} | |
.controls { | |
position: absolute; | |
bottom: 0; | |
font: 10pt monospace; | |
opacity: 0.5; | |
ul, li { | |
margin: 0; | |
} | |
} |