Skip to content

Instantly share code, notes, and snippets.

@DonBattery
Created January 30, 2020 11:51
Show Gist options
  • Save DonBattery/514e88136b56c13239b2acf036b592dd to your computer and use it in GitHub Desktop.
Save DonBattery/514e88136b56c13239b2acf036b592dd to your computer and use it in GitHub Desktop.
minimal js game engine
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="style.css">
<title>Tanx</title>
</head>
<body>
<canvas id="Game"></canvas>
<script src="script.js"></script>
</body>
</html>
"use strict";
var game;
function randRange(min, max) {
return Math.floor(Math.random() * (max - min + 1) ) + min;
};
function randInt(max) {
return randRange(0, max - 1);
};
function randomDirection() {
return ["left", "right", "up", "down"][randInt(4)];
};
function randomId() {
return Math.random().toString(36).substr(2, 5);
};
class Game {
constructor() {
this.gameElements = [];
this.canvas = document.getElementById("Game");
this.ctx = this.canvas.getContext("2d");
this.width = 800;
this.height = 600;
this.offsetX = 0;
this.offsetY = 0;
this.addElement = this.addElement.bind(this);
this.addElements = this.addElements.bind(this);
this.getElementById = this.getElementById.bind(this);
this.getElementsByType = this.getElementsByType.bind(this);
this.getChildElements = this.getChildElements.bind(this);
this.getChildElementsByType = this.getChildElementsByType.bind(this);
this.getOthersFromType = this.getOthersFromType.bind(this);
this.removeElementById = this.removeElementById.bind(this);
this.removeElementsByType = this.removeElementsByType.bind(this);
this.removeChildren = this.removeElementsByType.bind(this);
this.onKeyDown = this.onKeyDown.bind(this);
this.onKeyUp = this.onKeyUp.bind(this);
this.clear = this.clear.bind(this);
this.update = this.update.bind(this);
}
clear() {
this.ctx.canvas.width = window.innerWidth;
this.ctx.canvas.height = window.innerHeight;
let deltaW = this.ctx.canvas.width - this.width;
let deltaH = this.ctx.canvas.height - this.height;
this.offsetX = (deltaW > 0) ? Math.round(deltaW / 2) : 0;
this.offsetY = (deltaH > 0) ? Math.round(deltaH / 2) : 0;
this.ctx.fillStyle = "white";
this.ctx.fillRect(this.offsetX, this.offsetY, this.width, this.height);
}
update() {
this.clear();
this.gameElements.forEach(element => {
if (typeof element.update === 'function') {
element.update();
};
element.draw(this.ctx, this.offsetX, this.offsetY);
});
};
addElement(element) {
this.gameElements.push(element);
};
addElements(elements) {
this.gameElements = this.gameElements.concat(elements);
};
getElementById(elementId) {
return this.gameElements.find(element => element.id == elementId);
};
getElementsByType(elementType) {
return this.gameElements.filter(element => element.type == elementType);
};
getChildElements(elementId) {
return this.gameElements.filter(element => element.parentId == elementId);
};
getChildElementsByType(elementId, type) {
return this.gameElements.filter(element => (element.parentId == elementId && element.type == type));
};
getOthersFromType(elementId, type) {
return this.gameElements.filter(element => (element.id != elementId && element.type == type));
};
removeElementById(elementId) {
this.gameElements = this.gameElements.filter(element => element.id != elementId);
};
removeElementsByType(elementType) {
this.gameElements = this.gameElements.filter(element => element.type != elementType);
};
removeChildren(elementId) {
this.gameElements = this.gameElements.filter(element => element.parentId != elementId);
};
onKeyDown(event) {
this.getElementsByType("player").forEach(player => {
player.controls.forEach(control => {
if (event.key == control.key) {
control.pressed = true;
};
});
});
};
onKeyUp(event) {
this.getElementsByType("player").forEach(player => {
player.controls.forEach(control => {
if (event.key == control.key) {
control.pressed = false;
};
});
});
};
};
class Rect {
constructor(x, y, width, height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.collide = this.collide.bind(this);
this.collideMany = this.collideMany.bind(this);
};
collide(otherRect) {
return (this.x < otherRect.x + otherRect.width) && (this.x + this.width > otherRect.x) && (this.y < otherRect.y + otherRect.height) && (this.y + this.height > otherRect.y);
};
collideMany(otherRects) {
for (let index = 0; index < otherRects.length; index++) {
if (this.collide(otherRects[index])) {
return true;
};
};
return false;
};
};
class GameElement extends Rect {
constructor(parentId, id, type, x, y, width, height, color) {
super(x, y, width, height);
this.parentId = parentId;
this.id = id;
this.type = type;
this.color = color;
this.draw = this.draw.bind(this);
};
draw(ctx, offsetX, offsetY) {
ctx.fillStyle = this.color;
ctx.fillRect(this.x + offsetX, this.y + offsetY, this.width, this.height);
};
};
class Wall extends GameElement {
constructor(x, y, width, height, color) {
super("world", randomId(), "wall", x, y, width, height, color);
};
};
class Player extends GameElement {
constructor(name, color, x, y, controls) {
super("world", name, "player", x, y, 150, 40, color);
this.controls = controls;
this.score = 0;
this.draw = this.draw.bind(this);
this.update = this.update.bind(this);
};
draw(ctx, offsetX, offsetY) {
ctx.font = "18px Arial";
ctx.fillText(`${this.id} score:${this.score}`, offsetX + this.x, offsetY + this.y);
};
update() {
let tanks = game.getChildElementsByType(this.id, "tank");
if (tanks.length != 1) { return };
let tank = tanks[0];
tank.moving = false;
this.controls.forEach(control => {
if (control.pressed) {
tank.moving = true;
tank.direction = control.direction;
};
});
// console.log(`Player controls ${JSON.stringify(this.controls)} Tank: ${JSON.stringify(tank)}`);
};
};
class MovingGameElement extends GameElement {
constructor(parentId, id, type, x, y, width, height, color, direction, speed) {
super(parentId, id, type, x, y, width, height, color);
this.moving = false;
this.direction = direction;
this.speed = speed;
this.move = this.move.bind(this);
};
move(colliders) {
if (!this.moving) { return };
let collided = false;
let nextRect = new Rect(this.x, this.y, this.width, this.height);
for (let index = 0; index < this.speed && !collided; index++) {
switch(this.direction) {
case "up":
nextRect.y--;
break;
case "down":
nextRect.y++;
break;
case "left":
nextRect.x--;
break;
case "right":
nextRect.x++;
break;
};
if (nextRect.collideMany(colliders)) {
collided = true;
} else {
this.x = nextRect.x;
this.y = nextRect.y;
}
};
};
};
class Tank extends MovingGameElement {
constructor(parentId, id, x, y, color) {
super(parentId, id, "tank", x, y, 30, 30, color, randomDirection(), 8);
this.update = this.update.bind(this);
}
update() {
let colliders = [];
let walls = game.getElementsByType("wall");
let otherTanks = game.getOthersFromType(this.id, "tank");
colliders = colliders.concat(walls, otherTanks);
this.move(colliders);
};
};
function setupGame() {
game = new Game();
let player1 = new Player("Miki", "red", 0, 0, [
{
key : "ArrowUp",
direction : "up",
pressed : false,
},
{
key : "ArrowDown",
direction : "down",
pressed : false,
},
{
key : "ArrowLeft",
direction : "left",
pressed : false,
},
{
key : "ArrowRight",
direction : "right",
pressed : false,
},
]);
let player2 = new Player("Sanyi", "black", 650, 0, [
{
key : "w",
direction : "up",
pressed : false,
},
{
key : "s",
direction : "down",
pressed : false,
},
{
key : "a",
direction : "left",
pressed : false,
},
{
key : "d",
direction : "right",
pressed : false,
},
]);
let tank1 = new Tank("Miki", randomId(), 200, 2, "red");
let tank2 = new Tank("Sanyi", randomId(), 280, 2, "black");
game.addElements([player1, player2, tank1, tank2]);
for (let index = 0; index < 17; index++) {
let wall = new Wall(randRange(30, 700), randRange(30, 600), randRange(10, 120), randRange(10, 120), "yellow");
game.addElement(wall);
};
document.addEventListener("keydown", game.onKeyDown, false);
document.addEventListener("keyup", game.onKeyUp, false);
let frames = setInterval(game.update, 25);
};
window.addEventListener("load", setupGame);
html, body {
display: block;
box-sizing: border-box;
margin: 0px;
border: 0px;
padding: 0px;
height: 100%;
width: 100%;
overflow: hidden;
background-color: blue;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment