Implement more flexible handling of entities and collidables

This commit is contained in:
Matthias Schiffer 2018-11-08 22:03:08 +01:00
parent 40339947d1
commit 5eae6f29a8
Signed by: neocturne
GPG key ID: 16EF3F64CB201D9C
11 changed files with 204 additions and 73 deletions

View file

@ -1,60 +1,44 @@
import { Collision, MapData } from '../model/data/map';
import { CollidableGroup, collide, mkCollision } from './collision';
import { EntityContext } from './entitycontext';
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 { Collidable } from '../math/collision';
import { LineSegment, Movement } from '../math/line';
import { Point } from '../math/point';
import { Movement } from '../math/line';
import { getJSON } from '../util';
import { vec2 } from 'gl-matrix';
export class GameContext {
export class GameContext implements CollidableGroup {
public static async load(renderer: Renderer): Promise<GameContext> {
const entity = loadSimpleEntity(renderer, 'simple_circle');
const [mapView, collision] = await this.loadMap(renderer);
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, 6, 6);
vec2.set(entity.pos, 3, 3);
return new GameContext(
renderer,
mapView,
await entity,
collision,
player,
[entity],
mapCollision,
);
}
private static mkCollision(collision: Collision[]): Collidable[] {
const ret: Collidable[] = [];
for (const c of collision) {
switch (c.type) {
case 'polygon':
if (!c.vertices.length)
continue;
let prev = c.vertices[c.vertices.length - 1];
for (const v of c.vertices) {
ret.push(LineSegment.fromPoints(vec2.clone(prev), vec2.clone(v)));
prev = v;
}
for (const v of c.vertices) {
ret.push(new Point(vec2.clone(v)));
prev = v;
}
}
}
return ret;
}
private static async loadMap(renderer: Renderer): Promise<[SpriteView, Collidable[]]> {
const map = new MapData(await getJSON('resources/map/test.json'));
return [await loadMap(renderer, map), this.mkCollision(map.collision)];
private static async loadMap(renderer: Renderer, name: string): Promise<[SpriteView, Collidable[]]> {
const map = new MapData(await getJSON(`resources/map/${name}.json`));
return [await loadMap(renderer, map), mkCollision(map.collision)];
}
private time: number|null = null;
@ -65,28 +49,36 @@ export class GameContext {
private readonly input: DirectionHandler;
private readonly entityPos: vec2 = vec2.clone([6, 6]);
private readonly entityMovement: vec2 = vec2.create();
private readonly playerMovement: vec2 = vec2.create();
private readonly collisionRadius = 7 / 16;
private constructor(
private readonly renderer: Renderer,
private readonly mapView: SpriteView,
private readonly entity: SpriteView,
private readonly player: EntityContext,
private readonly entities: EntityContext[],
private readonly collision: Collidable[],
) {
this.input = new DirectionHandler();
this.input.addListener((v) => {
if (vec2.sqrLen(v) > 0)
vec2.normalize(this.entityMovement, v);
vec2.normalize(this.playerMovement, v);
else
vec2.copy(this.entityMovement, [0, 0]);
vec2.copy(this.playerMovement, [0, 0]);
});
window.requestAnimationFrame(this.render);
}
public getTranslation(): null {
return null;
}
public getCollidables(): Collidable[] {
return this.collision;
}
private updateTime(time: number): number {
const diff = this.time !== null ? time - this.time : 0;
this.time = time;
@ -95,41 +87,39 @@ export class GameContext {
}
private updateStepCollide(out: vec2, dest: vec2): boolean {
const move = new Movement(this.entityPos, dest);
const move = new Movement(this.player.pos, dest);
for (const c of this.collision) {
if (!c.collide(out, move, this.collisionRadius))
continue;
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;
if (vec2.squaredDistance(this.entityPos, out) >= vec2.squaredDistance(this.entityPos, dest))
continue;
return true;
return true;
}
}
return false;
}
private updateStep(): void {
const dest = vec2.scaleAndAdd(vec2.create(), this.entityPos, this.entityMovement, this.speed);
const dest = vec2.scaleAndAdd(vec2.create(), this.player.pos, this.playerMovement, this.speed);
const newDest = vec2.create();
while (this.updateStepCollide(newDest, dest)) {
if (vec2.equals(newDest, this.entityPos))
if (vec2.equals(newDest, this.player.pos))
return;
vec2.copy(dest, newDest);
}
vec2.copy(this.entityPos, dest);
vec2.copy(this.player.pos, 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);
if (vec2.sqrLen(this.playerMovement) === 0) {
this.renderer.snapToGrid(this.player.pos, this.player.pos);
return;
}
@ -140,13 +130,13 @@ export class GameContext {
private render = (time: number) => {
this.update(Math.round(time / this.tick));
this.renderer.setCenter(this.entityPos);
this.renderer.setCenter(this.player.pos);
this.renderer.clear();
this.mapView.render();
this.renderer.setTranslation(this.entityPos);
this.entity.render();
for (const r of [...this.entities, this.player])
r.render();
window.requestAnimationFrame(this.render);
}