diff --git a/.changeset/rich-moose-guess.md b/.changeset/rich-moose-guess.md new file mode 100644 index 0000000..2772b4e --- /dev/null +++ b/.changeset/rich-moose-guess.md @@ -0,0 +1,5 @@ +--- +'@tedengine/ted': minor +--- + +Merge world and level together diff --git a/packages/ted/src/actor-components/scene-component.ts b/packages/ted/src/actor-components/scene-component.ts index ffd7692..123562c 100644 --- a/packages/ted/src/actor-components/scene-component.ts +++ b/packages/ted/src/actor-components/scene-component.ts @@ -76,11 +76,11 @@ export default class TSceneComponent extends TActorComponent { public applyCentralForce(force: vec3) { if (!this.collider) return; - this.actor.level?.world?.applyCentralForce(this, force); + this.actor?.world?.applyCentralForce(this, force); } public applyCentralImpulse(impulse: vec3) { if (!this.collider) return; - this.actor.level?.world?.applyCentralImpulse(this, impulse); + this.actor?.world?.applyCentralImpulse(this, impulse); } } diff --git a/packages/ted/src/core/actor.ts b/packages/ted/src/core/actor.ts index d8db05c..9c855a0 100644 --- a/packages/ted/src/core/actor.ts +++ b/packages/ted/src/core/actor.ts @@ -3,7 +3,7 @@ import type TActorComponent from '../actor-components/actor-component'; import TSceneComponent from '../actor-components/scene-component'; import type TEngine from '../engine/engine'; import type { TSerializedRenderTask } from '../renderer/frame-params'; -import type TLevel from './level'; +import type TWorld from './world'; export interface TActorWithOnUpdate extends TActor { onUpdate(engine: TEngine, delta: number): Promise; @@ -14,7 +14,7 @@ const hasOnUpdate = (state: TActor): state is TActorWithOnUpdate => export default class TActor { public uuid: string = uuidv4(); - public level?: TLevel; + public world?: TWorld; /** * List of components that belong to this actor diff --git a/packages/ted/src/core/game-state.ts b/packages/ted/src/core/game-state.ts index d62e7a2..047d0a4 100644 --- a/packages/ted/src/core/game-state.ts +++ b/packages/ted/src/core/game-state.ts @@ -1,7 +1,7 @@ import type { ICamera } from '../cameras/camera'; import type TEngine from '../engine/engine'; import type TActor from './actor'; -import TLevel from './level'; +import TWorld from './world'; export interface TGameStateWithOnUpdate extends TGameState { onUpdate(engine: TEngine, delta: number): Promise; @@ -40,18 +40,18 @@ const hasOnLeave = (state: TGameState): state is TGameStateWithOnLeave => export default class TGameState { public created = false; - public level?: TLevel; + public world?: TWorld; public activeCamera?: ICamera; /** - * Adds actor to the level in this game state. + * Adds actor to the world in this game state. * This is here for convience. * * @param actor */ public addActor(actor: TActor): void { - this.level?.addActor(actor); + this.world?.addActor(actor); } /** @@ -64,7 +64,7 @@ export default class TGameState { * @hidden */ public async update(engine: TEngine, delta: number): Promise { - await this.level?.update(engine, delta); + await this.world?.update(engine, delta); if (hasOnUpdate(this)) { await this.onUpdate(engine, delta); @@ -72,7 +72,7 @@ export default class TGameState { } public getRenderTasks() { - return this.level?.getRenderTasks() || []; + return this.world?.getRenderTasks() || []; } /** @@ -82,9 +82,9 @@ export default class TGameState { * @hidden */ public async create(engine: TEngine) { - this.level = new TLevel(engine); + this.world = new TWorld(engine); - await this.level.load(); + await this.world.create(); if (hasOnCreate(this)) { await this.onCreate(engine); @@ -98,7 +98,7 @@ export default class TGameState { * @hidden */ public async enter(engine: TEngine, ...args: any[]) { - this.level?.start(); + this.world?.start(); if (hasOnEnter(this)) { await this.onEnter(engine, ...args); @@ -112,7 +112,7 @@ export default class TGameState { * @hidden */ public async leave(engine: TEngine) { - this.level?.pause(); + this.world?.pause(); if (hasOnLeave(this)) { await this.onLeave(engine); @@ -132,9 +132,9 @@ export default class TGameState { } /** - * Currently only calls destroy on the level which stops the physics worker + * Currently only calls destroy on the world which stops the physics worker */ public async destroy() { - this.level?.destroy(); + this.world?.destroy(); } } diff --git a/packages/ted/src/core/level.ts b/packages/ted/src/core/level.ts deleted file mode 100644 index 0d45291..0000000 --- a/packages/ted/src/core/level.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { quat, vec3 } from 'gl-matrix'; -import type TEngine from '../engine/engine'; -import type { TPhysicsBody } from '../physics/worker/physics-world'; -import TWorld from '../physics/world'; -import type { TSerializedRenderTask } from '../renderer/frame-params'; -import type TActor from './actor'; - -export default class TLevel { - public actors: TActor[] = []; - public world?: TWorld; - - private paused = false; - - private updateResolve?: () => void; - private lastDelta = 0; - - constructor(private engine: TEngine) {} - - public async load(): Promise { - this.world = new TWorld(this); - await this.world.create({ - enableGravity: true, - defaultCollisionClass: 'Solid', - collisionClasses: [ - { name: 'Solid' }, - { name: 'NoCollide', ignores: ['Solid'] }, - ], - }); - } - - /** - * Adds actor to the level - * - * @param actor - */ - public addActor(actor: TActor): void { - actor.level = this; - this.actors.push(actor); - - this.world?.registerActor(actor); - } - - /** - * Called every frame with delta and triggers update on all actors - */ - public update(engine: TEngine, delta: number): Promise { - return new Promise((resolve) => { - if (this.paused) { - resolve(); - } - - this.lastDelta = delta; - this.updateResolve = resolve; - - this.world?.step(delta); - }); - } - - public getRenderTasks(): TSerializedRenderTask[] { - const tasks: TSerializedRenderTask[] = []; - - for (const actor of this.actors) { - tasks.push(...actor.getRenderTasks()); - } - - return tasks; - } - - /** - * Called when a state is entered so the level updates should continue - */ - public start() { - this.paused = false; - } - - /** - * Called when a state is moved out of the active state so level updates should stop - */ - public pause() { - this.paused = true; - } - - public onPhysicsUpdate(worldState: TPhysicsBody[]) { - for (const obj of worldState) { - // Find the actor with root component with this uuid - for (const actor of this.actors) { - if (actor.rootComponent.uuid === obj.uuid) { - actor.rootComponent.transform.translation = vec3.fromValues( - ...obj.translation - ); - actor.rootComponent.transform.rotation = quat.fromValues( - ...obj.rotation - ); - - break; - } - } - } - - for (const actor of this.actors) { - actor.update(this.engine, this.lastDelta); - } - - this.updateResolve?.(); - } - - public destroy() { - this.world?.destroy(); - } -} diff --git a/packages/ted/src/physics/world.ts b/packages/ted/src/core/world.ts similarity index 51% rename from packages/ted/src/physics/world.ts rename to packages/ted/src/core/world.ts index 03cd1f0..b6d0d47 100644 --- a/packages/ted/src/physics/world.ts +++ b/packages/ted/src/core/world.ts @@ -1,7 +1,8 @@ -import type { vec3 } from 'gl-matrix'; -import type TSceneComponent from '../actor-components/scene-component'; -import type TActor from '../core/actor'; -import type TLevel from '../core/level'; +import { quat, vec3 } from 'gl-matrix'; +import type TEngine from '../engine/engine'; +import type { TPhysicsBody } from '../physics/worker/physics-world'; +import type { TSerializedRenderTask } from '../renderer/frame-params'; +import type TActor from './actor'; import type { TPhysicsInMessageRegisterBody, TPhysicsInMessageSimulateStep, @@ -9,8 +10,9 @@ import type { TPhysicsInMessageWorldSetup, TPhysicsInMessageApplyCentralForce, TPhysicsInMessageApplyCentralImpulse, -} from './worker/messages'; -import { TPhysicsMessageTypes } from './worker/messages'; +} from '../physics/worker/messages'; +import { TPhysicsMessageTypes } from '../physics/worker/messages'; +import type TSceneComponent from '../actor-components/scene-component'; export interface TWorldConfig { enableGravity: boolean; @@ -24,33 +26,51 @@ export interface TCollisionClass { } export default class TWorld { + public actors: TActor[] = []; + private worker?: Worker; - private config?: TWorldConfig; + private config: TWorldConfig = { + enableGravity: true, + defaultCollisionClass: 'Solid', + collisionClasses: [ + { name: 'Solid' }, + { name: 'NoCollide', ignores: ['Solid'] }, + ], + }; private workerPort?: MessagePort; - constructor(private level: TLevel) {} + private paused = false; + + private updateResolve?: () => void; + private lastDelta = 0; private onCreatedResolve?: () => void; - public create(config: TWorldConfig): Promise { + constructor(private engine: TEngine) {} + + public async create(): Promise { return new Promise((resolve) => { - this.config = config; this.onCreatedResolve = resolve; - this.worker = new Worker(new URL('./worker/worker.ts', import.meta.url)); + this.worker = new Worker( + new URL('../physics/worker/worker.ts', import.meta.url) + ); this.worker.onmessage = this.onMessage.bind(this); }); } - public step(delta: number) { - const message: TPhysicsInMessageSimulateStep = { - type: TPhysicsMessageTypes.SIMULATE_STEP, - delta, - }; + /** + * Adds actor to the world + * + * @param actor + */ + public addActor(actor: TActor): void { + actor.world = this; + this.actors.push(actor); - this.workerPort?.postMessage(message); + this.registerActorWithPhysicsWorker(actor); } - public registerActor(actor: TActor) { + private registerActorWithPhysicsWorker(actor: TActor) { // Register only the root component const component = actor.rootComponent; @@ -80,26 +100,6 @@ export default class TWorld { this.workerPort?.postMessage(message); } - public applyCentralForce(component: TSceneComponent, force: vec3) { - const message: TPhysicsInMessageApplyCentralForce = { - type: TPhysicsMessageTypes.APPLY_CENTRAL_FORCE, - uuid: component.uuid, - force, - }; - - this.workerPort?.postMessage(message); - } - - public applyCentralImpulse(component: TSceneComponent, impulse: vec3) { - const message: TPhysicsInMessageApplyCentralImpulse = { - type: TPhysicsMessageTypes.APPLY_CENTRAL_IMPULSE, - uuid: component.uuid, - impulse, - }; - - this.workerPort?.postMessage(message); - } - private onMessage(event: MessageEvent) { if (event.data.type !== TPhysicsMessageTypes.SIMULATE_DONE) { console.log('game world received', event.data); @@ -119,12 +119,15 @@ export default class TWorld { break; case TPhysicsMessageTypes.SIMULATE_DONE: { const message = data as TPhysicsOutMessageSimulateDone; - this.level.onPhysicsUpdate(message.bodies); + this.onPhysicsUpdate(message.bodies); break; } } } + /** + * Sends the world config once the worker has been created + */ private setupWorld() { if (!this.config) { throw new Error('config not set'); @@ -138,7 +141,97 @@ export default class TWorld { this.workerPort?.postMessage(message); } + /** + * Called every frame with delta and triggers update on all actors + */ + public update(engine: TEngine, delta: number): Promise { + return new Promise((resolve) => { + if (this.paused) { + resolve(); + } + + this.lastDelta = delta; + this.updateResolve = resolve; + + const message: TPhysicsInMessageSimulateStep = { + type: TPhysicsMessageTypes.SIMULATE_STEP, + delta, + }; + + // This will trigger a message that will eventually result in updateResolve being triggered + this.workerPort?.postMessage(message); + }); + } + + public getRenderTasks(): TSerializedRenderTask[] { + const tasks: TSerializedRenderTask[] = []; + + for (const actor of this.actors) { + tasks.push(...actor.getRenderTasks()); + } + + return tasks; + } + + /** + * Called when a state is entered so the world updates should continue + */ + public start() { + this.paused = false; + } + + /** + * Called when a state is moved out of the active state so world updates should stop + */ + public pause() { + this.paused = true; + } + + public onPhysicsUpdate(worldState: TPhysicsBody[]) { + for (const obj of worldState) { + // Find the actor with root component with this uuid + for (const actor of this.actors) { + if (actor.rootComponent.uuid === obj.uuid) { + actor.rootComponent.transform.translation = vec3.fromValues( + ...obj.translation + ); + actor.rootComponent.transform.rotation = quat.fromValues( + ...obj.rotation + ); + + break; + } + } + } + + for (const actor of this.actors) { + actor.update(this.engine, this.lastDelta); + } + + this.updateResolve?.(); + } + public destroy() { this.worker?.terminate(); } + + public applyCentralForce(component: TSceneComponent, force: vec3) { + const message: TPhysicsInMessageApplyCentralForce = { + type: TPhysicsMessageTypes.APPLY_CENTRAL_FORCE, + uuid: component.uuid, + force, + }; + + this.workerPort?.postMessage(message); + } + + public applyCentralImpulse(component: TSceneComponent, impulse: vec3) { + const message: TPhysicsInMessageApplyCentralImpulse = { + type: TPhysicsMessageTypes.APPLY_CENTRAL_IMPULSE, + uuid: component.uuid, + impulse, + }; + + this.workerPort?.postMessage(message); + } } diff --git a/packages/ted/src/index.ts b/packages/ted/src/index.ts index f6fabde..72bea6b 100644 --- a/packages/ted/src/index.ts +++ b/packages/ted/src/index.ts @@ -36,7 +36,7 @@ export * from './core/game-state'; export { default as TGameStateManager } from './core/game-state-manager'; -export { default as TLevel } from './core/level'; +export { default as TWorld } from './core/world'; export * from './core/messages'; export { default as TPawn } from './core/pawn'; @@ -74,9 +74,6 @@ export { default as TSimpleController } from './input/simple-controller'; export { default as TTransform } from './math/transform'; -export * from './physics/world'; -export { default as TWorld } from './physics/world'; - export * from './physics/colliders'; export { default as TBoxCollider } from './physics/colliders/box-collider'; diff --git a/packages/ted/src/physics/worker/messages.ts b/packages/ted/src/physics/worker/messages.ts index 19d4e13..549c260 100644 --- a/packages/ted/src/physics/worker/messages.ts +++ b/packages/ted/src/physics/worker/messages.ts @@ -1,6 +1,6 @@ import type { vec3 } from 'gl-matrix'; import type { TColliderConfig } from '../colliders'; -import type { TWorldConfig } from '../world'; +import type { TWorldConfig } from '../../core/world'; import type { TPhysicsBody, TPhysicsCollision } from './physics-world'; export enum TPhysicsMessageTypes { diff --git a/packages/ted/src/physics/worker/physics-world.ts b/packages/ted/src/physics/worker/physics-world.ts index cabf7a2..77677c5 100644 --- a/packages/ted/src/physics/worker/physics-world.ts +++ b/packages/ted/src/physics/worker/physics-world.ts @@ -1,6 +1,6 @@ import type { vec3 } from 'gl-matrix'; import type { TColliderConfig } from '../colliders'; -import type { TWorldConfig } from '../world'; +import type { TWorldConfig } from '../../core/world'; export interface TPhysicsWorld { create(config: TWorldConfig): Promise;