From 3c51a1994f41b625823c4f15e92396b5498ce23c Mon Sep 17 00:00:00 2001 From: Matthias Schiffer Date: Tue, 24 Dec 2019 13:53:16 +0100 Subject: Move renderer into "runtime" subdirectory --- src/renderer/runtime/controller/gamecontext.ts | 174 +++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 src/renderer/runtime/controller/gamecontext.ts (limited to 'src/renderer/runtime/controller/gamecontext.ts') diff --git a/src/renderer/runtime/controller/gamecontext.ts b/src/renderer/runtime/controller/gamecontext.ts new file mode 100644 index 0000000..9a68e25 --- /dev/null +++ b/src/renderer/runtime/controller/gamecontext.ts @@ -0,0 +1,174 @@ +import { CollidableGroup, collide, mkCollision } from './collision'; +import { EntityContext } from './entitycontext'; + +import { MapData } from '../model/data/map'; + +import { ButtonCode, GameInputHandler } from '../view/input/gameinput'; +import { MapView } from '../view/map'; +import { Renderer } from '../view/renderer/renderer'; + +import { Collidable } from '../math/collision'; +import { Movement } from '../math/line'; +import { getJSON, nextAnimationFrame } from '../util'; + +import { vec2 } from 'gl-matrix'; + +export class GameContext implements CollidableGroup { + public static async load(renderer: Renderer): Promise { + const map = this.loadMap(renderer, 'test'); + const loadPlayer = EntityContext.load(renderer, 'green_circle'); + const loadEntity = EntityContext.load(renderer, 'red_circle'); + + const [mapView, mapCollision] = await map; + const player = await loadPlayer; + const entity = await loadEntity; + + vec2.set(player.pos, 7, 6); + vec2.set(entity.pos, 4, 3); + + return new GameContext( + renderer, + mapView, + player, + [entity], + mapCollision, + ); + } + + private static async loadMap(renderer: Renderer, name: string): Promise<[MapView, Collidable[]]> { + const map = new MapData(await getJSON(`resources/map/${name}.json`)); + return [await MapView.load(renderer, map), mkCollision(map.collision)]; + } + + private readonly initTime: number = performance.now(); + private time = 0; + + private readonly tick = 10; // ms per tick + private readonly maxSpeed = 0.04; // movement per tick + private readonly maxSkip = 20; // maximum ticks to process in a single render step + + private readonly input: GameInputHandler; + + private readonly playerDir: vec2 = vec2.fromValues(0, 1); + private speed: number = 0; + + private readonly collisionRadius = 15 / 32; + private readonly interactLength = 1 / 32; + + private constructor( + private readonly renderer: Renderer, + private readonly mapView: MapView, + private readonly player: EntityContext, + private readonly entities: EntityContext[], + private readonly collision: Collidable[], + ) { + this.input = new GameInputHandler(); + this.input.addListener((input) => { + switch (input.type) { + case 'button': + if (input.button === ButtonCode.Action) + this.interact(); + break; + + case 'direction': + if (vec2.sqrLen(input.direction) > 0) { + vec2.copy(this.playerDir, input.direction); + this.speed = this.maxSpeed; + } else { + this.speed = 0; + } + break; + } + }); + + this.renderLoop(); + } + + public getTranslation(): null { + return null; + } + + public getCollidables(): Collidable[] { + return this.collision; + } + + private updateTime(time: number): number { + const diff = Math.round(time / this.tick) - Math.round(this.time / this.tick); + this.time = time; + + return diff; + } + + private canInteract(c: CollidableGroup): boolean { + const dest = vec2.scaleAndAdd(vec2.create(), this.player.pos, this.playerDir, this.interactLength); + const move = new Movement(this.player.pos, dest); + + return collide(c, vec2.create(), move, this.collisionRadius); + } + + private interact(): void { + for (const e of this.entities) { + if (!this.canInteract(e)) + continue; + + e.interact(); + break; + } + } + + private updateStepCollide(out: vec2, dest: vec2): boolean { + const move = new Movement(this.player.pos, dest); + + for (const c of [this, ...this.entities]) { + if (collide(c, out, move, this.collisionRadius)) { + if (vec2.squaredDistance(move.src, out) >= vec2.squaredDistance(move.src, move.dest)) + continue; + + return true; + } + } + + return false; + } + + private updateStep(): void { + const dest = vec2.scaleAndAdd(vec2.create(), this.player.pos, this.playerDir, this.speed); + const newDest = vec2.create(); + + while (this.updateStepCollide(newDest, dest)) { + if (vec2.equals(newDest, this.player.pos)) + return; + + vec2.copy(dest, newDest); + } + + vec2.copy(this.player.pos, dest); + } + + private update(time: number): void { + const diff = Math.min(this.maxSkip, this.updateTime(time)); + + if (!this.speed) { + this.renderer.snapToGrid(this.player.pos, this.player.pos); + return; + } + + for (let i = 0; i < diff; i++) + this.updateStep(); + } + + private render(): void { + this.renderer.setCenter(this.player.pos); + this.renderer.clear(); + + for (const r of [this.mapView, ...this.entities, this.player]) + r.render(this.time); + } + + private async renderLoop(): Promise { + while (true) { + this.update(await nextAnimationFrame() - this.initTime); + this.render(); + } + } +} -- cgit v1.2.3