freeblox/packages/engine/src/components/level.ts

199 lines
5.9 KiB
TypeScript
Raw Normal View History

2023-06-04 10:30:20 +00:00
import { Renderer } from '../core/renderer';
import { EngineComponent } from '../types/engine-component';
2023-06-04 15:25:00 +00:00
import {
ChangeEvent,
EngineEvents,
2023-06-04 17:23:41 +00:00
InstanceEvent,
2023-06-04 15:25:00 +00:00
RemoveEvent,
ReparentEvent,
} from '../types/events';
2023-06-04 10:30:20 +00:00
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';
2023-06-04 17:23:41 +00:00
import { instancableGameObjects } from '../gameobjects';
import { Object3D } from 'three';
2023-06-09 19:38:53 +00:00
import { GameObject, SerializedObject } from '../types/game-object';
2023-06-05 15:29:37 +00:00
import { environmentDefaults } from '../defaults/environment';
2023-06-04 10:30:20 +00:00
2023-06-04 15:25:00 +00:00
/**
* Game level management component. This component provides the World.
*
* EnvironmentComponent must be initialized before this component.
2023-06-04 15:25:00 +00:00
* @listens change Applies changes to objects
* @listens remove Removes objects from scene
* @listens reparent Reparents object
*/
2023-06-04 10:30:20 +00:00
export class LevelComponent extends EngineComponent {
public name = LevelComponent.name;
public world = new World();
2023-06-04 10:30:20 +00:00
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);
2023-06-04 10:30:20 +00:00
this.environment = this.renderer.scene.getObjectByName(
'Environment'
) as Environment;
this.cleanUpEvents = this.bindEvents();
}
update(delta: number): void {}
2023-06-18 15:51:09 +00:00
dispose(): void {
2023-06-04 10:30:20 +00:00
this.cleanUpEvents?.call(this);
}
public getSceneTree() {
return {
world: this.world,
environment: this.environment,
};
}
2023-06-22 12:09:31 +00:00
public createObject(object: string, setParent?: Object3D, skipJoin = false) {
2023-06-04 17:23:41 +00:00
const parent = setParent || this.world;
const ObjectType = instancableGameObjects[object];
if (!ObjectType) return;
const newObject = new ObjectType();
parent.add(newObject);
2023-06-22 12:09:31 +00:00
if (!skipJoin) this.events.emit('sceneJoin', newObject);
2023-06-09 19:38:53 +00:00
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);
2023-06-04 17:23:41 +00:00
}
2023-06-04 10:30:20 +00:00
public serializeLevelSave(name: string): WorldFile {
const world = this.world.serialize();
const environment = this.environment.serialize();
const assets = assetManager.serialize();
return {
name,
world,
environment,
assets,
};
}
2023-06-09 19:38:53 +00:00
public async deserializeLevelSave(save: WorldFile) {
// Reset the world
this.events.emit('reset');
// Load all assets
await assetManager.loadAll(save.assets);
// Load environment
2023-06-10 07:54:51 +00:00
this.environment.deserialize(save.environment);
2023-06-09 19:38:53 +00:00
this.events.emit('setEnvironment', this.environment);
// Load world
this.deserializeObject(save.world);
2023-06-24 18:33:01 +00:00
this.world.deserialize(save.world);
2023-06-14 17:54:49 +00:00
this.events.emit('loadComplete');
2023-06-09 19:38:53 +00:00
}
private recursiveCreate(entry: SerializedObject, setParent?: Object3D) {
const parent = setParent || this.world;
2023-06-22 12:09:31 +00:00
const newObject = this.createObject(entry.objectType, parent, true);
2023-06-10 07:54:51 +00:00
newObject?.deserialize(entry);
2023-06-09 19:38:53 +00:00
entry.children.forEach((child) => this.recursiveCreate(child, newObject));
return newObject;
}
2023-06-04 10:30:20 +00:00
private bindEvents() {
2023-06-04 12:19:04 +00:00
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;
};
2023-06-04 15:25:00 +00:00
const removeEvent = (event: RemoveEvent) => {
if (event.applied || !event.object) return;
2023-06-04 17:23:41 +00:00
if ((event.object as GameObject).virtual) return;
2023-06-04 15:25:00 +00:00
if (Array.isArray(event.object)) {
event.object.forEach((object) => object.removeFromParent());
return;
}
event.object.removeFromParent();
2023-06-04 17:23:41 +00:00
this.events.emit('sceneLeave', event.object);
2023-06-04 15:25:00 +00:00
};
const reparentEvent = (event: ReparentEvent) => {
if (event.applied || !event.object || !event.parent) return;
if (Array.isArray(event.object)) {
event.object.forEach((object) => {
2023-06-09 19:38:53 +00:00
event.parent.attach(object);
this.events.emit('change', {
object: object,
property: 'parent',
value: event.parent,
applied: true,
});
2023-06-04 15:25:00 +00:00
});
return;
}
2023-06-09 19:38:53 +00:00
event.parent.attach(event.object);
this.events.emit('change', {
object: event.object,
property: 'parent',
value: event.parent,
applied: true,
});
2023-06-04 15:25:00 +00:00
};
2023-06-25 11:51:16 +00:00
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);
}
};
2023-06-04 17:23:41 +00:00
2023-06-05 15:29:37 +00:00
const resetEvent = () => {
this.world.clear();
assetManager.freeAll();
this.events.emit('setEnvironment', environmentDefaults);
Object.assign(this.environment, environmentDefaults);
};
2023-06-04 12:19:04 +00:00
this.events.addListener('change', changeEvent);
2023-06-04 15:25:00 +00:00
this.events.addListener('remove', removeEvent);
this.events.addListener('reparent', reparentEvent);
2023-06-04 17:23:41 +00:00
this.events.addListener('instance', instanceEvent);
2023-06-05 15:29:37 +00:00
this.events.addListener('reset', resetEvent);
2023-06-04 12:19:04 +00:00
return () => {
this.events.removeEventListener('change', changeEvent);
2023-06-04 15:25:00 +00:00
this.events.removeEventListener('remove', removeEvent);
this.events.removeEventListener('reparent', reparentEvent);
2023-06-04 17:23:41 +00:00
this.events.removeEventListener('instance', instanceEvent);
2023-06-05 15:29:37 +00:00
this.events.removeEventListener('reset', resetEvent);
2023-06-04 12:19:04 +00:00
};
2023-06-04 10:30:20 +00:00
}
}