import Camera from "./camera"; import ControlledBody from "./controlledBody"; import GameObject from "./object"; import PhysicalBody from "./physicalBody"; import StaticBody from "./staticBody"; class Renderer extends HTMLCanvasElement { ctx: CanvasRenderingContext2D; objects: GameObject[]; physics?: { gravity: number }; camera: Camera; forceNotInObject: boolean; private beforeRenderFuncs: (() => void)[]; constructor() { super(); window.addEventListener("resize", () => this.resize()); this.ctx = this.getContext("2d") as CanvasRenderingContext2D; this.objects = []; this.render = this.render.bind(this); this.camera = new Camera(this); this.forceNotInObject = true; this.beforeRenderFuncs = []; } beforeRender(func: () => void) { this.beforeRenderFuncs.push(func); return this; } /** * Makes images not blur when scaled */ enablePixelated() { // allow for pixelated look when scaling // @ts-ignore this.ctx.webkitImageSmoothingEnabled = false; // @ts-ignore this.ctx.mozImageSmoothingEnabled = false; this.ctx.imageSmoothingEnabled = false; return this; } enablePhysics({ gravity = 0.7 }) { this.physics = { gravity, }; return this; } enableFixedPosition() { this.style.position = "fixed"; this.style.top = "0px"; this.style.left = "0px"; return this; } resize() { if (this.parentElement) { this.width = this.parentElement.offsetWidth; this.height = this.parentElement.offsetHeight; } else { this.width = window.innerWidth; this.height = window.innerHeight; } return this; } add(object: GameObject) { this.objects.push(object); return object; } destroy(item: GameObject | number) { if (typeof item === "number") { this.objects.filter((object) => object._randomId === item); } else { this.objects = this.objects.filter((object) => object !== item); } return item; } /** * Mounts renderer to the dom */ mount(container: HTMLElement) { container.appendChild(this); return this; } update(mulitplier = 1) { this.objects.forEach((object) => { if (this.physics) { if (object instanceof PhysicalBody) { object.v.y += this.physics.gravity * mulitplier; object.update(mulitplier); const { x: startX, y: startY } = object; object.x += object.v.x * mulitplier; object.y += object.v.y * mulitplier; const bigX = (startX + object.x) / 2; const bigY = (startY + object.y) / 2; const bigWidth = Math.abs(startX - object.x) + object.width; const bigHeight = Math.abs(startY - object.y) + object.height; const big = { x: bigX, y: bigY, width: bigWidth, height: bigHeight, }; object.isOnBody = false; if (object instanceof ControlledBody) object.wallSide = 1; for (const body of this.objects) { if (body.collides(big)) { body.onCollide(object); object.onCollide(body); if ( (body instanceof StaticBody || body instanceof PhysicalBody) && body._randomId !== object._randomId && !( body instanceof PhysicalBody && (!body.interactsWithPhysicalBodies || !object.interactsWithPhysicalBodies) ) ) { // if started above then on platform if (startY + object.height / 2 <= body.y - body.height / 2) { object.y = body.y - body.height / 2 - object.height / 2; if (body instanceof PhysicalBody) { body.v.y += (object.v.y * object.mass) / body.mass; object.v.y = 0; } else { object.v.y = 0; } object.isOnBody = true; if (object instanceof ControlledBody) object.jumps = 0; } else if ( startY - object.height / 2 >= body.y + body.height / 2 ) { object.y = body.y + body.height / 2 + object.height / 2; if (body instanceof PhysicalBody) { body.v.y += (object.v.y * object.mass) / body.mass; object.v.y = 0; } else { object.v.y = 0; } } else if ( startX + object.width / 2 <= body.x - body.width / 2 ) { object.x = body.x - body.width / 2 - object.width / 2; if (body instanceof PhysicalBody) { body.v.x += (object.v.x * object.mass) / body.mass; object.v.x = 0; } else { object.v.x = 0; } if ( object instanceof ControlledBody && !(body instanceof PhysicalBody) && object.wallJumps ) { object.jumps = 0; object.wallSide = 0; } } else if ( startX - object.width / 2 >= body.x + body.width / 2 ) { object.x = body.x + body.width / 2 + object.width / 2; if (body instanceof PhysicalBody) { body.v.x += (object.v.x * object.mass) / body.mass; object.v.x = 0; } else { object.v.x = 0; } if ( object instanceof ControlledBody && !(body instanceof PhysicalBody) && object.wallJumps ) { object.jumps = 0; object.wallSide = 2; } object.v.x = 0; } else { if (this.forceNotInObject) { // started in block const left = object.x - object.width / 2 - (body.x - body.width / 2); const right = body.x + body.width / 2 - (object.x + object.width / 2); const top = body.y - body.height / 2 - (object.y - object.height / 2); const bottom = body.y + body.height / 2 - (object.y + object.height / 2); const val = Math.min(left, right, top, bottom); if (val === top) { object.y = body.y - body.height / 2 - object.height / 2; if (body instanceof PhysicalBody) { body.v.y += (object.v.y * object.mass) / body.mass; object.v.y = 0; } else { object.v.y = 0; } object.isOnBody = true; if (object instanceof ControlledBody) object.jumps = 0; } else if (val === bottom) { object.y = body.y + body.height / 2 + object.height / 2; if (body instanceof PhysicalBody) { body.v.y += (object.v.y * object.mass) / body.mass; object.v.y = 0; } else { object.v.y = 0; } } else if (val === left) { object.x = body.x - body.width / 2 - object.width / 2; if (body instanceof PhysicalBody) { body.v.x += (object.v.x * object.mass) / body.mass; object.v.x = 0; } else { object.v.x = 0; } if ( object instanceof ControlledBody && !(body instanceof PhysicalBody) && object.wallJumps ) { object.jumps = 0; object.wallSide = 0; } } else { object.x = body.x + body.width / 2 + object.width / 2; if (body instanceof PhysicalBody) { body.v.x += (object.v.x * object.mass) / body.mass; object.v.x = 0; } else { object.v.x = 0; } if ( object instanceof ControlledBody && !(body instanceof PhysicalBody) && object.wallJumps ) { object.jumps = 0; object.wallSide = 2; } object.v.x = 0; } } } } } } } } }); } render() { this.camera.update(); this.ctx.clearRect(0, 0, this.width, this.height); this.beforeRenderFuncs.forEach((func) => func()); this.ctx.translate(this.width / 2 - this.camera.pos.x, this.height / 2 - this.camera.pos.y); this.objects .sort((a, b) => a.layer - b.layer) .forEach((object) => object._render(this.ctx)); this.ctx.translate(-this.width / 2 + this.camera.pos.x, -this.height / 2 + this.camera.pos.y); } } customElements.define("game-renderer", Renderer, { extends: "canvas" }); export default Renderer;