UNPKG

onsight

Version:

Interactive, easy to use JavaScript game framework.

246 lines (201 loc) 9.5 kB
import { APP_EVENTS, APP_SIZE, } from '../constants.js'; import { AssetManager } from './AssetManager.js'; const scriptFunctions = APP_EVENTS.toString(); const scriptReturnObject = {}; for (const event of APP_EVENTS) scriptReturnObject[event] = event; const scriptParameters = 'app,' + scriptFunctions; const scriptReturnString = JSON.stringify(scriptReturnObject).replace(/\"/g, ''); /* remove all qoutes */ class SceneManager { static app = undefined; /********** ENTITY */ /** Clone and copy children */ static cloneChildren(toEntity, fromEntity) { for (const entity of fromEntity.getEntities()) { const clone = entity.clone(false /* recursive? */); // Scripts SceneManager.loadScriptsFromComponents(clone, entity); // Children if (!entity.isStage) SceneManager.cloneChildren(clone, entity); // Map position of top level stage entities to world load position if (fromEntity.isStage) { // // Transform stage begin position // fromEntity.beginPosition.decompose(_beginPosition, _beginQuaternion, _beginScale); // clone.position.sub(_beginPosition); // clone.position.applyQuaternion(_invQuaternion.copy(_beginQuaternion).invert()); // clone.position.multiply(_beginScale); // clone.position.multiply(_worldScale); // clone.rotation.setFromQuaternion(_tempRotation.setFromEuler(clone.rotation).premultiply(_beginQuaternion)); // clone.scale.multiply(_beginScale); // Flag for Unloading if (toEntity.isWorld) { const loadedDistance = toEntity.loadDistance + Math.abs(clone.position.length()); clone.traverse((child) => { child.loadedDistance = loadedDistance; }); } // // Add world load position // clone.scale.multiply(_worldScale); // clone.rotation.setFromQuaternion(_tempRotation.setFromEuler(clone.rotation).premultiply(_worldQuaternion)); // clone.position.applyQuaternion(_worldQuaternion); // clone.position.add(_worldPosition); } // Add clone toEntity.add(clone); // Component 'onLoad' for (const component of clone.components) { if (typeof component.onLoad === 'function') component.onLoad(); } } } // Add Scripts static loadScriptsFromComponents(toEntity, fromEntity) { if (!toEntity || !toEntity.isEntity) return; if (!fromEntity || !fromEntity.isEntity || !fromEntity.components) return; for (const component of fromEntity.components) { if (component.type !== 'script' || !component.data) continue; // Find Script const scriptUUID = component.data.script; const script = AssetManager.get(scriptUUID); if (!script || !script.isScript) continue; // Script Body let body = `${script.source}\n`; for (const variable in component.data.variables) { const value = component.data.variables[variable]; if (value && typeof value === 'object') { // console.log(value); if (typeof value.value !== 'undefined') { let json = JSON.stringify(value.value); json = json.replace(/[.*+`'"?^${}()|[\]\\]/g, '\\$&'); /* fix special characters */ // console.log(json); body = body + `let ${variable} = JSON.parse('${json}');\n`; } else { body = body + `let ${variable} = undefined;\n`; } } } body = body + `return ${scriptReturnString};`; // Returns object holding script functions (with proper 'this' bound and access to globals / script variables) const returnFunctionObject = new Function(scriptParameters /* parameters */, body /* source */).bind(toEntity); const functionObject = returnFunctionObject(SceneManager.app); // Add functions to Event Dispatcher for (const name in functionObject) { if (typeof functionObject[name] !== 'function') continue; const callback = functionObject[name].bind(toEntity); SceneManager.app.addEvent(name, toEntity, callback); } } } static removeEntity(fromScene, entity) { if (!fromScene || !fromScene.isWorld) return; if (!entity || !entity.isEntity) return; SceneManager.app.dispatch('destroy', {}, [ entity.uuid ]); fromScene.removeEntity(entity, true /* forceDelete */); if (typeof entity.dispose === 'function') entity.dispose(); } /********** SCENE */ static cloneStage(toScene, fromStage, updateLoadPosition = true) { if (!toScene || !toScene.isWorld) return; if (!fromStage || !fromStage.isStage) return; // Add children // toScene.loadPosition.decompose(_worldPosition, _worldQuaternion, _worldScale); SceneManager.cloneChildren(toScene, fromStage); // Update load position if (updateLoadPosition) { // // Transform begin -> end position // fromStage.beginPosition.decompose(_beginPosition, _beginQuaternion, _beginScale); // fromStage.endPosition.decompose(_endPosition, _endQuaternion, _endScale); // _endPosition.sub(_beginPosition); // _endPosition.applyQuaternion(_invQuaternion.copy(_beginQuaternion).invert()); // _endPosition.multiply(_beginScale); /* scale position */ // _endPosition.multiply(_worldScale); // _endPosition.applyQuaternion(_worldQuaternion); // _endQuaternion.premultiply(_beginQuaternion); // _endScale.multiply(_beginScale); // // Add end position to load position // _worldScale.multiply(_endScale); // _worldQuaternion.premultiply(_endQuaternion); // _worldPosition.add(_endPosition); // toScene.loadPosition.compose(_worldPosition, _worldQuaternion, _worldScale); // // Load distance // toScene.loadDistance += Math.abs(_endPosition.length()); } } static loadStages(toScene, fromWorld, preload = 10) { if (!toScene || !toScene.isWorld) return; if (!fromWorld || !fromWorld.isWorld) return; if (preload < 0) return; const startDistance = toScene.loadDistance; let addedStageCount = 0; while (toScene.loadDistance - startDistance < preload) { // Possible Stages const stages = []; for (const stage of fromWorld.getStages()) { if (!stage.enabled) continue; if (stage.start >= 0 && stage.start > toScene.loadDistance) continue; if (stage.finish >= 0 && stage.finish < toScene.loadDistance) continue; stages.push(stage); } // Load Stage if (stages.length > 0) { SceneManager.cloneStage(toScene, stages[Math.floor(Math.random() * stages.length)]); addedStageCount++; // No Stages Available } else { toScene.loadDistance += preload; break; } } // Script 'init()' functions if (addedStageCount > 0) { SceneManager.app.dispatch('init'); } } static loadWorld(toScene, fromWorld) { if (!toScene || !toScene.isWorld) return; if (!fromWorld || !fromWorld.isWorld) return; // Background if (fromWorld.background != null) { if (fromWorld.background.isColor) { toScene.background = fromWorld.background.clone(); } else { const texture = AssetManager.get(fromWorld.background); if (texture && texture.isTexture) toScene.background = texture.clone(); } } // Physics? const worldPhysicsComponent = fromWorld.getComponentByType('physics'); if (worldPhysicsComponent) { const scenePhysicsComponent = toScene.addComponent(worldPhysicsComponent.type, worldPhysicsComponent.toJSON(), false); scenePhysicsComponent.onLoad(); toScene.physics = scenePhysicsComponent; } // Children SceneManager.cloneChildren(toScene, fromWorld); } /********** RENDER */ static renderWorld(world) { if (!world || !world.isWorld) return; } static setCamera(world, camera) { SceneManager.app.camera = camera; } static setSize(width, height) { const fit = SceneManager.app?.project?.settings?.orientation; const ratio = APP_SIZE / ((fit === 'portrait') ? height : width); const fixedWidth = width * ratio; const fixedHeight = height * ratio; } /********** DISPOSE */ static dispose() { } /******************** TYPES */ static register(type, ObjectClass) { _types.set(type, ObjectClass); } static type(type) { return _types.get(type); } } const _types = new Map(); export { SceneManager };