diff --git a/backend/app/Db.js b/backend/app/Db.js index 27587dd..672c22a 100644 --- a/backend/app/Db.js +++ b/backend/app/Db.js @@ -26,7 +26,7 @@ class Db { try { dbo = db.db(app.config.db.name); this.instance = dbo; - for (let c of ['users', 'user_sessions', 'history', 'log', 'assets', 'scenarios']){ + for (let c of ['users', 'user_sessions', 'history', 'log', 'assets', 'scenarios', 'games']){ try { await dbo.createCollection(c); }catch(err){} diff --git a/backend/app/bl/GamesManager.js b/backend/app/bl/GamesManager.js index c072f40..69402d6 100644 --- a/backend/app/bl/GamesManager.js +++ b/backend/app/bl/GamesManager.js @@ -1,9 +1,10 @@ +const collection = 'games'; /** * Games manager class */ class GamesManager{ - name = 'games'; + name = 'game'; /** * Class initializer, инициализация на плъгин @@ -18,7 +19,9 @@ class GamesManager{ * @param {Game} data the game description, дефиниция на играта */ this.create = async function(ctx, data){ - + data.id = (await db.getLastId(collection)) + 1; + await db.create(collection, data); + return data; } /** @@ -27,7 +30,8 @@ class GamesManager{ * @returns {Game} the game, игрова дефиниция */ this.read = async function(id){ - + id = parseInt(id); + return await db.get(collection, {id}); } /** @@ -36,7 +40,11 @@ class GamesManager{ * @param {Game} data the game description, игрова дефиниция */ this.update = async function(ctx, data){ - + data.id = parseInt(data.id); + let object = await this.read(data.id); + data = Object.assign(object, data); + await db.update(collection, {id: data.id}, data); + return data; } /** @@ -44,7 +52,8 @@ class GamesManager{ * @param {Number} id game definition ID, идентификатор на игровата дефиниция */ this.remove = async function(id){ - + id = parseInt(id); + await db.remove(collection, {id}); } /** @@ -53,7 +62,10 @@ class GamesManager{ * @returns {Game[]} Array of games, масив от игрови дефиниции */ this.list = async function(query){ - + return await db.list(collection, { + query, + project: { name:1, id:1, sceneThumb: '$scenes.data.environment'} + }); } } diff --git a/backend/app/bl/RulesManager.js b/backend/app/bl/RulesManager.js index 8d15b2b..4b4170d 100644 --- a/backend/app/bl/RulesManager.js +++ b/backend/app/bl/RulesManager.js @@ -3,7 +3,7 @@ * Rules manager class, контролен клас за управление на игрови правила */ class RulesManager{ - name = 'rules'; + name = 'rule'; /** * Class initializer, инициализация на плъгин diff --git a/backend/controllers/api/GamesController.js b/backend/controllers/api/GamesController.js index 6f8fea7..c37ab04 100644 --- a/backend/controllers/api/GamesController.js +++ b/backend/controllers/api/GamesController.js @@ -14,7 +14,7 @@ class GamesController{ */ init(app){ const router = express.Router(); - const { games } = app; + const { game } = app; /** * API: PUT /api/game/ Create or update game, създаване/обновяване на игрова дефиниция @@ -22,7 +22,14 @@ class GamesController{ * @memberof GamesController */ router.put('/', async (req, res)=>{ - + try{ + let data = req.body; + let object = await game[data.id? 'update' : 'create'](req, data) + res.json({status: 'OK', object}); + }catch(err){ + console.error(err); + res.status(500).json({status: 'ERR', err}); + } }); /** @@ -32,6 +39,8 @@ class GamesController{ * @memberof GamesController */ router.post('/', async (req, res)=>{ + let result = await game.list(req.body); + res.json(result); }) /** @@ -42,6 +51,8 @@ class GamesController{ * @memberof GamesController */ router.get('/:id', async (req, res)=>{ + let object = await game.read(parseInt(req.params.id)); + res.json(object); }) /** @@ -51,6 +62,8 @@ class GamesController{ * @memberof GamesController */ router.delete('/:id', async (req, res)=>{ + await scenario.remove(req.params.id); + res.json({status: 'OK'}); }) app.webServer.xapp.use(this.route, router); diff --git a/backend/main.js b/backend/main.js index 35fbd67..ad5d182 100644 --- a/backend/main.js +++ b/backend/main.js @@ -9,14 +9,19 @@ console.debug = function(){ import App from './app/App.js'; const modules = [ - {name:'Config', path:'app/Config.js'}, - {name:'Db', path:'app/Db.js'}, - {name:'GameObjectsManager', path:'app/bl/GameObjectsManager.js'}, - {name:'ScenariosManager', path:'app/bl/ScenariosManager.js'}, - {name:'WebServer', path:'app/WebServer.js'}, + {name: 'Config', path:'app/Config.js'}, + {name: 'Db', path:'app/Db.js'}, + + {name: 'GameObjectsManager', path:'app/bl/GameObjectsManager.js'}, + {name: 'ScenariosManager', path:'app/bl/ScenariosManager.js'}, + {name: 'GamesManager', path:'app/bl/GamesManager.js'}, + + {name: 'WebServer', path:'app/WebServer.js'}, + {name: 'AssetController', path: 'controllers/AssetController.js'}, - {name:'GameObjectsController', path:'controllers/api/GameObjectsController.js'}, - {name:'ScenariosController', path:'controllers/api/ScenariosController.js'}, + {name: 'GameObjectsController', path:'controllers/api/GameObjectsController.js'}, + {name: 'ScenariosController', path:'controllers/api/ScenariosController.js'}, + {name: 'GamesController', path:'controllers/api/GamesController.js'}, ] process.on('uncaughtException', err => { diff --git a/src/components/AssetsManagement/AssetPreview.vue b/src/components/AssetsManagement/AssetPreview.vue index 45882fb..c6cbac6 100644 --- a/src/components/AssetsManagement/AssetPreview.vue +++ b/src/components/AssetsManagement/AssetPreview.vue @@ -59,22 +59,25 @@ export default{ if (this.forRendering) { gameEngine.scene.clear(); if (this.obj.type == 'panorama2d') { - let t = await gameEngine.loadTexture(`/asset/default/${this.obj.asset.name}`); - t.mapping = gameEngine.$.EquirectangularReflectionMapping; - gameEngine.scene.background = t; - gameEngine.scene.environment = t; - gameEngine.scene.add(gameEngine.camera); + await gameEngine.loadPanorama(`/asset/default/${this.obj.asset.name}`); + // let t = await gameEngine.loadTexture(`/asset/default/${this.obj.asset.name}`); + // t.mapping = gameEngine.$.EquirectangularReflectionMapping; + // gameEngine.scene.background = t; + // gameEngine.scene.environment = t; + // gameEngine.scene.add(gameEngine.camera); } else { let gltf = await gameEngine.load(`/asset/default/${this.obj.asset.name}`); - console.debug('GLTF', gltf); + //console.debug('GLTF', gltf); this.loadedAsset = gltf; this.animations = gltf.animations.map(a => ({ name: a.name, id: a.uuid })); + gameEngine.autoScale(gltf.scene); let bb = new gameEngine.$.Box3().setFromObject(gltf.scene); gltf.scene.traverse(function (o) { o.frustumCulled = false; }); + //console.log(bb) gameEngine.camera.position.set(bb.max.x, bb.max.y, bb.max.z); gameEngine.controls.target.set((bb.max.x + bb.min.x) / 2, (bb.max.y + bb.min.y) / 2, (bb.max.z + bb.min.z) / 2) gameEngine.controls.update(); diff --git a/src/components/GameDesigner/GameDesigner.vue b/src/components/GameDesigner/GameDesigner.vue new file mode 100644 index 0000000..2dc62e8 --- /dev/null +++ b/src/components/GameDesigner/GameDesigner.vue @@ -0,0 +1,111 @@ + + + \ No newline at end of file diff --git a/src/lib/gameEngine.js b/src/lib/gameEngine.js index 4281665..e32726d 100644 --- a/src/lib/gameEngine.js +++ b/src/lib/gameEngine.js @@ -10,6 +10,7 @@ class GameEngine { const width = 1200, height = 800; const camera = new THREE.PerspectiveCamera(70, width / height, 0.01, 10000); + this.raycaster = new THREE.Raycaster(); camera.position.set(1, 0, 1); const scene = new THREE.Scene(); @@ -56,6 +57,10 @@ class GameEngine { this.camera = camera; this.controls = controls; this.mixer = mixer; + + this.activeObjects = new THREE.Group(); + scene.add(this.activeObjects); + domNode.appendChild(renderer.domElement); let texture = await this.loadTexture('/static/textures/bck.webp'); @@ -92,6 +97,13 @@ class GameEngine { }) } + async loadPanorama(url){ + let t = await this.loadTexture(url); + t.mapping = THREE.EquirectangularReflectionMapping; + this.scene.background = t; + this.scene.environment = t; + } + async captureScreenshot(type = 'image/webp', quality = 80){ return new Promise((resolve, reject)=>{ this.renderer.domElement.toBlob(resolve, type, quality) @@ -107,6 +119,46 @@ class GameEngine { stop(){ this.renderer.setAnimationLoop(null); } + + getMouseVector(mouseEvent, domElement){ + //console.log(mouseEvent, domElement) + const mouse = new THREE.Vector2(); + if (this.renderType == 'VR'){ + let x; + if (mouseEvent.offsetX > window.innerWidth / 2){ + x = (mouseEvent.offsetX - window.innerWidth / 2) * 2 + }else { + x = mouseEvent.offsetX * 2; + } + mouse.x = ( x / window.innerWidth ) * 2 - 1; + mouse.y = - ( mouseEvent.offsetY / window.innerHeight ) * 2 + 1; + }else{ + mouse.x = ( mouseEvent.offsetX / domElement.clientWidth ) * 2 - 1; + mouse.y = - ( mouseEvent.offsetY / domElement.clientHeight ) * 2 + 1; + } + return mouse; + } + + intersect(mouseEvent, domElement, objects, recursive = false, returnInputObjects = true){ + let mouse = this.getMouseVector(mouseEvent, domElement); + this.raycaster.setFromCamera(mouse, this.camera); + console.log(objects) + let intersects = this.raycaster.intersectObjects(objects, recursive); + if (returnInputObjects && recursive){ + intersects.forEach(o=>{ + while (o.object && !objects.includes(o.object)) { + o.object = o.object.parent; + } + }) + } + return intersects; + } + + autoScale(object, mk = 1){ + let bb = new THREE.Box3().setFromObject(object); + let k = Math.max(bb.max.x - bb.min.x, bb.max.y - bb.min.y, bb.max.z - bb.min.z); + object.scale.multiplyScalar(mk/k); + } } export {GameEngine} \ No newline at end of file diff --git a/src/pages/games/[[id]].vue b/src/pages/games/[[id]].vue index 8b00dca..a478312 100644 --- a/src/pages/games/[[id]].vue +++ b/src/pages/games/[[id]].vue @@ -1,9 +1,84 @@ \ No newline at end of file diff --git a/src/pages/games/list.vue b/src/pages/games/list.vue new file mode 100644 index 0000000..56d6c33 --- /dev/null +++ b/src/pages/games/list.vue @@ -0,0 +1,49 @@ + + + \ No newline at end of file diff --git a/src/plugins/api.js b/src/plugins/api.js index d6cc480..ce3fd6b 100644 --- a/src/plugins/api.js +++ b/src/plugins/api.js @@ -46,6 +46,20 @@ export default { async remove(id){ return await $ax.delete(`/scenario/${id}`) } + }, + game:{ + async save(data){ + return await $ax.put('/game', data); + }, + async load(id){ + return await $ax.get(`/game/${id}`); + }, + async search(query){ + return await $ax.post('/game', query); + }, + async remove(id){ + return await $ax.delete(`/game/${id}`) + } } } } diff --git a/src/plugins/lang.js b/src/plugins/lang.js index fc6f8f4..21fce42 100644 --- a/src/plugins/lang.js +++ b/src/plugins/lang.js @@ -3,6 +3,8 @@ const lang = { _code: 'en', createGameObject: 'Add game object', editGameObject: 'Edit game object', + createGame: 'Add game', + editGame: 'Edit game', name: 'Name', description: 'Description', fieldRequired: 'Field is required', @@ -23,11 +25,13 @@ const lang = { gameObjects: 'Objects', gameScenarios: 'Scenarios', gameRules: 'Rules', + gameDesigner: 'Game studio', games: 'Games', darkMode: 'Dark mode', confirmDeletionOf: 'Confirm deletion of', yes: 'Yes', no: 'No', + scenario: 'Scenario', createScenario: 'Create scenario', editScenario: 'Edit scenario', editScenes: 'Edit scenes', @@ -38,6 +42,8 @@ const lang = { _code: 'bg', createGameObject: 'Добавяне на игрови обект', editGameObject: 'Редактиране на игрови обект', + createGame: 'Добавяне на игра', + editGame: 'Редактиране на игра', name: 'Име', description: 'Описание', fieldRequired: 'Полето е задължително', @@ -58,11 +64,13 @@ const lang = { gameObjects: 'Обекти', gameScenarios: 'Сценарии', gameRules: 'Правила', + gameDesigner: 'Студио', games: 'Игри', darkMode: 'Тъмен режим', confirmDeletionOf: 'Потвърдете изтриването на', yes: 'Да', no: 'Не', + scenario: 'Сценарий', createScenario: 'Създаване на сценарий', editScenario: 'Редактиране на сценарий', editScenes: 'Редактиране на сцени',