summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthias Schiffer <mschiffer@universe-factory.net>2018-11-08 22:03:08 +0100
committerMatthias Schiffer <mschiffer@universe-factory.net>2018-11-08 22:25:53 +0100
commit5eae6f29a80cd47e268cb6eaefa96a0ab0a63e54 (patch)
tree2631f7b3a9d55adc7d07408b30aecb7b714c25be
parent40339947d1a2407a6be95fad58215cdaf7d4c2c9 (diff)
downloadrpgedit-5eae6f29a80cd47e268cb6eaefa96a0ab0a63e54.tar
rpgedit-5eae6f29a80cd47e268cb6eaefa96a0ab0a63e54.zip
Implement more flexible handling of entities and collidables
-rw-r--r--dist/resources/entity/green_circle.json3
-rw-r--r--dist/resources/entity/red_circle.json14
-rw-r--r--dist/resources/sprite/entity/green_circle.png (renamed from dist/resources/sprite/entity/simple_circle.png)bin1776 -> 1776 bytes
-rw-r--r--dist/resources/sprite/entity/red_circle.pngbin0 -> 1844 bytes
-rw-r--r--src/controller/collision.ts59
-rw-r--r--src/controller/entitycontext.ts45
-rw-r--r--src/controller/gamecontext.ts110
-rw-r--r--src/model/data/collision.ts6
-rw-r--r--src/model/data/entity.ts19
-rw-r--r--src/model/data/map.ts11
-rw-r--r--src/view/entity.ts10
11 files changed, 204 insertions, 73 deletions
diff --git a/dist/resources/entity/green_circle.json b/dist/resources/entity/green_circle.json
new file mode 100644
index 0000000..14952b2
--- /dev/null
+++ b/dist/resources/entity/green_circle.json
@@ -0,0 +1,3 @@
+{
+ "sprite": "green_circle"
+}
diff --git a/dist/resources/entity/red_circle.json b/dist/resources/entity/red_circle.json
new file mode 100644
index 0000000..c9479ae
--- /dev/null
+++ b/dist/resources/entity/red_circle.json
@@ -0,0 +1,14 @@
+{
+ "sprite": "red_circle",
+ "collision": [
+ {
+ "type": "polygon",
+ "vertices": [
+ [-0.5, -0.5],
+ [-0.5, 0.5],
+ [0.5, 0.5],
+ [0.5, -0.5]
+ ]
+ }
+ ]
+}
diff --git a/dist/resources/sprite/entity/simple_circle.png b/dist/resources/sprite/entity/green_circle.png
index 71cdadb..71cdadb 100644
--- a/dist/resources/sprite/entity/simple_circle.png
+++ b/dist/resources/sprite/entity/green_circle.png
Binary files differ
diff --git a/dist/resources/sprite/entity/red_circle.png b/dist/resources/sprite/entity/red_circle.png
new file mode 100644
index 0000000..1bdf36c
--- /dev/null
+++ b/dist/resources/sprite/entity/red_circle.png
Binary files differ
diff --git a/src/controller/collision.ts b/src/controller/collision.ts
new file mode 100644
index 0000000..e1bd9f9
--- /dev/null
+++ b/src/controller/collision.ts
@@ -0,0 +1,59 @@
+import { Collision } from '../model/data/collision';
+
+import { Collidable } from '../math/collision';
+import { LineSegment, Movement } from '../math/line';
+import { Point } from '../math/point';
+
+import { vec2 } from 'gl-matrix';
+
+export function 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;
+}
+
+export interface CollidableGroup {
+ getTranslation(): vec2|null;
+ getCollidables(): Collidable[];
+}
+
+export function collide(collision: CollidableGroup, out: vec2, move: Movement, radius: number): boolean {
+ const t = collision.getTranslation();
+ if (t)
+ move = move.translate(vec2.negate(vec2.create(), t));
+
+ for (const c of collision.getCollidables()) {
+ if (!c.collide(out, move, radius))
+ continue;
+
+ if (vec2.squaredDistance(move.src, out) >= vec2.squaredDistance(move.src, move.dest))
+ continue;
+
+ if (t)
+ vec2.add(out, out, t);
+
+ return true;
+ }
+
+ return false;
+}
diff --git a/src/controller/entitycontext.ts b/src/controller/entitycontext.ts
new file mode 100644
index 0000000..c11c698
--- /dev/null
+++ b/src/controller/entitycontext.ts
@@ -0,0 +1,45 @@
+import { EntityData } from '../model/data/entity';
+
+import { loadEntity } from '../view/entity';
+import { Renderer } from '../view/renderer/renderer';
+import { SpriteView } from '../view/sprite';
+
+import { Collidable } from '../math/collision';
+
+import { getJSON } from '../util';
+import { CollidableGroup, mkCollision } from './collision';
+
+import { vec2 } from 'gl-matrix';
+
+export class EntityContext implements CollidableGroup {
+ public static async load(renderer: Renderer, name: string): Promise<EntityContext> {
+ const entity = new EntityData(await getJSON(`resources/entity/${name}.json`));
+
+ return new EntityContext(
+ renderer,
+ await loadEntity(renderer, entity),
+ mkCollision(entity.collision),
+ );
+ }
+
+ public readonly pos: vec2 = vec2.create();
+
+ private constructor(
+ private readonly renderer: Renderer,
+ private readonly view: SpriteView,
+ public readonly collision: Collidable[],
+ ) {}
+
+ public render() {
+ this.renderer.setTranslation(this.pos);
+ this.view.render();
+ }
+
+ public getTranslation(): vec2 {
+ return this.pos;
+ }
+
+ public getCollidables(): Collidable[] {
+ return this.collision;
+ }
+}
diff --git a/src/controller/gamecontext.ts b/src/controller/gamecontext.ts
index b69b306..8ec54b5 100644
--- a/src/controller/gamecontext.ts
+++ b/src/controller/gamecontext.ts
@@ -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);
-
- for (const c of this.collision) {
- if (!c.collide(out, move, this.collisionRadius))
- continue;
+ const move = new Movement(this.player.pos, dest);
- if (vec2.squaredDistance(this.entityPos, out) >= vec2.squaredDistance(this.entityPos, dest))
- 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;
- 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);
}
diff --git a/src/model/data/collision.ts b/src/model/data/collision.ts
new file mode 100644
index 0000000..5008fa3
--- /dev/null
+++ b/src/model/data/collision.ts
@@ -0,0 +1,6 @@
+export interface CollisionPolygon {
+ readonly type: 'polygon';
+ readonly vertices: Array<[number, number]>;
+}
+
+export type Collision = CollisionPolygon;
diff --git a/src/model/data/entity.ts b/src/model/data/entity.ts
new file mode 100644
index 0000000..3474a38
--- /dev/null
+++ b/src/model/data/entity.ts
@@ -0,0 +1,19 @@
+import { Collision } from './collision';
+
+export interface EntityDataInput {
+ readonly sprite: string;
+ readonly anchor?: [number, number];
+ readonly collision?: Collision[];
+}
+
+export class EntityData {
+ public readonly sprite: string;
+ public readonly anchor: [number, number];
+ public readonly collision: Collision[];
+
+ constructor(input: EntityDataInput) {
+ this.sprite = input.sprite;
+ this.anchor = input.anchor || [0.5, 0.5];
+ this.collision = input.collision || [];
+ }
+}
diff --git a/src/model/data/map.ts b/src/model/data/map.ts
index 4d9ecda..b36f6b5 100644
--- a/src/model/data/map.ts
+++ b/src/model/data/map.ts
@@ -1,11 +1,6 @@
-export interface CollisionPolygon {
- readonly type: 'polygon';
- readonly vertices: Array<[number, number]>;
-}
-
-export type Collision = CollisionPolygon;
+import { Collision } from './collision';
-export interface Input {
+export interface MapDataInput {
readonly tiles: string[];
readonly layers: number[][][];
readonly collision: Collision[];
@@ -19,7 +14,7 @@ export class MapData {
public readonly width: number;
public readonly height: number;
- constructor(data: Input) {
+ constructor(data: MapDataInput) {
this.tiles = data.tiles;
this.layers = data.layers;
this.collision = data.collision;
diff --git a/src/view/entity.ts b/src/view/entity.ts
index 6241387..421a238 100644
--- a/src/view/entity.ts
+++ b/src/view/entity.ts
@@ -1,16 +1,16 @@
+import { EntityData } from '../model/data/entity';
import { Renderer } from './renderer/renderer';
import { SpriteView, SpriteViewBuilder } from './sprite';
import { loadImage, mkTexture } from './util/image';
-export async function loadSimpleEntity(
+export async function loadEntity(
r: Renderer,
- sprite: string,
- anchor: [number, number] = [0.5, 0.5],
+ data: EntityData,
): Promise<SpriteView> {
- const tile = await loadImage(`resources/sprite/entity/${sprite}.png`);
+ const tile = await loadImage(`resources/sprite/entity/${data.sprite}.png`);
const texture = mkTexture(r.getContext(), tile);
- const [x, y] = anchor;
+ const [x, y] = data.anchor;
const builder = new SpriteViewBuilder(r, texture);
builder.addSprite([-x, -y, -x + 1, -y + 1], [0, 0, 1, 1]);