generate uuids for entities; scaffolding for a server
This commit is contained in:
parent
2dc3120831
commit
732fe6f481
@ -7,15 +7,63 @@ import {
|
|||||||
Physics,
|
Physics,
|
||||||
Input,
|
Input,
|
||||||
Collision,
|
Collision,
|
||||||
|
MessageQueueProvider,
|
||||||
|
MessagePublisher,
|
||||||
|
NetworkUpdate,
|
||||||
} from "@engine/systems";
|
} from "@engine/systems";
|
||||||
|
|
||||||
|
class ClientSocketMessageQueueProvider implements MessageQueueProvider {
|
||||||
|
private socket: WebSocket;
|
||||||
|
private messages: any[];
|
||||||
|
|
||||||
|
constructor(socket: WebSocket) {
|
||||||
|
this.socket = socket;
|
||||||
|
this.messages = [];
|
||||||
|
|
||||||
|
this.socket.addEventListener("message", (e) => {
|
||||||
|
console.log(e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getNewMessages() {
|
||||||
|
return this.messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearMessages() {
|
||||||
|
this.messages = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ClientSocketMessagePublisher implements MessagePublisher {
|
||||||
|
private socket: WebSocket;
|
||||||
|
private messages: any[];
|
||||||
|
|
||||||
|
constructor(socket: WebSocket) {
|
||||||
|
this.socket = socket;
|
||||||
|
this.messages = [];
|
||||||
|
|
||||||
|
this.socket.addEventListener("message", (e) => {
|
||||||
|
console.log(e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addMessage(_message: any) {}
|
||||||
|
|
||||||
|
publish() {}
|
||||||
|
}
|
||||||
|
|
||||||
export class JumpStorm {
|
export class JumpStorm {
|
||||||
private game: Game;
|
private game: Game;
|
||||||
private socket: WebSocket;
|
|
||||||
|
|
||||||
constructor(ctx: CanvasRenderingContext2D) {
|
constructor(ctx: CanvasRenderingContext2D) {
|
||||||
this.game = new Game();
|
this.game = new Game();
|
||||||
this.socket = new WebSocket("ws://localhost:8080");
|
|
||||||
|
const socket = new WebSocket("ws://localhost:8080");
|
||||||
|
const clientSocketMessageQueueProvider =
|
||||||
|
new ClientSocketMessageQueueProvider(socket);
|
||||||
|
const clientSocketMessagePublisher = new ClientSocketMessagePublisher(
|
||||||
|
socket,
|
||||||
|
);
|
||||||
|
|
||||||
[
|
[
|
||||||
this.createInputSystem(),
|
this.createInputSystem(),
|
||||||
@ -23,6 +71,10 @@ export class JumpStorm {
|
|||||||
new Physics(),
|
new Physics(),
|
||||||
new Collision(),
|
new Collision(),
|
||||||
new WallBounds(ctx.canvas.width),
|
new WallBounds(ctx.canvas.width),
|
||||||
|
new NetworkUpdate(
|
||||||
|
clientSocketMessageQueueProvider,
|
||||||
|
clientSocketMessagePublisher,
|
||||||
|
),
|
||||||
new Render(ctx),
|
new Render(ctx),
|
||||||
].forEach((system) => this.game.addSystem(system));
|
].forEach((system) => this.game.addSystem(system));
|
||||||
|
|
||||||
@ -49,6 +101,7 @@ export class JumpStorm {
|
|||||||
inputSystem.keyPressed(e.key);
|
inputSystem.keyPressed(e.key);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener("keyup", (e) => inputSystem.keyReleased(e.key));
|
window.addEventListener("keyup", (e) => inputSystem.keyReleased(e.key));
|
||||||
|
|
||||||
return inputSystem;
|
return inputSystem;
|
||||||
|
@ -7,9 +7,9 @@ export class Game {
|
|||||||
private running: boolean;
|
private running: boolean;
|
||||||
private lastTimeStamp: number;
|
private lastTimeStamp: number;
|
||||||
|
|
||||||
public entities: Map<number, Entity>;
|
public entities: Map<string, Entity>;
|
||||||
public systems: Map<string, System>;
|
public systems: Map<string, System>;
|
||||||
public componentEntities: Map<string, Set<number>>;
|
public componentEntities: Map<string, Set<string>>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.lastTimeStamp = performance.now();
|
this.lastTimeStamp = performance.now();
|
||||||
@ -29,11 +29,11 @@ export class Game {
|
|||||||
this.entities.set(entity.id, entity);
|
this.entities.set(entity.id, entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getEntity(id: number): Entity | undefined {
|
public getEntity(id: string): Entity | undefined {
|
||||||
return this.entities.get(id);
|
return this.entities.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public removeEntity(id: number) {
|
public removeEntity(id: string) {
|
||||||
this.entities.delete(id);
|
this.entities.delete(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,7 +75,7 @@ export class Game {
|
|||||||
if (!this.componentEntities.has(component.name)) {
|
if (!this.componentEntities.has(component.name)) {
|
||||||
this.componentEntities.set(
|
this.componentEntities.set(
|
||||||
component.name,
|
component.name,
|
||||||
new Set<number>([entity.id]),
|
new Set<string>([entity.id]),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
import { Component, ComponentNames } from ".";
|
import { Component, ComponentNames } from ".";
|
||||||
|
|
||||||
export class NetworkUpdateable extends Component {
|
export class NetworkUpdateable extends Component {
|
||||||
constructor() {
|
public isPublish: boolean;
|
||||||
|
public isSubscribe: boolean;
|
||||||
|
|
||||||
|
constructor(isPublish: boolean, isSubscribe: boolean) {
|
||||||
super(ComponentNames.NetworkUpdateable);
|
super(ComponentNames.NetworkUpdateable);
|
||||||
|
|
||||||
|
this.isPublish = isPublish;
|
||||||
|
this.isSubscribe = isSubscribe;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
import type { Component } from "../components";
|
import type { Component } from "../components";
|
||||||
|
|
||||||
export abstract class Entity {
|
export abstract class Entity {
|
||||||
private static ID = 0;
|
public readonly id: string;
|
||||||
|
|
||||||
public readonly id: number;
|
|
||||||
public readonly components: Map<string, Component>;
|
public readonly components: Map<string, Component>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.id = Entity.ID++;
|
this.id = crypto.randomUUID();
|
||||||
this.components = new Map();
|
this.components = new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import type { Coord2D, Dimension2D } from "../interfaces";
|
import type { Coord2D, Dimension2D } from "../interfaces";
|
||||||
|
|
||||||
interface BoxedEntry {
|
interface BoxedEntry {
|
||||||
id: number;
|
id: string;
|
||||||
dimension: Dimension2D;
|
dimension: Dimension2D;
|
||||||
center: Coord2D;
|
center: Coord2D;
|
||||||
}
|
}
|
||||||
@ -72,8 +72,8 @@ export class QuadTree {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getNeighborIds(boxedEntry: BoxedEntry): number[] {
|
public getNeighborIds(boxedEntry: BoxedEntry): string[] {
|
||||||
const neighbors: number[] = this.objects.map(({ id }) => id);
|
const neighbors: string[] = this.objects.map(({ id }) => id);
|
||||||
|
|
||||||
if (this.hasChildren()) {
|
if (this.hasChildren()) {
|
||||||
this.getQuadrants(boxedEntry).forEach((quadrant) => {
|
this.getQuadrants(boxedEntry).forEach((quadrant) => {
|
||||||
@ -160,11 +160,7 @@ export class QuadTree {
|
|||||||
this.objects.forEach((boxedEntry) => {
|
this.objects.forEach((boxedEntry) => {
|
||||||
this.getQuadrants(boxedEntry).forEach((direction) => {
|
this.getQuadrants(boxedEntry).forEach((direction) => {
|
||||||
const quadrant = this.children.get(direction);
|
const quadrant = this.children.get(direction);
|
||||||
quadrant?.insert(
|
quadrant?.insert(boxedEntry);
|
||||||
boxedEntry.id,
|
|
||||||
boxedEntry.dimension,
|
|
||||||
boxedEntry.center,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,10 +1,42 @@
|
|||||||
import { System, SystemNames } from ".";
|
import { System, SystemNames } from ".";
|
||||||
import { Game } from "../Game";
|
import { Game } from "../Game";
|
||||||
|
import { ComponentNames, NetworkUpdateable } from "../components";
|
||||||
|
|
||||||
|
export interface MessageQueueProvider {
|
||||||
|
getNewMessages(): any[];
|
||||||
|
clearMessages(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MessagePublisher {
|
||||||
|
addMessage(message: any): void;
|
||||||
|
publish(): void;
|
||||||
|
}
|
||||||
|
|
||||||
export class NetworkUpdate extends System {
|
export class NetworkUpdate extends System {
|
||||||
constructor() {
|
private queueProvider: MessageQueueProvider;
|
||||||
|
private publisher: MessagePublisher;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
queueProvider: MessageQueueProvider,
|
||||||
|
publisher: MessagePublisher,
|
||||||
|
) {
|
||||||
super(SystemNames.NetworkUpdate);
|
super(SystemNames.NetworkUpdate);
|
||||||
|
|
||||||
|
this.queueProvider = queueProvider;
|
||||||
|
this.publisher = publisher;
|
||||||
}
|
}
|
||||||
|
|
||||||
public update(_dt: number, _game: Game) {}
|
public update(_dt: number, game: Game) {
|
||||||
|
const messages = this.queueProvider.getNewMessages();
|
||||||
|
this.queueProvider.clearMessages();
|
||||||
|
|
||||||
|
game.forEachEntityWithComponent(
|
||||||
|
ComponentNames.NetworkUpdateable,
|
||||||
|
(entity) => {
|
||||||
|
const networkUpdateComponent = entity.getComponent<NetworkUpdateable>(
|
||||||
|
ComponentNames.NetworkUpdateable,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,37 +1,60 @@
|
|||||||
import { Game } from "../../engine/Game";
|
import { Game } from "../../engine/Game";
|
||||||
import { Floor, Player } from "../../engine/entities";
|
import { Floor, Player } from "../../engine/entities";
|
||||||
import { WallBounds, Physics, Collision } from "../../engine/systems";
|
import {
|
||||||
|
WallBounds,
|
||||||
|
Physics,
|
||||||
|
Collision,
|
||||||
|
MessageQueueProvider,
|
||||||
|
MessagePublisher,
|
||||||
|
} from "../../engine/systems";
|
||||||
import { Miscellaneous } from "../../engine/config";
|
import { Miscellaneous } from "../../engine/config";
|
||||||
|
|
||||||
const TICK_RATE = 60 / 1000;
|
const TICK_RATE = 60 / 1000;
|
||||||
|
|
||||||
const game = new Game();
|
class Server {
|
||||||
|
private server: any;
|
||||||
|
private game: Game;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.game = new Game();
|
||||||
|
|
||||||
[
|
[
|
||||||
new Physics(),
|
new Physics(),
|
||||||
new Collision({ width: Miscellaneous.WIDTH, height: Miscellaneous.HEIGHT }),
|
new Collision({
|
||||||
|
width: Miscellaneous.WIDTH,
|
||||||
|
height: Miscellaneous.HEIGHT,
|
||||||
|
}),
|
||||||
new WallBounds(Miscellaneous.WIDTH),
|
new WallBounds(Miscellaneous.WIDTH),
|
||||||
].forEach((system) => game.addSystem(system));
|
].forEach((system) => this.game.addSystem(system));
|
||||||
|
|
||||||
[new Floor(160), new Player()].forEach((entity) => game.addEntity(entity));
|
[new Floor(160), new Player()].forEach((entity) =>
|
||||||
|
this.game.addEntity(entity),
|
||||||
|
);
|
||||||
|
|
||||||
game.start();
|
this.game.start();
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
game.doGameLoop(performance.now());
|
this.game.doGameLoop(performance.now());
|
||||||
}, TICK_RATE);
|
}, TICK_RATE);
|
||||||
|
|
||||||
const server = Bun.serve<>({
|
this.server = Bun.serve<any>({
|
||||||
port: 8080,
|
|
||||||
fetch(req, server) {
|
|
||||||
server.upgrade(req, {
|
|
||||||
data: {},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
websocket: {
|
websocket: {
|
||||||
// handler called when a message is received
|
open(ws) {
|
||||||
async message(ws, message) {
|
ws.subscribe("the-group-chat");
|
||||||
console.log(`Received ${message}`);
|
ws.publish("the-group-chat", msg);
|
||||||
|
},
|
||||||
|
message(ws, message) {
|
||||||
|
// this is a group chat
|
||||||
|
// so the server re-broadcasts incoming message to everyone
|
||||||
|
ws.publish("the-group-chat", `${ws.data.username}: ${message}`);
|
||||||
|
},
|
||||||
|
close(ws) {
|
||||||
|
const msg = `${ws.data.username} has left the chat`;
|
||||||
|
ws.unsubscribe("the-group-chat");
|
||||||
|
ws.publish("the-group-chat", msg);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
console.log(`Listening on localhost:${server.port}`);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new Server();
|
||||||
|
@ -1,21 +1,28 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"lib": ["ESNext"],
|
// add Bun type definitions
|
||||||
|
"types": ["bun-types"],
|
||||||
|
|
||||||
|
// enable latest features
|
||||||
|
"lib": ["esnext"],
|
||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
"target": "esnext",
|
"target": "esnext",
|
||||||
|
|
||||||
|
// if TS 5.x+
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"moduleDetection": "force",
|
|
||||||
"allowImportingTsExtensions": true,
|
|
||||||
"strict": true,
|
|
||||||
"downlevelIteration": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"jsx": "preserve",
|
|
||||||
"allowSyntheticDefaultImports": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"allowJs": true,
|
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"types": [
|
"allowImportingTsExtensions": true,
|
||||||
"bun-types" // add Bun global
|
"moduleDetection": "force",
|
||||||
]
|
// if TS 4.x or earlier
|
||||||
|
"moduleResolution": "nodenext",
|
||||||
|
|
||||||
|
"jsx": "react-jsx", // support JSX
|
||||||
|
"allowJs": true, // allow importing `.js` from `.ts`
|
||||||
|
"esModuleInterop": true, // allow default imports for CommonJS modules
|
||||||
|
|
||||||
|
// best practices
|
||||||
|
"strict": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"skipLibCheck": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user