summaryrefslogtreecommitdiffstats
path: root/src/renderer/runtime/controller/gamecontext.ts
diff options
context:
space:
mode:
authorMatthias Schiffer <mschiffer@universe-factory.net>2019-12-24 13:53:16 +0100
committerMatthias Schiffer <mschiffer@universe-factory.net>2019-12-24 13:53:16 +0100
commit3c51a1994f41b625823c4f15e92396b5498ce23c (patch)
tree7a96310ec32df82ac87039ea555300bcab510a5e /src/renderer/runtime/controller/gamecontext.ts
parent33926af829848050c54c698ed22da9fe2b912aea (diff)
downloadrpgedit-3c51a1994f41b625823c4f15e92396b5498ce23c.tar
rpgedit-3c51a1994f41b625823c4f15e92396b5498ce23c.zip
Move renderer into "runtime" subdirectory
Diffstat (limited to 'src/renderer/runtime/controller/gamecontext.ts')
-rw-r--r--src/renderer/runtime/controller/gamecontext.ts174
1 files changed, 174 insertions, 0 deletions
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<GameContext> {
+ 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<void> {
+ while (true) {
+ this.update(await nextAnimationFrame() - this.initTime);
+ this.render();
+ }
+ }
+}