199 lines
5.9 KiB
TypeScript
199 lines
5.9 KiB
TypeScript
import { Renderer } from '../core/renderer';
|
|
import { EngineComponent } from '../types/engine-component';
|
|
import {
|
|
ChangeEvent,
|
|
EngineEvents,
|
|
InstanceEvent,
|
|
RemoveEvent,
|
|
ReparentEvent,
|
|
} from '../types/events';
|
|
import { EventEmitter } from '../utils/events';
|
|
import { Environment } from '../gameobjects/environment.object';
|
|
import { assetManager } from '../assets/manager';
|
|
import { WorldFile } from '../types/world-file';
|
|
import { World } from '../gameobjects/world.object';
|
|
import { instancableGameObjects } from '../gameobjects';
|
|
import { Object3D } from 'three';
|
|
import { GameObject, SerializedObject } from '../types/game-object';
|
|
import { environmentDefaults } from '../defaults/environment';
|
|
|
|
/**
|
|
* Game level management component. This component provides the World.
|
|
*
|
|
* EnvironmentComponent must be initialized before this component.
|
|
* @listens change Applies changes to objects
|
|
* @listens remove Removes objects from scene
|
|
* @listens reparent Reparents object
|
|
*/
|
|
export class LevelComponent extends EngineComponent {
|
|
public name = LevelComponent.name;
|
|
public world = new World();
|
|
private environment!: Environment;
|
|
private cleanUpEvents?: Function;
|
|
|
|
constructor(
|
|
protected renderer: Renderer,
|
|
protected events: EventEmitter<EngineEvents>
|
|
) {
|
|
super(renderer, events);
|
|
}
|
|
|
|
initialize(): void {
|
|
this.renderer.scene.add(this.world);
|
|
this.environment = this.renderer.scene.getObjectByName(
|
|
'Environment'
|
|
) as Environment;
|
|
this.cleanUpEvents = this.bindEvents();
|
|
}
|
|
|
|
update(delta: number): void {}
|
|
|
|
dispose(): void {
|
|
this.cleanUpEvents?.call(this);
|
|
}
|
|
|
|
public getSceneTree() {
|
|
return {
|
|
world: this.world,
|
|
environment: this.environment,
|
|
};
|
|
}
|
|
|
|
public createObject(object: string, setParent?: Object3D, skipJoin = false) {
|
|
const parent = setParent || this.world;
|
|
const ObjectType = instancableGameObjects[object];
|
|
if (!ObjectType) return;
|
|
const newObject = new ObjectType();
|
|
parent.add(newObject);
|
|
if (!skipJoin) this.events.emit('sceneJoin', newObject);
|
|
return newObject;
|
|
}
|
|
|
|
public deserializeObject(root: SerializedObject, setParent?: Object3D) {
|
|
const parent = setParent || this.world;
|
|
|
|
if (root.objectType === 'World') {
|
|
root.children.forEach((entry) => this.recursiveCreate(entry, parent));
|
|
return parent;
|
|
}
|
|
|
|
return this.recursiveCreate(root, parent);
|
|
}
|
|
|
|
public serializeLevelSave(name: string): WorldFile {
|
|
const world = this.world.serialize();
|
|
const environment = this.environment.serialize();
|
|
const assets = assetManager.serialize();
|
|
return {
|
|
name,
|
|
world,
|
|
environment,
|
|
assets,
|
|
};
|
|
}
|
|
|
|
public async deserializeLevelSave(save: WorldFile) {
|
|
// Reset the world
|
|
this.events.emit('reset');
|
|
|
|
// Load all assets
|
|
await assetManager.loadAll(save.assets);
|
|
|
|
// Load environment
|
|
this.environment.deserialize(save.environment);
|
|
this.events.emit('setEnvironment', this.environment);
|
|
|
|
// Load world
|
|
this.deserializeObject(save.world);
|
|
this.world.deserialize(save.world);
|
|
this.events.emit('loadComplete');
|
|
}
|
|
|
|
private recursiveCreate(entry: SerializedObject, setParent?: Object3D) {
|
|
const parent = setParent || this.world;
|
|
const newObject = this.createObject(entry.objectType, parent, true);
|
|
newObject?.deserialize(entry);
|
|
entry.children.forEach((child) => this.recursiveCreate(child, newObject));
|
|
return newObject;
|
|
}
|
|
|
|
private bindEvents() {
|
|
const changeEvent = (event: ChangeEvent) => {
|
|
if (event.applied || !event.object) return;
|
|
const prop = (event.object as any)[event.property];
|
|
if (prop?.copy) prop.copy(event.value);
|
|
else (event.object as any)[event.property] = event.value;
|
|
};
|
|
|
|
const removeEvent = (event: RemoveEvent) => {
|
|
if (event.applied || !event.object) return;
|
|
if ((event.object as GameObject).virtual) return;
|
|
if (Array.isArray(event.object)) {
|
|
event.object.forEach((object) => object.removeFromParent());
|
|
return;
|
|
}
|
|
|
|
event.object.removeFromParent();
|
|
this.events.emit('sceneLeave', event.object);
|
|
};
|
|
|
|
const reparentEvent = (event: ReparentEvent) => {
|
|
if (event.applied || !event.object || !event.parent) return;
|
|
if (Array.isArray(event.object)) {
|
|
event.object.forEach((object) => {
|
|
event.parent.attach(object);
|
|
this.events.emit('change', {
|
|
object: object,
|
|
property: 'parent',
|
|
value: event.parent,
|
|
applied: true,
|
|
});
|
|
});
|
|
return;
|
|
}
|
|
|
|
event.parent.attach(event.object);
|
|
this.events.emit('change', {
|
|
object: event.object,
|
|
property: 'parent',
|
|
value: event.parent,
|
|
applied: true,
|
|
});
|
|
};
|
|
|
|
const instanceEvent = (event: InstanceEvent) => {
|
|
const parent =
|
|
typeof event.parent === 'string'
|
|
? this.world.getObjectByProperty('uuid', event.parent)
|
|
: event.parent;
|
|
const object = this.createObject(event.type, parent, !!event.data);
|
|
|
|
if (event.data && object) {
|
|
object.deserialize(event.data);
|
|
this.events.emit('sceneJoin', object);
|
|
}
|
|
};
|
|
|
|
const resetEvent = () => {
|
|
this.world.clear();
|
|
assetManager.freeAll();
|
|
this.events.emit('setEnvironment', environmentDefaults);
|
|
Object.assign(this.environment, environmentDefaults);
|
|
};
|
|
|
|
this.events.addListener('change', changeEvent);
|
|
this.events.addListener('remove', removeEvent);
|
|
this.events.addListener('reparent', reparentEvent);
|
|
this.events.addListener('instance', instanceEvent);
|
|
this.events.addListener('reset', resetEvent);
|
|
|
|
return () => {
|
|
this.events.removeEventListener('change', changeEvent);
|
|
this.events.removeEventListener('remove', removeEvent);
|
|
this.events.removeEventListener('reparent', reparentEvent);
|
|
this.events.removeEventListener('instance', instanceEvent);
|
|
this.events.removeEventListener('reset', resetEvent);
|
|
};
|
|
}
|
|
}
|