UNPKG

three-game-engine

Version:

Simple light-weight game engine using three.js, three-mesh-ui and rapier

323 lines (322 loc) 13.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const THREE = require("three"); const GameObject_1 = require("./GameObject"); const PhysicsHelpers = require("./physics/PhysicsHelpers"); const ThreeJSHelpers_1 = require("./util/ThreeJSHelpers"); const SoundAsset_1 = require("./assets/SoundAsset"); class Scene { constructor(game, jsonAssetPath) { this.game = game; this.jsonAssetPath = jsonAssetPath; this.sceneJSONAsset = null; this.name = 'unnamed-scene'; this.lights = []; this.gameObjects = []; this.threeJSScene = null; this.active = false; } getGameObjectClass(type) { return this.game.getGameObjectClass(type); } async load() { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u; if (this.jsonAssetPath) { this.sceneJSONAsset = await this.game.loadAsset(this.jsonAssetPath); } this.threeJSScene = new THREE.Scene(); this.threeJSScene.name = this.name; this.threeJSScene.background = ((_b = (_a = this.sceneJSONAsset) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.background) || new THREE.Color('lightblue'); this.setFog(((_d = (_c = this.sceneJSONAsset) === null || _c === void 0 ? void 0 : _c.data) === null || _d === void 0 ? void 0 : _d.fog) || null); this.setLights(((_f = (_e = this.sceneJSONAsset) === null || _e === void 0 ? void 0 : _e.data) === null || _f === void 0 ? void 0 : _f.lights) || []); await this.loadSounds(((_h = (_g = this.sceneJSONAsset) === null || _g === void 0 ? void 0 : _g.data) === null || _h === void 0 ? void 0 : _h.sounds) || []); await PhysicsHelpers.initRAPIER(); this.initialGravity = { x: ((_l = (_k = (_j = this.sceneJSONAsset) === null || _j === void 0 ? void 0 : _j.data) === null || _k === void 0 ? void 0 : _k.gravity) === null || _l === void 0 ? void 0 : _l.x) || 0, y: ((_p = (_o = (_m = this.sceneJSONAsset) === null || _m === void 0 ? void 0 : _m.data) === null || _o === void 0 ? void 0 : _o.gravity) === null || _p === void 0 ? void 0 : _p.y) || -9.8, z: ((_s = (_r = (_q = this.sceneJSONAsset) === null || _q === void 0 ? void 0 : _q.data) === null || _r === void 0 ? void 0 : _r.gravity) === null || _s === void 0 ? void 0 : _s.z) || 0, }; this.rapierWorld = PhysicsHelpers.createRapierWorld(this.initialGravity); this.gameObjects = []; (((_u = (_t = this.sceneJSONAsset) === null || _t === void 0 ? void 0 : _t.data) === null || _u === void 0 ? void 0 : _u.gameObjects) || []).forEach((g, index) => this._createGameObject(this, g, [index])); for (let i = 0; i < this.gameObjects.length; i++) { const gameObject = this.gameObjects[i]; await gameObject.load(); } } setFog(fog) { if (!this.threeJSScene) { throw new Error('Cant set/change fog, this scene has not finished loading, thus no .threeJSScene exists yet'); } if (fog instanceof THREE.Fog) { this.threeJSScene.fog = fog; } else if (typeof fog === 'object') { const fogDefaults = { color: '#00000', near: 1.0, far: 1000.0 }; const fogSettings = Object.assign({}, fogDefaults, fog); this.threeJSScene.fog = new THREE.Fog(fogSettings.color, fogSettings.near, fogSettings.far); } else if (fog === null) { this.threeJSScene.fog = null; } else { throw new Error(`scene.setFog(): invalid value ${fog}`); } } setLights(lights) { const existingLights = this.threeJSScene.children.filter(child => child instanceof THREE.Light); existingLights.forEach(existingLight => this.threeJSScene.remove(existingLight)); lights.forEach((lightData) => { const light = (0, ThreeJSHelpers_1.createLight)(lightData); this.threeJSScene.add(light); }); } async loadSounds(sounds) { const existingSounds = this.threeJSScene.children.filter(child => child instanceof THREE.Audio); existingSounds.forEach(sound => this.threeJSScene.remove(sound)); for (let i = 0; i < sounds.length; i++) { const soundData = sounds[i]; const asset = await this.game.loadAsset(soundData.assetPath); if (!(asset instanceof SoundAsset_1.default)) { throw new Error(`Scene: asset found at ${soundData.assetPath} in AssetStore should be a SoundAsset`); } const audioBuffer = asset.getData(); const audioListener = this.game.renderer.getCameraAudioListener(); const name = soundData.name || `sound_${i}`; const audio = (0, ThreeJSHelpers_1.createAudio)(soundData, audioBuffer, audioListener, name); this.threeJSScene.add(audio); } } playSound(soundName, delayInSec = 0, detune = null) { const audio = this.threeJSScene.children.find(c => c.name === soundName && c instanceof THREE.Audio); if (audio) { if (audio.isPlaying) { audio.pause(); // elsewise nothing will happen } audio.play(delayInSec); if (detune !== null) { audio.setDetune(detune); // set this here, rather than when creating the audio as setDetune can't be called till playback (where audio.source is set) } } else { throw new Error(`scene.playSound(): scene: ${this.name} has no sound with name: ${soundName}`); } } _createGameObject(parent, gameObjectJSON, indices) { const options = { ...gameObjectJSON }; delete options.children; options.userData = { indices }; let gameObject = null; if (gameObjectJSON.type) { const type = gameObjectJSON.type; if (!this.game.getGameObjectTypeJSON(type)) { throw new Error(`Scene: error creating game object: unknown game object type: ${type}. You must define this type in your game.json file`); } const RegisteredGameObjectClass = this.getGameObjectClass(type); if (RegisteredGameObjectClass) { // @ts-ignore gameObject = new RegisteredGameObjectClass(parent, options); } else { gameObject = new GameObject_1.default(parent, options); } } else { gameObject = new GameObject_1.default(parent, options); } if (!(gameObject instanceof GameObject_1.default)) { throw new Error(`Error: GameObject class must be a sub-class of GameObject. Invalid class registered for type ${gameObjectJSON.type}`); } parent.addGameObject(gameObject); this.threeJSScene.add(gameObject.threeJSGroup); (gameObjectJSON.children || []).forEach((childData, index) => { this._createGameObject(gameObject, childData, indices.concat(index)); }); } advancePhysics() { this.rapierWorld.step(); this.forEachGameObject(gameObject => { gameObject.syncWithRigidBody(); }); } isActive() { return this.active; } addGameObject(gameObject) { if (!this.gameObjects.some(g => g === gameObject)) { gameObject.parent = this; this.gameObjects.push(gameObject); this.threeJSScene.add(gameObject.threeJSGroup); if (this.isActive()) { gameObject.load().then(() => gameObject.afterLoaded()); // asynchronous } } } removeGameObject(gameObject) { if (this.gameObjects.some(g => g === gameObject)) { // gameObject is indeed a child of this scene this.gameObjects = this.gameObjects.filter(g => g !== gameObject); gameObject.parent = null; this.threeJSScene.remove(gameObject.threeJSGroup); } } getRapierWorld() { return this.rapierWorld; } getRootGameObjects() { return this.gameObjects; } forEachGameObject(fn) { for (let i = 0; i < this.gameObjects.length; i++) { const obj = this.gameObjects[i]; fn(obj); obj.gameObjects.forEach(child => { child.forEachGameObject(fn); }); } } getGameObject(fn) { for (let i = 0; i < this.gameObjects.length; i++) { const obj = this.gameObjects[i]; if (fn(obj)) { return obj; } const child = obj.getGameObject(fn); if (child) { return child; } } return null; } getGameObjects(fn) { let results = []; for (let i = 0; i < this.gameObjects.length; i++) { const obj = this.gameObjects[i]; if (fn(obj)) { results.push(obj); } const childResults = obj.getGameObjects(fn); results = results.concat(childResults); } return results; } getGameObjectWithName(name) { return this.getGameObject(g => g.name === name); } getGameObjectsWithTag(tag) { return this.getGameObjects(g => g.hasTag(tag)); } getGameObjectWithID(id) { return this.getGameObject(g => g.id === id); } getGameObjectIndices(gameObject) { return this._getGameObjectIndices(gameObject, this, []); } _getGameObjectIndices(gameObject, parent, indices) { const currentIndices = [...indices]; currentIndices.push(0); for (let i = 0; i < parent.gameObjects.length; i++) { currentIndices[currentIndices.length - 1] = i; const currentGameObject = parent.gameObjects[i]; if (currentGameObject === gameObject) { return currentIndices; } // Now check all the children of this game object (recursively) const result = this._getGameObjectIndices(gameObject, currentGameObject, currentIndices); if (result) { return result; } } return null; } getGameObjectByIndices(indices) { let parent = this; for (let i = 0; i < indices.length; i++) { const index = indices[i]; if (i == indices.length - 1) { return parent.gameObjects[index]; } else { parent = parent.gameObjects[index]; } if (!parent) { return null; } } } getGameObjectWithThreeJSObject(object3D) { if (object3D instanceof THREE.Group) { const { gameObjectID } = object3D.userData; if (gameObjectID) { return this.getGameObjectWithID(gameObjectID); } else { return this.getGameObjectWithThreeJSObject(object3D.parent); } } else if (object3D.parent) { return this.getGameObjectWithThreeJSObject(object3D.parent); } else { return null; } } afterLoaded() { // Optional: override and handle this event } beforeRender({ deltaTimeInSec }) { // Optional: override and handle this event } // Called on the scene and all its GameObjects just before // a new scene is loaded. Use this to do teardown operations. beforeUnloaded() { // Optional: override and handle this event } showGrid(size = 100, divisions = 100, colorCenterLine = new THREE.Color(0x444444), colorGrid = new THREE.Color(0x888888)) { this.hideGrid(); const gridHelper = new THREE.GridHelper(size, divisions, colorCenterLine, colorGrid); gridHelper.name = 'GridHelper'; this.threeJSScene.add(gridHelper); } hideGrid() { const gridHelper = this.threeJSScene.getObjectByName('GridHelper'); if (gridHelper) { this.threeJSScene.remove(gridHelper); } } showPhysics() { let physicsRenderingLines = this.threeJSScene.getObjectByName('PhysicsRenderingLines'); if (!physicsRenderingLines) { let material = new THREE.LineBasicMaterial({ color: 0xffffff, vertexColors: true }); let geometry = new THREE.BufferGeometry(); physicsRenderingLines = new THREE.LineSegments(geometry, material); physicsRenderingLines.name = 'PhysicsRenderingLines'; this.threeJSScene.add(physicsRenderingLines); } } hidePhysics() { const physicsRenderingLines = this.threeJSScene.getObjectByName('PhysicsRenderingLines'); if (physicsRenderingLines) { this.threeJSScene.remove(physicsRenderingLines); } } updatePhysicsGraphics() { var _a; const physicsRenderingLines = (_a = this.threeJSScene) === null || _a === void 0 ? void 0 : _a.getObjectByName('PhysicsRenderingLines'); if (physicsRenderingLines) { const buffers = this.rapierWorld.debugRender(); physicsRenderingLines.geometry.setAttribute('position', new THREE.BufferAttribute(buffers.vertices, 3)); physicsRenderingLines.geometry.setAttribute('color', new THREE.BufferAttribute(buffers.colors, 4)); } } } exports.default = Scene;