import { MapData } from '../model/data/map'; import { loadSimpleEntity } from '../view/entity'; import { DirectionHandler } from '../view/input/directionhandler'; import { loadMap } from '../view/map'; import { Renderer } from '../view/renderer/renderer'; import { SpriteView } from '../view/sprite'; import { LineSegment, Movement } from '../math/line'; import { getJSON } from '../util'; import { vec2 } from 'gl-matrix'; export class GameContext { public static async load(renderer: Renderer): Promise { const mapView = this.loadMap(renderer); const entity = loadSimpleEntity(renderer, 'simple_circle'); return new GameContext( renderer, await mapView, await entity, ); } private static async loadMap(renderer: Renderer): Promise { const map = new MapData(await getJSON('resources/map/test.json')); return loadMap(renderer, map); } private time: number|null = null; private readonly tick = 10; // ms per tick private readonly speed = 0.05; // movement per tick private readonly maxSkip = 20; // maximum ticks to process in a single render step private readonly input: DirectionHandler; private readonly entityPos: vec2 = vec2.clone([6, 6]); private readonly entityMovement: vec2 = vec2.create(); private readonly collisionRadius = 7 / 16; private readonly walls: LineSegment[] = [ LineSegment.fromPoints(vec2.fromValues(1, 1), vec2.fromValues(11, 1)), LineSegment.fromPoints(vec2.fromValues(11, 1), vec2.fromValues(11, 5)), LineSegment.fromPoints(vec2.fromValues(11, 5), vec2.fromValues(12, 5)), LineSegment.fromPoints(vec2.fromValues(12, 5), vec2.fromValues(12, 7)), LineSegment.fromPoints(vec2.fromValues(12, 7), vec2.fromValues(11, 7)), LineSegment.fromPoints(vec2.fromValues(11, 7), vec2.fromValues(11, 11)), LineSegment.fromPoints(vec2.fromValues(11, 11), vec2.fromValues(7, 11)), LineSegment.fromPoints(vec2.fromValues(7, 11), vec2.fromValues(7, 12)), LineSegment.fromPoints(vec2.fromValues(7, 12), vec2.fromValues(5, 12)), LineSegment.fromPoints(vec2.fromValues(5, 12), vec2.fromValues(5, 11)), LineSegment.fromPoints(vec2.fromValues(5, 11), vec2.fromValues(1, 11)), LineSegment.fromPoints(vec2.fromValues(1, 11), vec2.fromValues(1, 7)), LineSegment.fromPoints(vec2.fromValues(1, 7), vec2.fromValues(0, 7)), LineSegment.fromPoints(vec2.fromValues(0, 7), vec2.fromValues(0, 5)), LineSegment.fromPoints(vec2.fromValues(0, 5), vec2.fromValues(1, 5)), LineSegment.fromPoints(vec2.fromValues(1, 5), vec2.fromValues(1, 1)), ]; private constructor( private readonly renderer: Renderer, private readonly mapView: SpriteView, private readonly entity: SpriteView, ) { this.input = new DirectionHandler(); this.input.addListener((v) => { if (vec2.sqrLen(v) > 0) vec2.normalize(this.entityMovement, v); else vec2.copy(this.entityMovement, [0, 0]); }); window.requestAnimationFrame(this.render); } private updateTime(time: number): number { const diff = this.time !== null ? time - this.time : 0; this.time = time; return diff; } private updateStep(): void { const dest = vec2.scaleAndAdd(vec2.create(), this.entityPos, this.entityMovement, this.speed); const dest2 = vec2.create(); let rescan = true; while (rescan) { rescan = false; if (vec2.equals(dest, this.entityPos)) return; const move = new Movement(this.entityPos, dest); for (const w of this.walls) { if (!w.collidesMoveCircle(dest2, move, this.collisionRadius)) continue; if (!vec2.exactEquals(dest, dest2)) { // Ensure termination if (vec2.squaredDistance(this.entityPos, dest2) >= vec2.squaredDistance(this.entityPos, dest)) return; vec2.copy(dest, dest2); rescan = true; break; } } } vec2.copy(this.entityPos, dest); } private update(time: number): void { const diff = Math.min(this.maxSkip, this.updateTime(time)); if (vec2.sqrLen(this.entityMovement) === 0) { this.renderer.snapToGrid(this.entityPos, this.entityPos); return; } for (let i = 0; i < diff; i++) this.updateStep(); } private render = (time: number) => { this.update(Math.round(time / this.tick)); this.renderer.setCenter(this.entityPos); this.renderer.clear(); this.mapView.render(); this.renderer.setTranslation(this.entityPos); this.entity.render(); window.requestAnimationFrame(this.render); } }