A lightweight JavaScript game engine
git clone git://soophie.de/game-engine
1
import { Shape } from './game.js';3
export function collideRect(a, b) {4
return a.x < b.x + b.shape.width && a.x + a.shape.width > b.x5
&& a.y < b.y + b.shape.height && a.y + a.shape.height > b.y;6
}8
export function collideCircle(a, b) {9
const dx = Math.abs(a.x - b.x);10
const dy = Math.abs(a.y - b.y);11
return Math.sqrt(dx ** 2 + dy ** 2) < a.shape.radius + b.shape.radius;12
}14
export function collideRectAndCircle(a, b) {15
const [rect, circle] = a instanceof Shape.Rect ? [a, b] : [b, a];16
const dx = Math.abs(circle.x - rect.x);17
const dy = Math.abs(circle.y - rect.y);18
if(dx > rect.shape.width / 2 + circle.radius) {19
return false;20
}21
if(dy > rect.shape.height / 2 + circle.radius) {22
return false;23
}24
if(dx < rect.shape.width / 2) {25
return true;26
}27
if(dy < rect.shape.height / 2) {28
return true;29
}30
return (dx - rect.shape.width / 2) ** 2 + (dy - rect.shape.height / 2) ** 2 < circle.radius ** 2;31
}31
\ No newline at end of file
1
import { scaleCanvas } from './canvas.js';2
import * as Collision from './collision.js';4
export class Game {5
constructor(width, height, { fps } = {}) {
15
requestAnimationFrame(this.loop.bind(this));16
this.update();17
this.g.save();17
this.render(this.g),18
this.render(this.g);19
this.g.restore();20
}21
loop() {
26
this.then = this.now - (this.elapsed % this.fps);27
this.update();28
this.g.save();28
this.render(this.g),29
this.render(this.g);30
this.g.restore();31
}32
}
54
g.restore();55
}56
}57
collide() {58
for(const i of this.items) {59
i.items = [];60
i.collided = false;61
}62
const duplicate = [];63
for(const a of this.items) {64
for(const b of this.items) {65
if(!duplicate.includes(b)) {66
if(a !== b) {67
let collided = false;68
if(a.shape instanceof Shape.Rect && b.shape instanceof Shape.Rect) {69
collided = Collision.collideRect(a, b);70
}71
else if(a.shape instanceof Shape.Circle && b.shape instanceof Shape.Circle) {72
collided = Collision.collideCircle(a, b);73
}74
else {75
collided = Collision.collideRectAndCircle(a, b);76
}77
if(!a.collided) {78
a.collided = collided;79
}80
if(!b.collided) {81
b.collided = collided;82
}83
if(collided) {84
if(!a.items.includes(b)) {85
a.items.push(b);86
}87
if(!b.items.includes(a)) {88
b.items.push(a);89
}90
}91
}92
}93
}94
duplicate.push(a);95
}96
}97
add(item) {98
this.items.push(item);99
}
103
}105
export class Surface {65
constructor(x, y, width, height) {66
this.rect = new Rect(x, y, width, height);106
constructor(x, y, shape) {107
this.x = x;108
this.y = y;109
this.shape = shape;110
}111
update() {112
// empty
116
}117
}76
export class Rect {77
constructor(x, y, width, height) {78
this.x = x;79
this.y = y;119
class Rect {120
constructor(width, height) {121
this.width = width;122
this.height = height;123
}83
}83
\ No newline at end of file124
}126
class Circle {127
constructor(radius) {128
this.radius = radius;129
}130
}132
export const Shape = { Rect, Circle };132
\ No newline at end of file
12
if(match) {13
const [, type] = match;14
code = code.slice(0, -(type.length + 3));15
code += `data-event-ref="${events.length}"`;15
code += `data-event-ref-${events.length}`;16
events.push({ type, handler: props[i] });17
}18
continue;
21
for(const item of props[i]) {22
if(item instanceof HTMLElement) {23
const tag = item.tagName.toLowerCase();24
code += `<${tag} data-child-ref="${children.length}"></${tag}>`;24
code += `<${tag} data-child-ref-${children.length}></${tag}>`;25
children.push(item);26
continue;27
}
31
}32
if(props[i] instanceof HTMLElement) {33
const tag = props[i].tagName.toLowerCase();34
code += `<${tag} data-child-ref="${children.length}"></${tag}>`;34
code += `<${tag} data-child-ref-${children.length}></${tag}>`;35
children.push(props[i]);36
continue;37
}
44
const element = fragment.firstElementChild;45
for(const [id, {type, handler}] of Object.entries(events)) {46
let target;47
if(element.getAttribute('data-event-ref') === id) {47
if(element.hasAttribute(`data-event-ref-${id}`)) {48
target = element;49
}50
else {51
target = element.querySelector(`[data-event-ref="${id}"]`);51
target = element.querySelector(`[data-event-ref-${id}]`);52
}53
if(target) {54
target.addEventListener(type, handler);55
target.removeAttribute('data-event-ref');55
target.removeAttribute(`data-event-ref-${id}`);56
}57
else {58
console.warn(`Unable to bind ${type} event to element with id: ${id}`);59
}60
}61
for(const [id, child] of Object.entries(children)) {62
const target = element.querySelector(`[data-child-ref="${id}"]`);62
const target = element.querySelector(`[data-child-ref-${id}]`);63
if(target) {64
target.replaceWith(child);65
}