Skip to content

Instantly share code, notes, and snippets.

@lewdev
Last active November 23, 2023 02:33
Show Gist options
  • Select an option

  • Save lewdev/a166c32dad5353d4114c3625688f0b85 to your computer and use it in GitHub Desktop.

Select an option

Save lewdev/a166c32dad5353d4114c3625688f0b85 to your computer and use it in GitHub Desktop.
πŸš— Tank Movement in JavaScript Canvas
<style>html,body{background:#222;display:flex;justify-content:center;align-items:center;height:100%;margin:0}#c{background:white;max-height:100%;max-width:100%}</style><canvas id=c width=1080 height=1080></canvas><script>x=c.getContext`2d`,lfr=1,S=Math.sin,C=Math.cos,T=Math.tan,R=(r,g,b,a=1)=>`rgba(${r|0},${g|0},${b|0},${a})`,t=f=nfm=0;
const vec2 = (x,y=x)=>new Vec2(x, y);
class Vec2 {
constructor(x = 0, y = 0) { this.x = x; this.y = y; }
copy() { return vec2(this.x, this.y); }
scale(s) { return vec2(this.x * s, this.y * s); }
add(v) { return vec2(this.x + v.x, this.y + v.y); }
subtract(v) { return vec2(this.x - v.x, this.y - v.y); }
move(a, l) { return vec2(this.x + l * Math.sin(a), this.y - l * Math.cos(a)); }
setAngle(a=0, length=1) { this.x = length*Math.sin(a); this.y = length*Math.cos(a); return this; }
}
class Speed {
constructor(maxSpeed = 1, direction = 0) {
this.maxSpeed = maxSpeed;
this.speed = direction * maxSpeed;
}
move(direction) {
this.speed = direction * this.maxSpeed;
}
copy() {
const { maxSpeed, speed } = this;
const direction = speed < 0 ? -1 : speed > 0 ? 1 : 0;
return new Speed(maxSpeed, direction);
}
}
class Timer {
constructor(timeLeft) { this.time = timeLeft == undefined ? undefined : time + timeLeft; this.setTime = timeLeft; }
set(timeLeft=0) { this.time = time + timeLeft; this.setTime = timeLeft; }
unset() { this.time = undefined; }
elapsed() { return time > this.time; }
getPercent() { return this.isSet()? percent(this.time - time, this.setTime, 0) : 0; }
}
const TILE_SIZE = 100;
const HALF_TILE = TILE_SIZE / 2;
let globalScale = 1;
let time = tDiff = 0;
let SCREEN_SIZE = vec2(1080);
let cameraPos = null;
let objList = [];
const TANK_COLOR = "blue";
const TANK_SIZE = vec2(80, 100);
const TANK_SPEED = new Speed(300); //per second
const TANK_ROTATE_SPEED = new Speed(3); //per second
const TANK_FIRING_RATE = 1; // per second
const BULLET_SIZE = vec2(10, 10);
const BULLET_SPEED = new Speed(500, 1);
const MAP_DIMENSIONS = vec2(50, 50);
const MAP_SIZE = MAP_DIMENSIONS.scale(TILE_SIZE);
const rect = (pos, size, direction, center, color, s = 1) => {
const scale = s * globalScale;
const relativePos = pos.scale(scale).subtract(cameraPos.scale(scale)).add(SCREEN_SIZE.scale(.5))
const scaledSize = size.scale(scale);
const halfSize = scaledSize.scale(.5);
if (relativePos.x < -scaledSize.x || relativePos.y < -scaledSize.y
|| relativePos.x > SCREEN_SIZE.x + scaledSize.x || relativePos.y > SCREEN_SIZE.y + scaledSize.y) return;
x.fillStyle = color;
x.save();
x.translate(relativePos.x, relativePos.y);
x.rotate(direction);
x.fillRect(-halfSize.x, -halfSize.y, scaledSize.x, scaledSize.y);
x.restore();
};
class Object {
constructor(pos, size, angle = 0, center, color = "gray", moveSpeed, rotateSpeed) {
this.pos = pos;
this.size = size;
this.angle = angle;
this.center = center || size.scale(.5);
this.color = color;
this.moveSpeed = moveSpeed || new Speed;
this.rotateSpeed = rotateSpeed || new Speed;
}
move(direction) { this.moveSpeed.move(direction); }
rotate(direction) { this.rotateSpeed.move(direction); }
update() {
if (this.moveSpeed.speed) this.pos = this.pos.move(this.angle, this.moveSpeed.speed * tDiff);
if (this.rotateSpeed.speed) this.angle = (this.angle + this.rotateSpeed.speed * tDiff);
}
render() {
const { pos, size, angle, center, color } = this;
rect(pos, size, angle, center, color);
}
}
class Bullet extends Object {
constructor(pos, angle) {
super(pos, BULLET_SIZE, angle, 0, "red", BULLET_SPEED.copy());
}
update() {
super.update();
//handle collision
const { pos } = this;
this.delete = pos.x < -HALF_TILE || pos.y < -HALF_TILE || pos.y > MAP_SIZE.y || pos.x > MAP_SIZE.x;
}
}
class Tank extends Object {
constructor(pos, angle) {
super(pos, TANK_SIZE, angle, null, TANK_COLOR, TANK_SPEED.copy(), TANK_ROTATE_SPEED.copy());
this.lastFired = new Timer(TANK_FIRING_RATE);
this.hp = 5;
}
update() {
super.update();
if (this.isFiring && this.lastFired.elapsed()) {
this.lastFired.set(TANK_FIRING_RATE);
objList.push(new Bullet(this.pos.move(this.angle, TANK_SIZE.y / 2), this.angle));
this.isFiring = false;
}
}
}
class EnemyTank extends Tank {
constructor(pos) {
super(pos, 0);
this.color = "orange";
}
}
const drawMap = () => {
for (i = MAP_DIMENSIONS.x; i--;) for (j = MAP_DIMENSIONS.y; j--;)
rect(vec2(i, j).scale(TILE_SIZE), vec2(TILE_SIZE * .9), 0, 0, "#AFA")
};
const player = new Tank(vec2(10, 10).scale(TILE_SIZE));
objList.push(player);
for (i = 10; i--;) objList.push(new EnemyTank(vec2(i * 3 + 3, 5).scale(TILE_SIZE)));
u=r=d=l=X=Y=0;
onkeydown=onkeyup=e=> {
this[`lurd${"*".repeat(24)}l**r************l*d***u**u`[e.which-37]]=e.type[5]
if (e.code === "Space" && e.type[5]) player.isFiring = true;
};
window.addEventListener("wheel", e => {
const direction = (e.detail < 0) ? 1 : (e.wheelDelta > 0) ? 1 : -1;
globalScale = Math.min(3, Math.max(0.2, globalScale + direction * .2));
});
z=t=>{ c.width|=0 //loop and clear canvas
tDiff = t - time;
time = t;
// player controls
player.move(u ? 1 : d ? -1 : 0);
player.rotate(r ? 1 : l ? -1 : 0);
// update
objList = objList.filter(o => !o.delete);
cameraPos = player.pos.copy();
// render
drawMap();
for (o of objList) o.update(), o.render();
};loop=fTime=>{requestAnimationFrame(loop);if(lfr&&fTime<nfm-2)return;nfm=Math.max(nfm+1e3/60,fTime);t=f/60;if(t*60|0!=f++)t+=1e-6;z(t)};
onload = _ => loop()</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment