gamedesigner
This commit is contained in:
+1
-1
@@ -26,7 +26,7 @@ class Db {
|
|||||||
try {
|
try {
|
||||||
dbo = db.db(app.config.db.name);
|
dbo = db.db(app.config.db.name);
|
||||||
this.instance = dbo;
|
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 {
|
try {
|
||||||
await dbo.createCollection(c);
|
await dbo.createCollection(c);
|
||||||
}catch(err){}
|
}catch(err){}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
|
|
||||||
|
const collection = 'games';
|
||||||
/**
|
/**
|
||||||
* Games manager class
|
* Games manager class
|
||||||
*/
|
*/
|
||||||
class GamesManager{
|
class GamesManager{
|
||||||
name = 'games';
|
name = 'game';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class initializer, инициализация на плъгин
|
* Class initializer, инициализация на плъгин
|
||||||
@@ -18,7 +19,9 @@ class GamesManager{
|
|||||||
* @param {Game} data the game description, дефиниция на играта
|
* @param {Game} data the game description, дефиниция на играта
|
||||||
*/
|
*/
|
||||||
this.create = async function(ctx, data){
|
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, игрова дефиниция
|
* @returns {Game} the game, игрова дефиниция
|
||||||
*/
|
*/
|
||||||
this.read = async function(id){
|
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, игрова дефиниция
|
* @param {Game} data the game description, игрова дефиниция
|
||||||
*/
|
*/
|
||||||
this.update = async function(ctx, data){
|
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, идентификатор на игровата дефиниция
|
* @param {Number} id game definition ID, идентификатор на игровата дефиниция
|
||||||
*/
|
*/
|
||||||
this.remove = async function(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, масив от игрови дефиниции
|
* @returns {Game[]} Array of games, масив от игрови дефиниции
|
||||||
*/
|
*/
|
||||||
this.list = async function(query){
|
this.list = async function(query){
|
||||||
|
return await db.list(collection, {
|
||||||
|
query,
|
||||||
|
project: { name:1, id:1, sceneThumb: '$scenes.data.environment'}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* Rules manager class, контролен клас за управление на игрови правила
|
* Rules manager class, контролен клас за управление на игрови правила
|
||||||
*/
|
*/
|
||||||
class RulesManager{
|
class RulesManager{
|
||||||
name = 'rules';
|
name = 'rule';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class initializer, инициализация на плъгин
|
* Class initializer, инициализация на плъгин
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class GamesController{
|
|||||||
*/
|
*/
|
||||||
init(app){
|
init(app){
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const { games } = app;
|
const { game } = app;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API: PUT /api/game/ Create or update game, създаване/обновяване на игрова дефиниция
|
* API: PUT /api/game/ Create or update game, създаване/обновяване на игрова дефиниция
|
||||||
@@ -22,7 +22,14 @@ class GamesController{
|
|||||||
* @memberof GamesController
|
* @memberof GamesController
|
||||||
*/
|
*/
|
||||||
router.put('/', async (req, res)=>{
|
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
|
* @memberof GamesController
|
||||||
*/
|
*/
|
||||||
router.post('/', async (req, res)=>{
|
router.post('/', async (req, res)=>{
|
||||||
|
let result = await game.list(req.body);
|
||||||
|
res.json(result);
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -42,6 +51,8 @@ class GamesController{
|
|||||||
* @memberof GamesController
|
* @memberof GamesController
|
||||||
*/
|
*/
|
||||||
router.get('/:id', async (req, res)=>{
|
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
|
* @memberof GamesController
|
||||||
*/
|
*/
|
||||||
router.delete('/:id', async (req, res)=>{
|
router.delete('/:id', async (req, res)=>{
|
||||||
|
await scenario.remove(req.params.id);
|
||||||
|
res.json({status: 'OK'});
|
||||||
})
|
})
|
||||||
|
|
||||||
app.webServer.xapp.use(this.route, router);
|
app.webServer.xapp.use(this.route, router);
|
||||||
|
|||||||
+12
-7
@@ -9,14 +9,19 @@ console.debug = function(){
|
|||||||
import App from './app/App.js';
|
import App from './app/App.js';
|
||||||
|
|
||||||
const modules = [
|
const modules = [
|
||||||
{name:'Config', path:'app/Config.js'},
|
{name: 'Config', path:'app/Config.js'},
|
||||||
{name:'Db', path:'app/Db.js'},
|
{name: 'Db', path:'app/Db.js'},
|
||||||
{name:'GameObjectsManager', path:'app/bl/GameObjectsManager.js'},
|
|
||||||
{name:'ScenariosManager', path:'app/bl/ScenariosManager.js'},
|
{name: 'GameObjectsManager', path:'app/bl/GameObjectsManager.js'},
|
||||||
{name:'WebServer', path:'app/WebServer.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: 'AssetController', path: 'controllers/AssetController.js'},
|
||||||
{name:'GameObjectsController', path:'controllers/api/GameObjectsController.js'},
|
{name: 'GameObjectsController', path:'controllers/api/GameObjectsController.js'},
|
||||||
{name:'ScenariosController', path:'controllers/api/ScenariosController.js'},
|
{name: 'ScenariosController', path:'controllers/api/ScenariosController.js'},
|
||||||
|
{name: 'GamesController', path:'controllers/api/GamesController.js'},
|
||||||
]
|
]
|
||||||
|
|
||||||
process.on('uncaughtException', err => {
|
process.on('uncaughtException', err => {
|
||||||
|
|||||||
@@ -59,22 +59,25 @@ export default{
|
|||||||
if (this.forRendering) {
|
if (this.forRendering) {
|
||||||
gameEngine.scene.clear();
|
gameEngine.scene.clear();
|
||||||
if (this.obj.type == 'panorama2d') {
|
if (this.obj.type == 'panorama2d') {
|
||||||
let t = await gameEngine.loadTexture(`/asset/default/${this.obj.asset.name}`);
|
await gameEngine.loadPanorama(`/asset/default/${this.obj.asset.name}`);
|
||||||
t.mapping = gameEngine.$.EquirectangularReflectionMapping;
|
// let t = await gameEngine.loadTexture(`/asset/default/${this.obj.asset.name}`);
|
||||||
gameEngine.scene.background = t;
|
// t.mapping = gameEngine.$.EquirectangularReflectionMapping;
|
||||||
gameEngine.scene.environment = t;
|
// gameEngine.scene.background = t;
|
||||||
gameEngine.scene.add(gameEngine.camera);
|
// gameEngine.scene.environment = t;
|
||||||
|
// gameEngine.scene.add(gameEngine.camera);
|
||||||
} else {
|
} else {
|
||||||
let gltf = await gameEngine.load(`/asset/default/${this.obj.asset.name}`);
|
let gltf = await gameEngine.load(`/asset/default/${this.obj.asset.name}`);
|
||||||
console.debug('GLTF', gltf);
|
//console.debug('GLTF', gltf);
|
||||||
this.loadedAsset = gltf;
|
this.loadedAsset = gltf;
|
||||||
this.animations = gltf.animations.map(a => ({
|
this.animations = gltf.animations.map(a => ({
|
||||||
name: a.name, id: a.uuid
|
name: a.name, id: a.uuid
|
||||||
}));
|
}));
|
||||||
|
gameEngine.autoScale(gltf.scene);
|
||||||
let bb = new gameEngine.$.Box3().setFromObject(gltf.scene);
|
let bb = new gameEngine.$.Box3().setFromObject(gltf.scene);
|
||||||
gltf.scene.traverse(function (o) {
|
gltf.scene.traverse(function (o) {
|
||||||
o.frustumCulled = false;
|
o.frustumCulled = false;
|
||||||
});
|
});
|
||||||
|
//console.log(bb)
|
||||||
gameEngine.camera.position.set(bb.max.x, bb.max.y, bb.max.z);
|
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.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();
|
gameEngine.controls.update();
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
<template>
|
||||||
|
<v-btn-toggle variant="tonal" density="compact" class="mx-auto" v-model="mode" color="blue">
|
||||||
|
<!-- <v-btn size="small" class="text-none" value="default"
|
||||||
|
prepend-icon="mdi-cursor-default-click">Pointer</v-btn> -->
|
||||||
|
<v-btn size="small" class="text-none" value="translate" prepend-icon="mdi-cursor-move">Move</v-btn>
|
||||||
|
<v-btn size="small" class="text-none" value="rotate" prepend-icon="mdi-rotate-orbit">Rotate</v-btn>
|
||||||
|
<v-btn size="small" class="text-none" value="scale" prepend-icon="mdi-resize">Scale</v-btn>
|
||||||
|
</v-btn-toggle>
|
||||||
|
<div class="container my-3">
|
||||||
|
<div ref="target" @click="targetClick" @pointerdown="targetPointerDown"></div>
|
||||||
|
</div>
|
||||||
|
<v-navigation-drawer location="right">
|
||||||
|
<v-list v-model:selected="scenesList" selectable>
|
||||||
|
<v-list-item v-for="(s, i) in scenes" :key="i" :title="s.data.title" :value="s"></v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-navigation-drawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import { GameEngine } from '@/lib/gameEngine';
|
||||||
|
|
||||||
|
let gameEngine = null;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props:{
|
||||||
|
modelValue: Object,
|
||||||
|
scenario: Object
|
||||||
|
},
|
||||||
|
data(){
|
||||||
|
return {
|
||||||
|
scenesList: [],
|
||||||
|
mode: 'translate',
|
||||||
|
pointerDownTime: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed:{
|
||||||
|
scenes(){
|
||||||
|
return this.scenario.scenes || [];
|
||||||
|
},
|
||||||
|
scene(){
|
||||||
|
return this.scenesList[0];
|
||||||
|
},
|
||||||
|
object(){
|
||||||
|
return this.modelValue;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch:{
|
||||||
|
async scene(n){
|
||||||
|
await this.loadEnvironment();
|
||||||
|
},
|
||||||
|
mode(n){
|
||||||
|
gameEngine.transformControls.setMode(n)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted(){
|
||||||
|
gameEngine = new GameEngine();
|
||||||
|
this.gameEngine = gameEngine;
|
||||||
|
await gameEngine.init(this.$refs.target);
|
||||||
|
gameEngine.scene.add(gameEngine.transformControls.getHelper())
|
||||||
|
},
|
||||||
|
methods:{
|
||||||
|
async loadEnvironment(){
|
||||||
|
await this.expandScenarioData();
|
||||||
|
if (!this.object.gd){
|
||||||
|
this.object.gd = {
|
||||||
|
items: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (this.scene.data.$environment.type == 'panorama2d'){
|
||||||
|
await gameEngine.loadPanorama(`/asset/default/${this.scene.data.$environment.asset.name}`);
|
||||||
|
}else{
|
||||||
|
let env = await gameEngine.load(`/asset/default/${this.scene.data.$environment.asset.name}`);
|
||||||
|
gameEngine.autoScale(env.scene, 10);
|
||||||
|
gameEngine.activeObjects.add(env.scene);
|
||||||
|
}
|
||||||
|
for (let i of this.scene.data.items) {
|
||||||
|
let gltf = await gameEngine.load(`/asset/default/${i.data.$go.asset.name}`);
|
||||||
|
let o = gltf.scene;
|
||||||
|
gameEngine.autoScale(o);
|
||||||
|
gameEngine.activeObjects.add(o);
|
||||||
|
const {position, scale, rotation} = o;
|
||||||
|
i.gd = {position, scale, rotation};
|
||||||
|
console.log(JSON.stringify(i.gd));
|
||||||
|
window.gameEngine = gameEngine;
|
||||||
|
//console.log(new gameEngine.$.Euler({"isEuler":true,"_x":0,"_y":0,"_z":0,"_order":"XYZ"}));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async expandScenarioData(){
|
||||||
|
this.scene.data.$environment = (await this.$api.gameObject.load(this.scene.data.environment)).data
|
||||||
|
for (let i of this.scene.data.items) {
|
||||||
|
i.data.$go = (await this.$api.gameObject.load(i.data.go)).data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
targetPointerDown(){
|
||||||
|
this.pointerDownTime = performance.now();
|
||||||
|
},
|
||||||
|
targetClick(e){
|
||||||
|
if (performance.now() - this.pointerDownTime < 200){
|
||||||
|
let intersects = gameEngine.intersect(e, this.$refs.target, gameEngine.activeObjects.children, true);
|
||||||
|
if (intersects.length){
|
||||||
|
console.log('attaching controls to', intersects[0].object)
|
||||||
|
gameEngine.transformControls.attach(intersects[0].object);
|
||||||
|
}else{
|
||||||
|
gameEngine.transformControls.detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -10,6 +10,7 @@ class GameEngine {
|
|||||||
const width = 1200, height = 800;
|
const width = 1200, height = 800;
|
||||||
|
|
||||||
const camera = new THREE.PerspectiveCamera(70, width / height, 0.01, 10000);
|
const camera = new THREE.PerspectiveCamera(70, width / height, 0.01, 10000);
|
||||||
|
this.raycaster = new THREE.Raycaster();
|
||||||
camera.position.set(1, 0, 1);
|
camera.position.set(1, 0, 1);
|
||||||
|
|
||||||
const scene = new THREE.Scene();
|
const scene = new THREE.Scene();
|
||||||
@@ -56,6 +57,10 @@ class GameEngine {
|
|||||||
this.camera = camera;
|
this.camera = camera;
|
||||||
this.controls = controls;
|
this.controls = controls;
|
||||||
this.mixer = mixer;
|
this.mixer = mixer;
|
||||||
|
|
||||||
|
this.activeObjects = new THREE.Group();
|
||||||
|
scene.add(this.activeObjects);
|
||||||
|
|
||||||
domNode.appendChild(renderer.domElement);
|
domNode.appendChild(renderer.domElement);
|
||||||
|
|
||||||
let texture = await this.loadTexture('/static/textures/bck.webp');
|
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){
|
async captureScreenshot(type = 'image/webp', quality = 80){
|
||||||
return new Promise((resolve, reject)=>{
|
return new Promise((resolve, reject)=>{
|
||||||
this.renderer.domElement.toBlob(resolve, type, quality)
|
this.renderer.domElement.toBlob(resolve, type, quality)
|
||||||
@@ -107,6 +119,46 @@ class GameEngine {
|
|||||||
stop(){
|
stop(){
|
||||||
this.renderer.setAnimationLoop(null);
|
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}
|
export {GameEngine}
|
||||||
@@ -1,9 +1,84 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<v-container max-width="1400">
|
||||||
|
<v-tabs v-model="panel">
|
||||||
|
<v-tab value="game">
|
||||||
|
<v-icon icon="mdi-pencil"></v-icon> {{ id == 'add' ? l.createGame : l.editGame }}
|
||||||
|
</v-tab>
|
||||||
|
<v-tab value="gameDesigner" v-if="scenario">
|
||||||
|
<v-icon icon="mdi-movie-open-outline"></v-icon> {{ l.gameDesigner }}
|
||||||
|
</v-tab>
|
||||||
|
</v-tabs>
|
||||||
|
<v-tabs-window v-model="panel">
|
||||||
|
<v-tabs-window-item value="game">
|
||||||
|
<v-form class="pa-4" v-model="valid">
|
||||||
|
<v-text-field :label="l.name" v-model="object.name" :rules="[rules.required]"></v-text-field>
|
||||||
|
<v-textarea :label="l.description" v-model="object.description"></v-textarea>
|
||||||
|
<v-select :label="l.scenario" :items="scenarios" v-model="object.scenario" item-title="name" item-value="id"></v-select>
|
||||||
|
</v-form>
|
||||||
|
</v-tabs-window-item>
|
||||||
|
<v-tabs-window-item value="gameDesigner">
|
||||||
|
<GameDesigner v-model="object" :scenario="scenario" ref="gameDesigner" ></GameDesigner>
|
||||||
|
</v-tabs-window-item>
|
||||||
|
</v-tabs-window>
|
||||||
|
<v-btn @click="save" :loading="loading" prepend-icon="mdi-content-save" color="primary">{{ l.save }}</v-btn>
|
||||||
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
object: {},
|
||||||
|
valid: false,
|
||||||
|
rules: {
|
||||||
|
required: v => v ? true : this.l.fieldRequired,
|
||||||
|
requiredFile: v => (v?.length || this.id != 'add') ? true : this.l.fieldRequired
|
||||||
|
},
|
||||||
|
loading: false,
|
||||||
|
panel: [],
|
||||||
|
scenarios: [],
|
||||||
|
scenario: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted(){
|
||||||
|
if (this.id && this.id != 'add') {
|
||||||
|
this.object = (await this.$api.game.load(this.id)).data;
|
||||||
|
}
|
||||||
|
this.scenarios = (await this.$api.scenario.search()).data.data;
|
||||||
|
await this.loadScenario()
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
id() {
|
||||||
|
return this.$route.params?.id;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch:{
|
||||||
|
async 'object.scenario'(n){
|
||||||
|
await this.loadScenario()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods:{
|
||||||
|
async loadScenario(){
|
||||||
|
if (this.object.scenario){
|
||||||
|
this.scenario = (await this.$api.scenario.load(this.object.scenario)).data;
|
||||||
|
}else{
|
||||||
|
this.scenario = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async save(params) {
|
||||||
|
this.loading = true;
|
||||||
|
try {
|
||||||
|
let result = await this.$api.game.save(this.object);
|
||||||
|
Object.assign(this.object, result.data.object);
|
||||||
|
if (this.id == 'add') {
|
||||||
|
this.$router.replace({ params: { id: this.object.id } });
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
this.loading = false
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
<template>
|
||||||
|
<v-container>
|
||||||
|
<v-row>
|
||||||
|
<v-col v-for="(v, i) in items" :key="i" cols="12" xs="6" sm="4" md="3" xl="2" class="position-relative">
|
||||||
|
<router-link :to="`/games/${v.id}`">
|
||||||
|
<v-img :src="`/asset/thumb/${v.thumb?.[0]}.webp`"></v-img>
|
||||||
|
</router-link>
|
||||||
|
<div class="d-flex">
|
||||||
|
<span class="flex-grow-1">{{ v.name }}</span>
|
||||||
|
<v-btn density="compact" variant="text" icon="mdi-pencil-outline" :to="`/games/${v.id}`" color="primary"></v-btn>
|
||||||
|
<v-btn density="compact" variant="text" icon="mdi-close" @click="confirmTarget = v; confirmDialog = true" color="red"></v-btn>
|
||||||
|
</div>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
<v-dialog v-model="confirmDialog" width="auto">
|
||||||
|
<v-card :title="`${l.confirmDeletionOf} ${ confirmTarget.name }?`">
|
||||||
|
<template v-slot:actions>
|
||||||
|
<v-btn @click="confirmDialog = false">{{ l.no }}</v-btn>
|
||||||
|
<v-btn color="red-darken-4" @click="remove(confirmTarget)">{{ l.yes }}</v-btn>
|
||||||
|
</template>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data(){
|
||||||
|
return {
|
||||||
|
items: [],
|
||||||
|
confirmDialog: false,
|
||||||
|
confirmTarget: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async created(){
|
||||||
|
this.items = (await this.$api.game.search()).data.data
|
||||||
|
},
|
||||||
|
|
||||||
|
methods:{
|
||||||
|
async remove(item){
|
||||||
|
await this.$api.game.remove(item.id);
|
||||||
|
this.confirmDialog = false;
|
||||||
|
this.items.splice(this.items.indexOf(item), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -46,6 +46,20 @@ export default {
|
|||||||
async remove(id){
|
async remove(id){
|
||||||
return await $ax.delete(`/scenario/${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}`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ const lang = {
|
|||||||
_code: 'en',
|
_code: 'en',
|
||||||
createGameObject: 'Add game object',
|
createGameObject: 'Add game object',
|
||||||
editGameObject: 'Edit game object',
|
editGameObject: 'Edit game object',
|
||||||
|
createGame: 'Add game',
|
||||||
|
editGame: 'Edit game',
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
description: 'Description',
|
description: 'Description',
|
||||||
fieldRequired: 'Field is required',
|
fieldRequired: 'Field is required',
|
||||||
@@ -23,11 +25,13 @@ const lang = {
|
|||||||
gameObjects: 'Objects',
|
gameObjects: 'Objects',
|
||||||
gameScenarios: 'Scenarios',
|
gameScenarios: 'Scenarios',
|
||||||
gameRules: 'Rules',
|
gameRules: 'Rules',
|
||||||
|
gameDesigner: 'Game studio',
|
||||||
games: 'Games',
|
games: 'Games',
|
||||||
darkMode: 'Dark mode',
|
darkMode: 'Dark mode',
|
||||||
confirmDeletionOf: 'Confirm deletion of',
|
confirmDeletionOf: 'Confirm deletion of',
|
||||||
yes: 'Yes',
|
yes: 'Yes',
|
||||||
no: 'No',
|
no: 'No',
|
||||||
|
scenario: 'Scenario',
|
||||||
createScenario: 'Create scenario',
|
createScenario: 'Create scenario',
|
||||||
editScenario: 'Edit scenario',
|
editScenario: 'Edit scenario',
|
||||||
editScenes: 'Edit scenes',
|
editScenes: 'Edit scenes',
|
||||||
@@ -38,6 +42,8 @@ const lang = {
|
|||||||
_code: 'bg',
|
_code: 'bg',
|
||||||
createGameObject: 'Добавяне на игрови обект',
|
createGameObject: 'Добавяне на игрови обект',
|
||||||
editGameObject: 'Редактиране на игрови обект',
|
editGameObject: 'Редактиране на игрови обект',
|
||||||
|
createGame: 'Добавяне на игра',
|
||||||
|
editGame: 'Редактиране на игра',
|
||||||
name: 'Име',
|
name: 'Име',
|
||||||
description: 'Описание',
|
description: 'Описание',
|
||||||
fieldRequired: 'Полето е задължително',
|
fieldRequired: 'Полето е задължително',
|
||||||
@@ -58,11 +64,13 @@ const lang = {
|
|||||||
gameObjects: 'Обекти',
|
gameObjects: 'Обекти',
|
||||||
gameScenarios: 'Сценарии',
|
gameScenarios: 'Сценарии',
|
||||||
gameRules: 'Правила',
|
gameRules: 'Правила',
|
||||||
|
gameDesigner: 'Студио',
|
||||||
games: 'Игри',
|
games: 'Игри',
|
||||||
darkMode: 'Тъмен режим',
|
darkMode: 'Тъмен режим',
|
||||||
confirmDeletionOf: 'Потвърдете изтриването на',
|
confirmDeletionOf: 'Потвърдете изтриването на',
|
||||||
yes: 'Да',
|
yes: 'Да',
|
||||||
no: 'Не',
|
no: 'Не',
|
||||||
|
scenario: 'Сценарий',
|
||||||
createScenario: 'Създаване на сценарий',
|
createScenario: 'Създаване на сценарий',
|
||||||
editScenario: 'Редактиране на сценарий',
|
editScenario: 'Редактиране на сценарий',
|
||||||
editScenes: 'Редактиране на сцени',
|
editScenes: 'Редактиране на сцени',
|
||||||
|
|||||||
Reference in New Issue
Block a user