Skip to content

Commit a16a437

Browse files
committed
Switch and rework in TS!
Most of the V2 rework has been lifted over to TypeScript and improved upon. This will be the main version from now on.
1 parent fe843c3 commit a16a437

31 files changed

+808
-1242
lines changed

.gitignore

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
lib-cov
2+
*.seed
3+
*.log
4+
*.csv
5+
*.dat
6+
*.out
7+
*.pid
8+
*.gz
9+
*.swp
10+
11+
pids
12+
logs
13+
results
14+
tmp
15+
16+
# Build
17+
public/css/main.css
18+
19+
# Coverage reports
20+
coverage
21+
22+
# API keys and secrets
23+
.env
24+
25+
# Dependency directory
26+
node_modules
27+
bower_components
28+
29+
# Editors
30+
.idea
31+
*.iml
32+
33+
# OS metadata
34+
.DS_Store
35+
Thumbs.db
36+
37+
# Ignore built ts files
38+
dist/**/*
39+
js/**/*
40+
41+
# ignore yarn.lock
42+
yarn.lock
43+
44+
.vs

Core/Engine/Core.ts

+148
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import { EntityList } from "../Entities/Entities.js";
2+
import { AnimationUpdateLoop } from "../Graphics/AnimationUpdateLoop.js";
3+
import { CanvasRenderer } from "../Graphics/CanvasRenderer.js";
4+
import { SpriteSheetList } from "../Sprites/Sprites.js";
5+
6+
export class EntityEngine {
7+
private _targetfps: number = 60;
8+
private _lastCycleExecuteTimestamp: number;
9+
private _lastCycleExecuteStart: number;
10+
private _lastCycleExecuteDuration: number;
11+
private _awaitUntilTimestamp: number = 0;
12+
private _runEnabled: boolean = true;
13+
14+
public Awake = () => { };
15+
16+
public Update = () => { };
17+
18+
public CustomRender = (renderer: CanvasRenderer) => { };
19+
20+
public Renderer: CanvasRenderer;
21+
22+
private CycleExecute = (timeStamp: number) => {
23+
const msDelta = (this.DeltaTime() * 1000);
24+
let allow = msDelta >= (1000 / this._targetfps);
25+
if (this._awaitUntilTimestamp != 0) {
26+
// Non-thread blocking absolute cutie, I love you.
27+
if (msDelta < this._awaitUntilTimestamp) {
28+
allow = false;
29+
}
30+
else if (msDelta >= this._awaitUntilTimestamp) {
31+
this._awaitUntilTimestamp = 0;
32+
}
33+
}
34+
if (allow) {
35+
this._lastCycleExecuteStart = performance.now();
36+
// ------
37+
// Actual update function called now; this is where any gamecode is executed
38+
this.Update();
39+
// Render entities
40+
this.Renderer.Render();
41+
// Update animations
42+
AnimationUpdateLoop();
43+
// Call custom render function
44+
this.CustomRender(this.Renderer);
45+
// ------
46+
this._lastCycleExecuteDuration = (performance.now() - this._lastCycleExecuteStart);
47+
if (timeStamp == null) {
48+
this._lastCycleExecuteTimestamp = performance.now();
49+
}
50+
else {
51+
this._lastCycleExecuteTimestamp = timeStamp;
52+
}
53+
}
54+
if (this._runEnabled) {
55+
requestAnimationFrame(this.CycleExecute);
56+
}
57+
else {
58+
this._runEnabled = true;
59+
}
60+
}
61+
62+
/**
63+
* Starts the engine and sets it to its default state.
64+
*/
65+
public Start() {
66+
SpriteSheetList.length = 0;
67+
EntityList.length = 0;
68+
this.Awake();
69+
this._awaitUntilTimestamp = 0;
70+
this._lastCycleExecuteTimestamp = 0;
71+
requestAnimationFrame(this.CycleExecute);
72+
}
73+
74+
/**
75+
* Stops the engine.
76+
*/
77+
public Stop() {
78+
this._runEnabled = true;
79+
this._awaitUntilTimestamp = 0;
80+
}
81+
82+
/**
83+
* Restarts the engine while keeping its state.
84+
*/
85+
public Restart() {
86+
this._runEnabled = true;
87+
this._awaitUntilTimestamp = 0;
88+
window.requestAnimationFrame(this.CycleExecute);
89+
}
90+
91+
/**
92+
* Attach the engine instance to an HTML Canvas to draw the frame onto.
93+
* @param canvasID
94+
*/
95+
public AttachToCanvas(canvasID: string) {
96+
this.Renderer = new CanvasRenderer(canvasID);
97+
}
98+
99+
/**
100+
* Stops engine execution for the specified duration (ms). During this time no new frames are rendered, or game code is executed.
101+
* @param durationMS
102+
*/
103+
public Wait(durationMS: number): number {
104+
const waitEnd = performance.now() + durationMS;
105+
this._awaitUntilTimestamp = waitEnd;
106+
return this._awaitUntilTimestamp;
107+
}
108+
109+
/**
110+
* Cancels Wait. Does not restart execution immediately, but at the next frame render.
111+
*/
112+
public CancelWait() {
113+
this._awaitUntilTimestamp = 0;
114+
}
115+
116+
/**
117+
* Gets the time since the last frame in milliseconds.
118+
*/
119+
public DeltaTime(): number {
120+
const timeStamp = performance.now();
121+
const deltaTime = (timeStamp - this._lastCycleExecuteTimestamp) / 1000;
122+
return deltaTime;
123+
}
124+
125+
/**
126+
* Gets the time the engine needs to execute a single frame in milliseconds.
127+
*/
128+
public GetExecutionTime(): number {
129+
return this._lastCycleExecuteDuration;
130+
}
131+
132+
/**
133+
* Gets the time the engine is idling between frames in milliseconds.
134+
*/
135+
public GetExecutionIdleTime(): number {
136+
const frameTimeAllocation = (1000 / this._targetfps);
137+
const frameIdleTime = frameTimeAllocation - this._lastCycleExecuteDuration;
138+
return frameIdleTime;
139+
}
140+
141+
/**
142+
* Gets the potential framerate that the engine could currently execute at.
143+
*/
144+
public GetVirtualFrameRate(): number {
145+
const fps = (1000 / this._lastCycleExecuteDuration);
146+
return fps;
147+
}
148+
}

Core/Entities/Collision.ts

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { EntityList } from "./Entities.js";
2+
import { vector2D } from "../Types/Types";
3+
import { Entity } from "./Entity.js";
4+
5+
/**
6+
* Gets the list of Entities with the given tag(s) that are overlapping the specified Entity.
7+
* @param self The main Entity we are testing against.
8+
* @param tags The tags that are used for filtering. Only one must apply to an Entity in order to be tested.
9+
*/
10+
export function GetCollisionsByTag(self: Entity, tags: string[]): Array<Entity> {
11+
let collisionObjects = [];
12+
for (const target of EntityList) {
13+
const isMatching = tags.some(tag => target.Tags.includes(tag));
14+
if (isMatching) {
15+
// We need to check both self -> target, and target -> self
16+
// because in some cases (target is bigger than self) it's possible for vertices not be inside self or target
17+
if (isOverlapping(self, target) || isOverlapping(target, self))
18+
{
19+
collisionObjects.push(target);
20+
}
21+
}
22+
}
23+
return collisionObjects;
24+
}
25+
26+
/**
27+
* Checks if two Entities are overlapping eachother.
28+
* @param entityA
29+
* @param entityB
30+
*/
31+
export function GetCollision(entityA: Entity, entityB: Entity): boolean {
32+
if (isOverlapping(entityA, entityB) || isOverlapping(entityB, entityA))
33+
{
34+
return true;
35+
}
36+
return false;
37+
}
38+
39+
/**
40+
* Gets the list of every Entity that is colliding with the target. Note that this tests against every registered Entity, which can be extremely slow.
41+
* @param target
42+
*/
43+
export function GetCollisions(target: Entity): Array<Entity> {
44+
let collisionObjects = [];
45+
for (const target of EntityList) {
46+
if (isOverlapping(target, target) || isOverlapping(target, target))
47+
{
48+
collisionObjects.push(target);
49+
}
50+
}
51+
return collisionObjects;
52+
}
53+
54+
55+
function isOverlapping(self: Entity, target: Entity): boolean {
56+
let overlap = false;
57+
for (let testVertexIndex = 0; testVertexIndex < target.Vertices.length; testVertexIndex++) {
58+
// Assume we have a collision
59+
let isTestVertexInsideSelf = true;
60+
let isTestEdgeIntersect = false;
61+
// Get the vertex, whose orientation we want to check relative to the line
62+
const target_pointA_index = testVertexIndex;
63+
const target_pointA = target.Vertices[target_pointA_index];
64+
const target_pointB_index = (testVertexIndex + 1 < self.Vertices.length) ? (testVertexIndex + 1) : (0);
65+
const target_pointB = target.Vertices[target_pointB_index];
66+
// Check vertex against self's every edge
67+
for (let selfVertexIndex = 0; selfVertexIndex < self.Vertices.length; selfVertexIndex++) {
68+
const self_pointA_index = selfVertexIndex;
69+
// Wrap-around for the final edge
70+
const self_pointB_index = (selfVertexIndex + 1 < self.Vertices.length) ? (selfVertexIndex + 1) : (0);
71+
const self_pointA = self.Vertices[self_pointA_index];
72+
const self_pointB = self.Vertices[self_pointB_index];
73+
// Check if testVertex is to the right of self's edge (counter-clockwise)
74+
const doEdgesIntersect = ((isLeft(self_pointA, self_pointB, target_pointB).edge != isLeft(self_pointA, self_pointB, target_pointA).edge) && (isLeft(target_pointA, target_pointB, self_pointA).edge != isLeft(target_pointA, target_pointB, self_pointB).edge));
75+
const isPointWithinSelf = isLeft(self_pointA, self_pointB, target_pointA).point;
76+
if (doEdgesIntersect) {
77+
// If true, we can ignore the rest of the edges, since one intersecting edge means collision
78+
isTestEdgeIntersect = true;
79+
break;
80+
}
81+
else if (isPointWithinSelf == false) {
82+
// If false, we can ignore the rest of the edges, since this MUST be true to ALL edges
83+
isTestVertexInsideSelf = false;
84+
break;
85+
}
86+
}
87+
if (isTestEdgeIntersect == true || isTestVertexInsideSelf == true) {
88+
// testVertex is inside self
89+
overlap = true;
90+
break;
91+
}
92+
}
93+
return overlap;
94+
}
95+
96+
function isLeft(pointA: vector2D, pointB: vector2D, pointF: vector2D) {
97+
const BAx = pointB.x - pointA.x;
98+
const FAy = pointF.y - pointA.y;
99+
const BAy = pointB.y - pointA.y;
100+
const FAx = pointF.x - pointA.x;
101+
//
102+
const first = BAx * FAy;
103+
const second = BAy * FAx;
104+
//
105+
return {
106+
edge: (first > second),
107+
point: (first - second > 0),
108+
};
109+
}

Core/Entities/Entities.ts

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { Entity } from "./Entity.js";
2+
3+
export const EntityList: Array<Entity> = [];
4+
5+
export const Entities = {
6+
Register(entity: Entity) {
7+
const doesEntityExist = (Entities.Find(entity.Name) != undefined);
8+
if (doesEntityExist == false) {
9+
EntityList.push(entity);
10+
}
11+
else {
12+
console.warn(`Can not register "${entity.Name}"; an Entity with the same name already exists.`);
13+
}
14+
},
15+
16+
FindAllTagged(tag: string) {
17+
const taggedEntities = EntityList.filter(element => element.Tags.includes(tag));
18+
return taggedEntities;
19+
},
20+
21+
Find(name: string) {
22+
const searchedEntity = EntityList.find(element => element.Name == name);
23+
return searchedEntity;
24+
},
25+
26+
Destroy(name: string) {
27+
const index = EntityList.findIndex(element => element.Name == name);
28+
if (index != -1) {
29+
EntityList.splice(index, 1);
30+
}
31+
}
32+
}

0 commit comments

Comments
 (0)