1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
|
import { Collision, 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 { getJSON } from '../util';
import { vec2 } from 'gl-matrix';
export class GameContext {
public static async load(renderer: Renderer): Promise<GameContext> {
const entity = loadSimpleEntity(renderer, 'simple_circle');
const [mapView, collision] = await this.loadMap(renderer);
return new GameContext(
renderer,
mapView,
await entity,
collision,
);
}
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 time: number|null = null;
private readonly tick = 10; // ms per tick
private readonly speed = 0.04; // 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 constructor(
private readonly renderer: Renderer,
private readonly mapView: SpriteView,
private readonly entity: SpriteView,
private readonly collision: Collidable[],
) {
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 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;
if (vec2.squaredDistance(this.entityPos, out) >= vec2.squaredDistance(this.entityPos, dest))
continue;
return true;
}
return false;
}
private updateStep(): void {
const dest = vec2.scaleAndAdd(vec2.create(), this.entityPos, this.entityMovement, this.speed);
const newDest = vec2.create();
while (this.updateStepCollide(newDest, dest)) {
if (vec2.equals(newDest, this.entityPos))
return;
vec2.copy(dest, newDest);
}
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);
}
}
|