mirror of https://github.com/mode777/rayjs.git
Add typescript framework
This commit is contained in:
parent
930b087864
commit
9d7397b513
|
@ -2,287 +2,41 @@
|
||||||
/******/ "use strict";
|
/******/ "use strict";
|
||||||
/******/ var __webpack_modules__ = ({
|
/******/ var __webpack_modules__ = ({
|
||||||
|
|
||||||
/***/ "./src/examples.ts":
|
|
||||||
/*!*************************!*\
|
|
||||||
!*** ./src/examples.ts ***!
|
|
||||||
\*************************/
|
|
||||||
/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
|
|
||||||
|
|
||||||
|
|
||||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
||||||
exports.FirstPersonMaze = exports.GameController = void 0;
|
|
||||||
const systems_1 = __webpack_require__(/*! ./systems */ "./src/systems.ts");
|
|
||||||
class GameController extends systems_1.SystemContainer {
|
|
||||||
constructor() {
|
|
||||||
super(...arguments);
|
|
||||||
this.systems = [
|
|
||||||
new BasicWindow(),
|
|
||||||
new FirstPersonMaze()
|
|
||||||
];
|
|
||||||
}
|
|
||||||
load() {
|
|
||||||
super.load();
|
|
||||||
this.currentIndex = 0;
|
|
||||||
this.currentId = this.addSystem(this.systems[this.currentIndex]);
|
|
||||||
}
|
|
||||||
update(dt) {
|
|
||||||
if (isKeyPressed(KEY_RIGHT)) {
|
|
||||||
this.removeSystem(this.currentId);
|
|
||||||
this.currentIndex = (this.currentIndex + 1) % this.systems.length;
|
|
||||||
this.currentId = this.addSystem(this.systems[this.currentIndex]);
|
|
||||||
}
|
|
||||||
super.update(dt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exports.GameController = GameController;
|
|
||||||
class BasicWindow extends systems_1.SystemBase {
|
|
||||||
draw() {
|
|
||||||
super.draw();
|
|
||||||
drawText("Congrats! You created your first window!", 190, 200, 20, LIGHTGRAY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
class FirstPersonMaze extends systems_1.SystemBase {
|
|
||||||
load() {
|
|
||||||
super.load();
|
|
||||||
// Define the camera to look into our 3d world
|
|
||||||
this.camera = new Camera3D(new Vector3(0.2, 0.4, 0.2), new Vector3(0.185, 0.4, 0.0), new Vector3(0, 1, 0), 45, CAMERA_PERSPECTIVE);
|
|
||||||
const position = new Vector3(0, 0, 0); // Set model position
|
|
||||||
const imMap = loadImage("assets/cubicmap.png"); // Load cubicmap image (RAM)
|
|
||||||
this.cubicmap = loadTextureFromImage(imMap); // Convert image to texture to display (VRAM)
|
|
||||||
const mesh = genMeshCubicmap(imMap, new Vector3(1.0, 1.0, 1.0));
|
|
||||||
this.model = loadModelFromMesh(mesh);
|
|
||||||
// NOTE: By default each cube is mapped to one part of texture atlas
|
|
||||||
this.texture = loadTexture("assets/cubicmap_atlas.png"); // Load map texture
|
|
||||||
//model.materials[0].maps[MATERIAL_MAP_DIFFUSE].texture = texture; // Set map diffuse texture
|
|
||||||
const mat = loadMaterialDefault();
|
|
||||||
setMaterialTexture(mat, MATERIAL_MAP_DIFFUSE, this.texture);
|
|
||||||
setModelMaterial(this.model, 0, mat);
|
|
||||||
// Get map image data to be used for collision detection
|
|
||||||
this.mapPixels = new Uint8Array(loadImageColors(imMap));
|
|
||||||
unloadImage(imMap); // Unload image from RAM
|
|
||||||
this.mapPosition = new Vector3(-16.0, 0.0, -8.0); // Set model position
|
|
||||||
disableCursor();
|
|
||||||
}
|
|
||||||
update(dt) {
|
|
||||||
super.update(dt);
|
|
||||||
let oldCamPos = this.camera.position; // Store old camera position
|
|
||||||
updateCamera(this.camera, CAMERA_FIRST_PERSON);
|
|
||||||
// Check player collision (we simplify to 2D collision detection)
|
|
||||||
const playerPos = new Vector2(this.camera.position.x, this.camera.position.z);
|
|
||||||
const playerRadius = 0.1; // Collision radius (player is modelled as a cilinder for collision)
|
|
||||||
this.playerCellX = Math.floor(playerPos.x - this.mapPosition.x + 0.5);
|
|
||||||
this.playerCellY = Math.floor(playerPos.y - this.mapPosition.z + 0.5);
|
|
||||||
// Out-of-limits security check
|
|
||||||
if (this.playerCellX < 0)
|
|
||||||
this.playerCellX = 0;
|
|
||||||
else if (this.playerCellX >= this.cubicmap.width)
|
|
||||||
this.playerCellX = this.cubicmap.width - 1;
|
|
||||||
if (this.playerCellY < 0)
|
|
||||||
this.playerCellY = 0;
|
|
||||||
else if (this.playerCellY >= this.cubicmap.height)
|
|
||||||
this.playerCellY = this.cubicmap.height - 1;
|
|
||||||
// Check map collisions using image data and player position
|
|
||||||
// TODO: Improvement: Just check player surrounding cells for collision
|
|
||||||
for (let y = 0; y < this.cubicmap.height; y++) {
|
|
||||||
for (let x = 0; x < this.cubicmap.width; x++) {
|
|
||||||
const pixelValR = this.mapPixels[((y * this.cubicmap.width + x) * 4)];
|
|
||||||
if ((pixelValR == 255) && // Collision: white pixel, only check R channel
|
|
||||||
(checkCollisionCircleRec(playerPos, playerRadius, new Rectangle(this.mapPosition.x - 0.5 + x * 1.0, this.mapPosition.z - 0.5 + y * 1.0, 1.0, 1.0)))) {
|
|
||||||
// Collision detected, reset camera position
|
|
||||||
this.camera.position = oldCamPos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
draw() {
|
|
||||||
super.draw();
|
|
||||||
beginMode3D(this.camera);
|
|
||||||
drawModel(this.model, this.mapPosition, 1.0, WHITE); // Draw maze map
|
|
||||||
endMode3D();
|
|
||||||
drawTextureEx(this.cubicmap, new Vector2(getScreenWidth() - this.cubicmap.width * 4.0 - 20, 20.0), 0.0, 4.0, WHITE);
|
|
||||||
drawRectangleLines(getScreenWidth() - this.cubicmap.width * 4 - 20, 20, this.cubicmap.width * 4, this.cubicmap.height * 4, GREEN);
|
|
||||||
// Draw player position radar
|
|
||||||
drawRectangle(getScreenWidth() - this.cubicmap.width * 4 - 20 + this.playerCellX * 4, 20 + this.playerCellY * 4, 4, 4, RED);
|
|
||||||
drawFPS(10, 10);
|
|
||||||
}
|
|
||||||
unload() {
|
|
||||||
enableCursor();
|
|
||||||
unloadTexture(this.cubicmap); // Unload cubicmap texture
|
|
||||||
unloadTexture(this.texture); // Unload map texture
|
|
||||||
unloadModel(this.model); // Unload map model
|
|
||||||
super.unload();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exports.FirstPersonMaze = FirstPersonMaze;
|
|
||||||
|
|
||||||
|
|
||||||
/***/ }),
|
|
||||||
|
|
||||||
/***/ "./src/game.ts":
|
/***/ "./src/game.ts":
|
||||||
/*!*********************!*\
|
/*!*********************!*\
|
||||||
!*** ./src/game.ts ***!
|
!*** ./src/game.ts ***!
|
||||||
\*********************/
|
\*********************/
|
||||||
/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
|
/***/ ((__unused_webpack_module, exports) => {
|
||||||
|
|
||||||
|
|
||||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||||
exports.Game = void 0;
|
exports.Game = void 0;
|
||||||
const systems_1 = __webpack_require__(/*! ./systems */ "./src/systems.ts");
|
|
||||||
class Game {
|
class Game {
|
||||||
constructor(width, height, title) {
|
constructor(width, height, title) {
|
||||||
this.width = width;
|
this.width = width;
|
||||||
this.height = height;
|
this.height = height;
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.clearColor = RAYWHITE;
|
this.clearColor = RAYWHITE;
|
||||||
this.systemHost = new systems_1.SystemHost();
|
|
||||||
this.quit = false;
|
this.quit = false;
|
||||||
}
|
}
|
||||||
run() {
|
run() {
|
||||||
initWindow(this.width, this.height, this.title);
|
initWindow(this.width, this.height, this.title);
|
||||||
setTargetFPS(60);
|
setTargetFPS(60);
|
||||||
|
this.load();
|
||||||
while (!(this.quit = windowShouldClose())) {
|
while (!(this.quit = windowShouldClose())) {
|
||||||
this.systemHost.loadSystems();
|
this.update();
|
||||||
this.systemHost.updateSystems();
|
|
||||||
beginDrawing();
|
beginDrawing();
|
||||||
clearBackground(this.clearColor);
|
clearBackground(this.clearColor);
|
||||||
this.systemHost.drawSystems();
|
this.draw();
|
||||||
this.systemHost.unloadSystems();
|
|
||||||
endDrawing();
|
endDrawing();
|
||||||
}
|
}
|
||||||
this.systemHost.requestShutdown();
|
this.unload();
|
||||||
closeWindow();
|
closeWindow();
|
||||||
}
|
}
|
||||||
addSystem(system) {
|
|
||||||
return this.systemHost.addSystem(system);
|
|
||||||
}
|
|
||||||
removeSystem(id) {
|
|
||||||
return this.systemHost.removeSystem(id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
exports.Game = Game;
|
exports.Game = Game;
|
||||||
|
|
||||||
|
|
||||||
/***/ }),
|
|
||||||
|
|
||||||
/***/ "./src/systems.ts":
|
|
||||||
/*!************************!*\
|
|
||||||
!*** ./src/systems.ts ***!
|
|
||||||
\************************/
|
|
||||||
/***/ ((__unused_webpack_module, exports) => {
|
|
||||||
|
|
||||||
|
|
||||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
||||||
exports.SystemHost = exports.SystemContainer = exports.SystemBase = void 0;
|
|
||||||
class SystemBase {
|
|
||||||
constructor() {
|
|
||||||
this.isFinished = false;
|
|
||||||
}
|
|
||||||
load() {
|
|
||||||
this.promise = new Promise((res, rej) => this.complete = res);
|
|
||||||
}
|
|
||||||
unload() {
|
|
||||||
this.complete();
|
|
||||||
}
|
|
||||||
draw() { }
|
|
||||||
update(dt) { }
|
|
||||||
stop() {
|
|
||||||
this.isFinished = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exports.SystemBase = SystemBase;
|
|
||||||
class SystemContainer extends SystemBase {
|
|
||||||
constructor() {
|
|
||||||
super(...arguments);
|
|
||||||
this.systemHost = new SystemHost();
|
|
||||||
}
|
|
||||||
update(dt) {
|
|
||||||
this.systemHost.loadSystems();
|
|
||||||
this.systemHost.updateSystems();
|
|
||||||
}
|
|
||||||
draw() {
|
|
||||||
this.systemHost.drawSystems();
|
|
||||||
this.systemHost.unloadSystems();
|
|
||||||
}
|
|
||||||
unload() {
|
|
||||||
this.systemHost.requestShutdown();
|
|
||||||
super.unload();
|
|
||||||
}
|
|
||||||
addSystem(system) {
|
|
||||||
return this.systemHost.addSystem(system);
|
|
||||||
}
|
|
||||||
removeSystem(id) {
|
|
||||||
return this.systemHost.removeSystem(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exports.SystemContainer = SystemContainer;
|
|
||||||
class SystemHost {
|
|
||||||
constructor() {
|
|
||||||
this.systems = new Map();
|
|
||||||
this.unloadQueue = new Set();
|
|
||||||
this.loadQueue = new Set();
|
|
||||||
this.updateOrder = [];
|
|
||||||
this.updateOrderRev = [];
|
|
||||||
this.systemPrio = 0;
|
|
||||||
}
|
|
||||||
addSystem(system) {
|
|
||||||
const id = this.systemPrio++;
|
|
||||||
this.systems.set(id, system);
|
|
||||||
this.loadQueue.add(id);
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
removeSystem(id) {
|
|
||||||
if (this.systems.has(id)) {
|
|
||||||
this.unloadQueue.add(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
refreshUpdateOrder() {
|
|
||||||
this.updateOrder = Array.from(this.systems.keys()).sort((a, b) => a - b);
|
|
||||||
this.updateOrderRev = this.updateOrder.reverse();
|
|
||||||
}
|
|
||||||
loadSystems() {
|
|
||||||
if (this.loadQueue.size === 0)
|
|
||||||
return;
|
|
||||||
this.refreshUpdateOrder();
|
|
||||||
for (const id of this.updateOrder) {
|
|
||||||
if (this.loadQueue.has(id))
|
|
||||||
this.systems.get(id)?.load();
|
|
||||||
}
|
|
||||||
this.loadQueue.clear();
|
|
||||||
}
|
|
||||||
unloadSystems() {
|
|
||||||
if (this.unloadQueue.size === 0)
|
|
||||||
return;
|
|
||||||
for (const id of this.updateOrderRev) {
|
|
||||||
if (this.unloadQueue.has(id)) {
|
|
||||||
this.systems.get(id)?.unload();
|
|
||||||
this.systems.delete(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.refreshUpdateOrder();
|
|
||||||
this.unloadQueue.clear();
|
|
||||||
}
|
|
||||||
updateSystems() {
|
|
||||||
for (const id of this.updateOrder) {
|
|
||||||
this.systems.get(id)?.update(getFrameTime());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
drawSystems() {
|
|
||||||
for (const id of this.updateOrder) {
|
|
||||||
const sys = this.systems.get(id);
|
|
||||||
sys?.draw();
|
|
||||||
if (sys?.isFinished)
|
|
||||||
this.unloadQueue.add(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
requestShutdown() {
|
|
||||||
for (const id of this.updateOrderRev) {
|
|
||||||
this.systems.get(id)?.unload();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exports.SystemHost = SystemHost;
|
|
||||||
|
|
||||||
|
|
||||||
/***/ })
|
/***/ })
|
||||||
|
|
||||||
/******/ });
|
/******/ });
|
||||||
|
@ -321,44 +75,22 @@ var exports = __webpack_exports__;
|
||||||
\**********************/
|
\**********************/
|
||||||
|
|
||||||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||||||
const examples_1 = __webpack_require__(/*! ./examples */ "./src/examples.ts");
|
|
||||||
const game_1 = __webpack_require__(/*! ./game */ "./src/game.ts");
|
const game_1 = __webpack_require__(/*! ./game */ "./src/game.ts");
|
||||||
const systems_1 = __webpack_require__(/*! ./systems */ "./src/systems.ts");
|
class MyGame extends game_1.Game {
|
||||||
class MySys extends systems_1.SystemBase {
|
|
||||||
load() {
|
|
||||||
super.load();
|
|
||||||
this.mesh = new Mesh();
|
|
||||||
this.mesh.vertexCount = 3;
|
|
||||||
this.mesh.triangleCount = 1;
|
|
||||||
const v1 = new Vector3(400, 0, 0);
|
|
||||||
const v2 = new Vector3(0, 450, 0);
|
|
||||||
const v3 = new Vector3(800, 450, 0);
|
|
||||||
this.mesh.indices = new Uint16Array([0, 1, 2]).buffer;
|
|
||||||
this.mesh.vertices = new Float32Array([
|
|
||||||
v1.x, v1.y, v1.z,
|
|
||||||
v2.x, v2.y, v2.z,
|
|
||||||
v3.x, v3.y, v3.z
|
|
||||||
]).buffer;
|
|
||||||
// If your forget to upload to GPU draw will segfault
|
|
||||||
uploadMesh(this.mesh, false);
|
|
||||||
this.material = loadMaterialDefault();
|
|
||||||
this.matrix = matrixIdentity();
|
|
||||||
}
|
|
||||||
update(dt) {
|
|
||||||
this.matrix = matrixRotateZ(getTime());
|
|
||||||
}
|
|
||||||
draw() {
|
draw() {
|
||||||
drawMesh(this.mesh, this.material, this.matrix);
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
update() {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
load() {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
}
|
}
|
||||||
unload() {
|
unload() {
|
||||||
super.unload();
|
throw new Error("Method not implemented.");
|
||||||
unloadMaterial(this.material);
|
|
||||||
unloadMesh(this.mesh);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const game = new game_1.Game(800, 450, "Typescript Game");
|
const game = new MyGame(800, 450, "Typescript Game");
|
||||||
game.addSystem(new examples_1.GameController());
|
|
||||||
game.addSystem(new MySys());
|
|
||||||
game.run();
|
game.run();
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
node_modules/
|
node_modules/
|
||||||
|
main.js
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { HasColor } from "./entity"
|
||||||
|
import { makeUpdateablePromise } from "./game"
|
||||||
|
|
||||||
|
export type easeFunc = (t: number, a: number, b: number, d: number) => number
|
||||||
|
|
||||||
|
export const interpolate = (a: number, b: number, d: number, setter: (v: number) => void, fn: easeFunc) => {
|
||||||
|
const start = getTime()
|
||||||
|
return makeUpdateablePromise(() => {
|
||||||
|
const cur = getTime()-start
|
||||||
|
if(cur < d){
|
||||||
|
setter(fn(cur, a, b, d))
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
setter(b)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const wait = (time: number) => {
|
||||||
|
const start = getTime()
|
||||||
|
return makeUpdateablePromise(() => {
|
||||||
|
const cur = getTime()-start
|
||||||
|
if(cur < time){
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fadeIn = (c: HasColor, time: number, easeFunc = easeLinearNone) => interpolate(0, 1, time, (v) => c.color = fade(c.color, v), easeFunc)
|
||||||
|
export const fadeOut = (c: HasColor, time: number, easeFunc = easeLinearNone) => interpolate(0, 1, time, (v) => c.color = fade(c.color, 1-v), easeFunc)
|
|
@ -0,0 +1,44 @@
|
||||||
|
|
||||||
|
export interface HasIdentity {
|
||||||
|
id: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Drawable<T> {
|
||||||
|
draw: (entity: T) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Updatable<T> {
|
||||||
|
update: (entity: T) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HasResources<T> {
|
||||||
|
load: (entity: T) => void
|
||||||
|
unload: (entity: T) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HasPosition {
|
||||||
|
position: Vector2
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HasColor {
|
||||||
|
color: Color
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EntityOf<T> = HasIdentity & Partial<HasResources<T>> & Partial<Updatable<T>> & Partial<Drawable<T>> & T
|
||||||
|
|
||||||
|
let ID = 0
|
||||||
|
export const makeEntity = () => ({
|
||||||
|
id: ID++
|
||||||
|
})
|
||||||
|
|
||||||
|
export const makePosition = (x = 0, y = 0) => ({
|
||||||
|
position: new Vector2(x,y)
|
||||||
|
})
|
||||||
|
|
||||||
|
export const makeColorRgb = (r = 255, g = 255, b = 255, a = 255) => ({
|
||||||
|
color: new Color(r,g,b,a)
|
||||||
|
})
|
||||||
|
|
||||||
|
export const makeColorClone = (c = WHITE) => ({
|
||||||
|
color: new Color(c.r,c.g,c.b,c.a)
|
||||||
|
})
|
|
@ -1,135 +0,0 @@
|
||||||
import { SystemBase, SystemContainer } from "./systems";
|
|
||||||
|
|
||||||
export class GameController extends SystemContainer {
|
|
||||||
private currentId!: number
|
|
||||||
private currentIndex!: number
|
|
||||||
private systems = [
|
|
||||||
new BasicWindow(),
|
|
||||||
new FirstPersonMaze()
|
|
||||||
]
|
|
||||||
|
|
||||||
load(): void {
|
|
||||||
super.load()
|
|
||||||
this.currentIndex = 0
|
|
||||||
this.currentId = this.addSystem(this.systems[this.currentIndex])
|
|
||||||
}
|
|
||||||
|
|
||||||
update(dt: number): void {
|
|
||||||
if(isKeyPressed(KEY_RIGHT)){
|
|
||||||
this.removeSystem(this.currentId)
|
|
||||||
this.currentIndex = (this.currentIndex+1)%this.systems.length
|
|
||||||
this.currentId = this.addSystem(this.systems[this.currentIndex])
|
|
||||||
}
|
|
||||||
super.update(dt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class BasicWindow extends SystemBase {
|
|
||||||
draw(): void {
|
|
||||||
super.draw()
|
|
||||||
drawText("Congrats! You created your first window!", 190, 200, 20, LIGHTGRAY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class FirstPersonMaze extends SystemBase {
|
|
||||||
|
|
||||||
private camera!: Camera3D;
|
|
||||||
private playerCellX!: number;
|
|
||||||
private playerCellY!: number;
|
|
||||||
private cubicmap!: Texture;
|
|
||||||
private texture!: Texture;
|
|
||||||
private mapPixels!: Uint8Array;
|
|
||||||
private model!: Model;
|
|
||||||
private mapPosition!: Vector3;
|
|
||||||
|
|
||||||
load(): void {
|
|
||||||
super.load()
|
|
||||||
// Define the camera to look into our 3d world
|
|
||||||
this.camera = new Camera3D(new Vector3(0.2, 0.4, 0.2), new Vector3(0.185, 0.4, 0.0), new Vector3(0, 1, 0), 45, CAMERA_PERSPECTIVE);
|
|
||||||
const position = new Vector3(0, 0, 0); // Set model position
|
|
||||||
|
|
||||||
const imMap = loadImage("assets/cubicmap.png"); // Load cubicmap image (RAM)
|
|
||||||
this.cubicmap = loadTextureFromImage(imMap); // Convert image to texture to display (VRAM)
|
|
||||||
const mesh = genMeshCubicmap(imMap, new Vector3(1.0, 1.0, 1.0));
|
|
||||||
this.model = loadModelFromMesh(mesh);
|
|
||||||
|
|
||||||
// NOTE: By default each cube is mapped to one part of texture atlas
|
|
||||||
this.texture = loadTexture("assets/cubicmap_atlas.png"); // Load map texture
|
|
||||||
|
|
||||||
//model.materials[0].maps[MATERIAL_MAP_DIFFUSE].texture = texture; // Set map diffuse texture
|
|
||||||
const mat = loadMaterialDefault();
|
|
||||||
setMaterialTexture(mat, MATERIAL_MAP_DIFFUSE, this.texture);
|
|
||||||
setModelMaterial(this.model, 0, mat);
|
|
||||||
|
|
||||||
// Get map image data to be used for collision detection
|
|
||||||
this.mapPixels = new Uint8Array(loadImageColors(imMap));
|
|
||||||
unloadImage(imMap); // Unload image from RAM
|
|
||||||
|
|
||||||
this.mapPosition = new Vector3(-16.0, 0.0, -8.0); // Set model position
|
|
||||||
|
|
||||||
disableCursor();
|
|
||||||
}
|
|
||||||
|
|
||||||
update(dt: number): void {
|
|
||||||
super.update(dt);
|
|
||||||
let oldCamPos = this.camera.position; // Store old camera position
|
|
||||||
|
|
||||||
updateCamera(this.camera, CAMERA_FIRST_PERSON);
|
|
||||||
|
|
||||||
// Check player collision (we simplify to 2D collision detection)
|
|
||||||
const playerPos = new Vector2(this.camera.position.x, this.camera.position.z);
|
|
||||||
const playerRadius = 0.1; // Collision radius (player is modelled as a cilinder for collision)
|
|
||||||
|
|
||||||
this.playerCellX = Math.floor(playerPos.x - this.mapPosition.x + 0.5);
|
|
||||||
this.playerCellY = Math.floor(playerPos.y - this.mapPosition.z + 0.5);
|
|
||||||
|
|
||||||
// Out-of-limits security check
|
|
||||||
if (this.playerCellX < 0)
|
|
||||||
this.playerCellX = 0;
|
|
||||||
else if (this.playerCellX >= this.cubicmap.width)
|
|
||||||
this.playerCellX = this.cubicmap.width - 1;
|
|
||||||
|
|
||||||
if (this.playerCellY < 0)
|
|
||||||
this.playerCellY = 0;
|
|
||||||
else if (this.playerCellY >= this.cubicmap.height)
|
|
||||||
this.playerCellY = this.cubicmap.height - 1;
|
|
||||||
|
|
||||||
// Check map collisions using image data and player position
|
|
||||||
// TODO: Improvement: Just check player surrounding cells for collision
|
|
||||||
for (let y = 0; y < this.cubicmap.height; y++) {
|
|
||||||
for (let x = 0; x < this.cubicmap.width; x++) {
|
|
||||||
const pixelValR = this.mapPixels[((y * this.cubicmap.width + x) * 4)];
|
|
||||||
if ((pixelValR == 255) && // Collision: white pixel, only check R channel
|
|
||||||
(checkCollisionCircleRec(playerPos, playerRadius, new Rectangle(
|
|
||||||
this.mapPosition.x - 0.5 + x * 1.0,
|
|
||||||
this.mapPosition.z - 0.5 + y * 1.0, 1.0, 1.0)))) {
|
|
||||||
// Collision detected, reset camera position
|
|
||||||
this.camera.position = oldCamPos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
draw(): void {
|
|
||||||
super.draw()
|
|
||||||
beginMode3D(this.camera);
|
|
||||||
drawModel(this.model, this.mapPosition, 1.0, WHITE); // Draw maze map
|
|
||||||
endMode3D();
|
|
||||||
|
|
||||||
drawTextureEx(this.cubicmap, new Vector2(getScreenWidth() - this.cubicmap.width * 4.0 - 20, 20.0), 0.0, 4.0, WHITE);
|
|
||||||
drawRectangleLines(getScreenWidth() - this.cubicmap.width * 4 - 20, 20, this.cubicmap.width * 4, this.cubicmap.height * 4, GREEN);
|
|
||||||
|
|
||||||
// Draw player position radar
|
|
||||||
drawRectangle(getScreenWidth() - this.cubicmap.width * 4 - 20 + this.playerCellX * 4, 20 + this.playerCellY * 4, 4, 4, RED);
|
|
||||||
|
|
||||||
drawFPS(10, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
unload(): void {
|
|
||||||
enableCursor();
|
|
||||||
unloadTexture(this.cubicmap); // Unload cubicmap texture
|
|
||||||
unloadTexture(this.texture); // Unload map texture
|
|
||||||
unloadModel(this.model); // Unload map model
|
|
||||||
super.unload()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +1,31 @@
|
||||||
import { System, SystemHost } from "./systems"
|
const promiseUpdateList: (()=>boolean)[] = []
|
||||||
|
|
||||||
export class Game {
|
const dispatchPromises = () => {
|
||||||
public clearColor = RAYWHITE
|
for (var i = promiseUpdateList.length - 1; i >= 0; i--) {
|
||||||
private systemHost = new SystemHost()
|
const finished = promiseUpdateList[i]()
|
||||||
|
if (finished) {
|
||||||
|
promiseUpdateList.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const makeUpdateablePromise = (updateFn: () => boolean) => {
|
||||||
|
let resFn: () => void
|
||||||
|
const promise = new Promise<void>((resolve, reject) => {
|
||||||
|
resFn = resolve
|
||||||
|
});
|
||||||
|
const update = () => {
|
||||||
|
const res = updateFn()
|
||||||
|
if(res) resFn()
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
promiseUpdateList.unshift(update)
|
||||||
|
return promise
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export abstract class Game {
|
||||||
|
public clearColor = BLACK
|
||||||
private quit = false
|
private quit = false
|
||||||
|
|
||||||
constructor(public readonly width: number,
|
constructor(public readonly width: number,
|
||||||
|
@ -13,26 +36,21 @@ export class Game {
|
||||||
public run(){
|
public run(){
|
||||||
initWindow(this.width,this.height,this.title)
|
initWindow(this.width,this.height,this.title)
|
||||||
setTargetFPS(60)
|
setTargetFPS(60)
|
||||||
|
this.load()
|
||||||
while(!(this.quit = windowShouldClose())){
|
while(!(this.quit = windowShouldClose())){
|
||||||
this.systemHost.loadSystems()
|
dispatchPromises()
|
||||||
this.systemHost.updateSystems()
|
this.update()
|
||||||
beginDrawing()
|
beginDrawing()
|
||||||
clearBackground(this.clearColor)
|
clearBackground(this.clearColor)
|
||||||
this.systemHost.drawSystems()
|
this.draw()
|
||||||
this.systemHost.unloadSystems()
|
|
||||||
endDrawing()
|
endDrawing()
|
||||||
}
|
}
|
||||||
this.systemHost.requestShutdown()
|
this.unload()
|
||||||
closeWindow()
|
closeWindow()
|
||||||
}
|
}
|
||||||
|
|
||||||
addSystem(system: System){
|
abstract draw(): void;
|
||||||
return this.systemHost.addSystem(system)
|
abstract update(): void;
|
||||||
}
|
abstract load(): void;
|
||||||
|
abstract unload(): void;
|
||||||
removeSystem(id: number){
|
|
||||||
return this.systemHost.removeSystem(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,46 +1,47 @@
|
||||||
import { GameController } from "./examples";
|
import { fadeIn, fadeOut, wait } from "./animation";
|
||||||
import { Game } from "./game";
|
import { EntityOf } from "./entity";
|
||||||
import { SystemBase, SystemContainer } from "./systems";
|
import { Game, makeUpdateablePromise } from "./game";
|
||||||
|
import { TextEntity, makeTextEntity } from "./text";
|
||||||
|
|
||||||
class MySys extends SystemBase {
|
class MyGame<T> extends Game {
|
||||||
mesh!: Mesh;
|
entities: EntityOf<T>[] = []
|
||||||
material!: Material
|
|
||||||
matrix!: Matrix
|
|
||||||
|
|
||||||
load(): void {
|
|
||||||
super.load()
|
|
||||||
this.mesh = new Mesh()
|
|
||||||
this.mesh.vertexCount = 3
|
|
||||||
this.mesh.triangleCount = 1
|
|
||||||
const v1 = new Vector3(400, 0, 0)
|
|
||||||
const v2 = new Vector3(0, 450, 0 )
|
|
||||||
const v3 = new Vector3(800, 450, 0)
|
|
||||||
this.mesh.indices = new Uint16Array([0,1,2]).buffer
|
|
||||||
this.mesh.vertices = new Float32Array([
|
|
||||||
v1.x, v1.y, v1.z,
|
|
||||||
v2.x, v2.y, v2.z,
|
|
||||||
v3.x, v3.y, v3.z
|
|
||||||
]).buffer
|
|
||||||
// If your forget to upload to GPU draw will segfault
|
|
||||||
uploadMesh(this.mesh, false)
|
|
||||||
this.material = loadMaterialDefault()
|
|
||||||
this.matrix = matrixIdentity()
|
|
||||||
}
|
|
||||||
update(dt: number): void {
|
|
||||||
this.matrix = matrixRotateZ(getTime())
|
|
||||||
}
|
|
||||||
draw(): void {
|
draw(): void {
|
||||||
drawMesh(this.mesh, this.material, this.matrix)
|
for (const entity of this.entities) {
|
||||||
|
if(entity.draw) entity.draw(entity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
update(): void {
|
||||||
|
for (const entity of this.entities) {
|
||||||
|
if(entity.update) entity.update(entity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
load(): void {
|
||||||
}
|
}
|
||||||
unload(): void {
|
unload(): void {
|
||||||
super.unload()
|
}
|
||||||
unloadMaterial(this.material)
|
|
||||||
unloadMesh(this.mesh)
|
addEntity(entity: EntityOf<T>){
|
||||||
|
this.entities.push(entity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const game = new MyGame<TextEntity>(800,450,"Typescript Game")
|
||||||
|
|
||||||
const game = new Game(800,450,"Typescript Game")
|
const main = async () => {
|
||||||
game.addSystem(new GameController())
|
const text = <TextEntity>{
|
||||||
game.addSystem(new MySys())
|
...makeTextEntity("Welcome to rayjs!"),
|
||||||
|
size: 80,
|
||||||
|
position: new Vector2(100, game.height/2 - 40)
|
||||||
|
}
|
||||||
|
game.addEntity(text)
|
||||||
|
await fadeIn(text, 2)
|
||||||
|
await wait(2)
|
||||||
|
await fadeOut(text, 2)
|
||||||
|
text.position.x += 75
|
||||||
|
text.text = "This Summer!"
|
||||||
|
await fadeIn(text, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
game.run()
|
game.run()
|
|
@ -1,122 +0,0 @@
|
||||||
export interface System {
|
|
||||||
isFinished: boolean;
|
|
||||||
load(): void;
|
|
||||||
unload(): void;
|
|
||||||
draw(): void;
|
|
||||||
update(dt: number): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export abstract class SystemBase implements System {
|
|
||||||
|
|
||||||
public promise!: Promise<void>
|
|
||||||
private complete!: () => void
|
|
||||||
public isFinished = false
|
|
||||||
|
|
||||||
load(): void {
|
|
||||||
this.promise = new Promise((res,rej) => this.complete = res)
|
|
||||||
}
|
|
||||||
|
|
||||||
unload(): void {
|
|
||||||
this.complete()
|
|
||||||
}
|
|
||||||
draw(): void {}
|
|
||||||
update(dt: number): void {}
|
|
||||||
stop(){
|
|
||||||
this.isFinished = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SystemContainer extends SystemBase {
|
|
||||||
private systemHost = new SystemHost()
|
|
||||||
|
|
||||||
update(dt: number): void {
|
|
||||||
this.systemHost.loadSystems()
|
|
||||||
this.systemHost.updateSystems()
|
|
||||||
}
|
|
||||||
|
|
||||||
draw(): void {
|
|
||||||
this.systemHost.drawSystems()
|
|
||||||
this.systemHost.unloadSystems()
|
|
||||||
}
|
|
||||||
|
|
||||||
unload(): void {
|
|
||||||
this.systemHost.requestShutdown()
|
|
||||||
super.unload()
|
|
||||||
}
|
|
||||||
|
|
||||||
addSystem(system: System){
|
|
||||||
return this.systemHost.addSystem(system)
|
|
||||||
}
|
|
||||||
|
|
||||||
removeSystem(id: number){
|
|
||||||
return this.systemHost.removeSystem(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SystemHost {
|
|
||||||
private systems = new Map<number,System>()
|
|
||||||
private unloadQueue = new Set<number>()
|
|
||||||
private loadQueue = new Set<number>()
|
|
||||||
private updateOrder: number[] = []
|
|
||||||
private updateOrderRev: number[] = []
|
|
||||||
private systemPrio = 0
|
|
||||||
|
|
||||||
public addSystem(system: System){
|
|
||||||
const id = this.systemPrio++
|
|
||||||
this.systems.set(id, system)
|
|
||||||
this.loadQueue.add(id)
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
|
|
||||||
public removeSystem(id: number){
|
|
||||||
if(this.systems.has(id)) {
|
|
||||||
this.unloadQueue.add(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private refreshUpdateOrder(){
|
|
||||||
this.updateOrder = Array.from(this.systems.keys()).sort((a, b) => a - b);
|
|
||||||
this.updateOrderRev = this.updateOrder.reverse()
|
|
||||||
}
|
|
||||||
|
|
||||||
public loadSystems(){
|
|
||||||
if(this.loadQueue.size === 0) return
|
|
||||||
this.refreshUpdateOrder()
|
|
||||||
for (const id of this.updateOrder) {
|
|
||||||
if(this.loadQueue.has(id)) this.systems.get(id)?.load()
|
|
||||||
}
|
|
||||||
this.loadQueue.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
public unloadSystems(){
|
|
||||||
if(this.unloadQueue.size === 0) return
|
|
||||||
for (const id of this.updateOrderRev) {
|
|
||||||
if(this.unloadQueue.has(id)) {
|
|
||||||
this.systems.get(id)?.unload()
|
|
||||||
this.systems.delete(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.refreshUpdateOrder()
|
|
||||||
this.unloadQueue.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
public updateSystems(){
|
|
||||||
for (const id of this.updateOrder) {
|
|
||||||
this.systems.get(id)?.update(getFrameTime())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public drawSystems(){
|
|
||||||
for (const id of this.updateOrder) {
|
|
||||||
const sys = this.systems.get(id)
|
|
||||||
sys?.draw()
|
|
||||||
if(sys?.isFinished) this.unloadQueue.add(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public requestShutdown(){
|
|
||||||
for (const id of this.updateOrderRev) {
|
|
||||||
this.systems.get(id)?.unload()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { EntityOf, HasColor, HasPosition, makeColorRgb, makeEntity, makePosition } from "./entity"
|
||||||
|
|
||||||
|
export interface Text extends HasPosition, HasColor {
|
||||||
|
text: string,
|
||||||
|
font: Font,
|
||||||
|
size: number,
|
||||||
|
spacing: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TextEntity extends EntityOf<Text> {
|
||||||
|
type: "text"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const makeText = (text = "", size = 20, font = null, spacing = 1) => (<Text>{
|
||||||
|
...makePosition(),
|
||||||
|
...makeColorRgb(),
|
||||||
|
text: text,
|
||||||
|
font: font === null ? getFontDefault() : loadFont(font),
|
||||||
|
size: size,
|
||||||
|
spacing: spacing
|
||||||
|
})
|
||||||
|
|
||||||
|
export const textDraw = (t: Text) => {
|
||||||
|
return drawTextEx(t.font, t.text, t.position, t.size, t.spacing, t.color);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const makeTextEntity = (text: string = "") => (<TextEntity>{
|
||||||
|
...makeEntity(),
|
||||||
|
...makeText(text),
|
||||||
|
type: "text",
|
||||||
|
draw: textDraw,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ module.exports = {
|
||||||
extensions: ['.tsx', '.ts', '.js'],
|
extensions: ['.tsx', '.ts', '.js'],
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
filename: 'ts_game.js',
|
filename: 'main.js',
|
||||||
path: path.resolve(__dirname, '..'),
|
path: path.resolve(__dirname, '.'),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue