loading progress added

This commit is contained in:
2025-11-11 08:09:07 +02:00
parent 03edeaef2d
commit 35fa1863ff
11 changed files with 170 additions and 95 deletions
@@ -27,7 +27,9 @@ class GenericObject{
z: scale*this.object.scale.z z: scale*this.object.scale.z
} }
} }
await engine.dashboard.attach(this.object, placement, true, true); await engine.dashboard.attach(this.object, {
placement, rotate: true, plane: true
});
} }
} }
if (data.description){ if (data.description){
@@ -16,8 +16,9 @@ import { Particles } from "./Particles";
import { assignMaterial, assignParams } from "@/lib/MeshUtils"; import { assignMaterial, assignParams } from "@/lib/MeshUtils";
const InteractiveObjectsImports = { const InteractiveObjectsImports = {
PuzzleGame1, PuzzleGame2, PuzzleGame4, PuzzleGame1, PuzzleGame2, PuzzleGame4, VideoPlayer,
GenericObject, CharacterObject, MazeQuizGame, Particles }; GenericObject, CharacterObject, MazeQuizGame, Particles
};
class InteractiveObject { class InteractiveObject {
constructor(gameEngine, obj) { constructor(gameEngine, obj) {
@@ -77,9 +78,6 @@ class InteractiveObject {
this.object.game = game; this.object.game = game;
break; break;
case 'VideoPlayer': case 'VideoPlayer':
this.source = await new VideoPlayer(gameEngine, obj.$go.asset.name, gameEngine.assetPath);
this.object = this.source.object;
break;
case 'PuzzleGame1': case 'PuzzleGame1':
case 'PuzzleGame2': case 'PuzzleGame2':
case 'MazeQuizGame': case 'MazeQuizGame':
@@ -47,7 +47,7 @@ class MazeQuizGame {
let ud = {...ud1, ...ud2} let ud = {...ud1, ...ud2}
if (ud?.finish){ if (ud?.finish){
if (e.started){ if (e.started){
engine.dashboard.updateProgress(1) engine.dashboard.levelProgress.update(1)
engine.hero.animationsMap._idle = engine.hero.animationsMap.idle engine.hero.animationsMap._idle = engine.hero.animationsMap.idle
if ( engine.hero.animationsMap?.win){ if ( engine.hero.animationsMap?.win){
engine.hero.animationsMap.idle = engine.hero.animationsMap.win engine.hero.animationsMap.idle = engine.hero.animationsMap.win
@@ -65,7 +65,7 @@ class MazeQuizGame {
} }
if (ud.qid !== undefined && e.started){ if (ud.qid !== undefined && e.started){
engine.dashboard.updateText(ud.question.q) engine.dashboard.updateText(ud.question.q)
engine.dashboard.updateProgress(ud.qid / questions.length) engine.dashboard.levelProgress.update(ud.qid / questions.length)
} }
//console.log(e, ud, engine.hero?.animationsMap); //console.log(e, ud, engine.hero?.animationsMap);
}) })
@@ -1,5 +1,4 @@
<template> <template>
<v-checkbox v-model="modelValue.shuffle" label="Shuffle questions"></v-checkbox>
<v-dialog max-width="1400" scrollable> <v-dialog max-width="1400" scrollable>
<template v-slot:activator="{ props: activatorProps }"> <template v-slot:activator="{ props: activatorProps }">
<v-btn v-bind="activatorProps">Manage Questions</v-btn> <v-btn v-bind="activatorProps">Manage Questions</v-btn>
@@ -30,10 +29,16 @@
</v-card> </v-card>
</div> </div>
</v-card-text> </v-card-text>
<v-btn @click="addQuestion" block>Add question</v-btn> <v-card-actions>
<v-btn color="success" @click="addQuestion" block>Add question</v-btn>
<v-btn color="primary" @click="isActive.value = false" >Close</v-btn>
</v-card-actions>
</v-card> </v-card>
</template> </template>
</v-dialog> </v-dialog>
<v-checkbox v-model="modelValue.shuffle" hide-details label="Shuffle questions"></v-checkbox>
<v-number-input density="compact" label="Correct answer points" v-model="modelValue.questionPoints"></v-number-input>
<v-number-input density="compact" label="Wrong answer penalty points" v-model="modelValue.questionPenalty"></v-number-input>
</template> </template>
<script> <script>
@@ -41,6 +46,8 @@ export default {
props:['modelValue'], props:['modelValue'],
mounted(){ mounted(){
this.modelValue.questions ??= []; this.modelValue.questions ??= [];
this.modelValue.questionPoints ??= 10;
this.modelValue.questionPenalty ??= 0
}, },
methods:{ methods:{
addQuestion(){ addQuestion(){
@@ -1,4 +1,4 @@
import { BoxGeometry, Mesh, MeshBasicMaterial, Group } from 'three'; import { BoxGeometry, Mesh, MeshStandardMaterial, Group } from 'three';
import { MotionEngine } from '../../lib/MotionEngine'; import { MotionEngine } from '../../lib/MotionEngine';
import { centerOrigin } from '@/lib/MeshUtils'; import { centerOrigin } from '@/lib/MeshUtils';
@@ -13,8 +13,10 @@ class PuzzleGame1 {
let bm = new BoxGeometry(1, 1, 1); let bm = new BoxGeometry(1, 1, 1);
let material = new MeshBasicMaterial({ let material = new MeshStandardMaterial({
map: await engine.loadTexture(data.$go.asset.name) map: await engine.loadTexture(data.$go.asset.name),
// roughness:1, metalness:0,
// normalMap: await engine.loadTexture('NormalMap.png', '/static/textures/'),
}); });
//material.map.encoding = sRGBEncoding; //material.map.encoding = sRGBEncoding;
@@ -2,11 +2,11 @@ import * as THREE from 'three';
class VideoPlayer { class VideoPlayer {
constructor(engine, src, path = ''){ constructor(engine, data){
let vi, plane; let vi, plane;
return new Promise((resolve, reject)=>{ return new Promise((resolve, reject)=>{
vi = document.createElement('video'); vi = document.createElement('video');
vi.src = path + src; vi.src = engine.assetPath + data.$go.asset.name;
vi.addEventListener('loadedmetadata', ()=>{ vi.addEventListener('loadedmetadata', ()=>{
this.aspect = vi.videoWidth / vi.videoHeight; this.aspect = vi.videoWidth / vi.videoHeight;
let geometry = new THREE.PlaneGeometry( this.aspect, 1 ); let geometry = new THREE.PlaneGeometry( this.aspect, 1 );
@@ -31,13 +31,22 @@ class VideoPlayer {
vi.addEventListener('play', ()=>{ vi.addEventListener('play', ()=>{
material.opacity = 1 material.opacity = 1
if (engine.dashboard?.active){ if (data.playInHud && engine.dashboard?.active){
engine.dashboard.attach(plane); engine.dashboard.attach(plane, {
skipTransition: data.skipTransition
});
} }
}) })
vi.addEventListener('pause', ()=>{ vi.addEventListener('pause', ()=>{
material.opacity = 0.5; //material.opacity = 0.5;
engine.dashboard?.detach(plane); if (data.playInHud){
engine.dashboard?.detach(plane, {
skipTransition: !data.skipTransition
});
}
})
vi.addEventListener('ended', ()=>{
this.onfinish?.();
}) })
this.video = vi; this.video = vi;
@@ -1,10 +1,9 @@
<template> <template>
<v-card v-if="modelValue.go"> <div v-if="modelValue.go">
<v-card-item>
<v-img :src="`/asset/thumb/${modelValue.go}.webp`" /> <v-img :src="`/asset/thumb/${modelValue.go}.webp`" />
<div class="text-caption text-center">{{ modelValue.title }}</div> <div class="text-caption text-center">{{ modelValue.title }}</div>
</v-card-item> <v-checkbox density="compact" v-model="modelValue.playInHud" hide-details label="Play in full screen"></v-checkbox>
</v-card> </div>
<asset-selector @select="assignVideoObject" :type="['Video']"> <asset-selector @select="assignVideoObject" :type="['Video']">
<template v-slot:activator="props"> <template v-slot:activator="props">
<v-btn v-bind="props" prepend-icon="mdi-video-box" color="deep-orange-darken-4" block>Choose video object</v-btn> <v-btn v-bind="props" prepend-icon="mdi-video-box" color="deep-orange-darken-4" block>Choose video object</v-btn>
@@ -19,6 +19,7 @@
<v-form class="pt-4"> <v-form class="pt-4">
<v-text-field density="compact" :label="l.name" v-model="modelValue.title"></v-text-field> <v-text-field density="compact" :label="l.name" v-model="modelValue.title"></v-text-field>
<v-text-field density="compact" :label="l.id" v-model="modelValue.id"></v-text-field> <v-text-field density="compact" :label="l.id" v-model="modelValue.id"></v-text-field>
<v-number-input density="compact" label="Completion points" v-model="modelValue.points"></v-number-input>
</v-form> </v-form>
</v-card> </v-card>
@@ -49,6 +50,7 @@ export default {
}, },
mounted(){ mounted(){
this.active = true; this.active = true;
this.modelValue.points ??= 10;
}, },
props:{ props:{
//context: Object, //context: Object,
+67 -35
View File
@@ -18,13 +18,14 @@ class DashBoard {
<text x="90%" text-anchor="middle" y="8%" font-family="MyriadPro-Regular, &apos;Myriad Pro&apos;" font-size="150%">Points</text> <text x="90%" text-anchor="middle" y="8%" font-family="MyriadPro-Regular, &apos;Myriad Pro&apos;" font-size="150%">Points</text>
</svg>`; </svg>`;
let img = new Image(), url, progressCylinder; let img = new Image(), url, levelProgress;
let canvas = document.createElement('canvas'); let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d'); let ctx = canvas.getContext('2d');
let texture = new CanvasTexture(canvas) let texture = new CanvasTexture(canvas)
let updating = false; let updating = false;
let params = {} let params = {}
let occupied = false; let occupied = false;
let points = 0;
img.addEventListener('load', function () { img.addEventListener('load', function () {
ctx.drawImage(img, 0, 0, engine.w, engine.h); ctx.drawImage(img, 0, 0, engine.w, engine.h);
@@ -50,6 +51,18 @@ class DashBoard {
dash.add(dashMesh); dash.add(dashMesh);
engine.scene.add(dash); engine.scene.add(dash);
const loadingPlane = new Mesh(
new PlaneGeometry(engine.aspect, 1),
new MeshBasicMaterial({
color:0xFAFAFA,
})
);
const loadingProgress = new ProgressBar();
loadingProgress.object.scale.set(engine.aspect*0.8, 0.05, 0.05)
loadingProgress.object.position.set(-engine.aspect/2 + engine.aspect*0.1, 0, 0.1)
loadingPlane.add(loadingProgress.object);
dash.add(loadingPlane);
(async()=>{ (async()=>{
hudPlane = new Mesh( hudPlane = new Mesh(
new PlaneGeometry(engine.aspect, 1), new PlaneGeometry(engine.aspect, 1),
@@ -122,30 +135,15 @@ class DashBoard {
}) })
} }
this.createProgressBar = function(){ levelProgress = new ProgressBar({})
const padLeft = engine.aspect/30; dash.add(levelProgress.object);
const progressGeometry = new CylinderGeometry( 0.5, 0.5, 1, 3, 1, false, 0, Math.PI ); levelProgress.object.position.set(-engine.aspect/2 + engine.aspect/30, 0.45, -0.01)
const staticCylinder = new Mesh( progressGeometry, new MeshStandardMaterial({ levelProgress.object.scale.set(engine.aspect/3, 0.02, 0.02)
roughness: 0, metalness:0.1, transparent: true, opacity:0.52, color: 0x55ff00, side: DoubleSide this.levelProgress = levelProgress;
}) );
staticCylinder.rotation.set(-Math.PI/2, 0, Math.PI/2,)
staticCylinder.scale.set(0.02, engine.aspect/3, 0.02)
staticCylinder.position.set(padLeft - engine.aspect/3, 0.45, 0);
dash.add( staticCylinder );
progressCylinder = new Mesh( progressGeometry, new MeshStandardMaterial({ this.addPoints = function(p){
roughness: 0, metalness:0.1, transparent: true, opacity:0.77, color: 0x11ff00 points += p;
}) ); console.log('adding points', p, points)
progressCylinder.rotation.set(Math.PI/2, 0, Math.PI/2,)
progressCylinder.scale.set(0.017, 0, 0.017)
progressCylinder.position.set(0, 0.45, 0);
dash.add( progressCylinder );
}
this.updateProgress = function(value){
const padLeft = engine.aspect/30;
progressCylinder.scale.y = engine.aspect/3 * value;
progressCylinder.position.x = padLeft - engine.aspect/2 + progressCylinder.scale.y/2
} }
this.enable = ()=>{ this.enable = ()=>{
@@ -157,14 +155,15 @@ class DashBoard {
} }
this.reset = ()=>{ this.reset = ()=>{
this.updateProgress(0); this.levelProgress.update(0);
this.updateText(''); this.updateText('');
} }
this.attach = (object, dashPlacement, rotate = false, plane = false)=>{ //dashPlacement, rotate = false, plane = false
this.attach = (object, opts = {})=>{
hud.visible = true; hud.visible = true;
hudPlane.visible = plane; hudPlane.visible = !!opts.plane;
if (plane){ if (opts.plane){
hudPlane.scale.set(0, .1, 1); hudPlane.scale.set(0, .1, 1);
hudPlane.material.opacity = 0.5; hudPlane.material.opacity = 0.5;
engine.motionQueue.add([{ engine.motionQueue.add([{
@@ -190,17 +189,17 @@ class DashBoard {
let result = new Promise((resolve, reject)=>{ let result = new Promise((resolve, reject)=>{
engine.motionQueue.add({ engine.motionQueue.add({
o: object, o: object,
a: dashPlacement || { a: opts.placement || {
quaternion: { x:0, y:0, z:0, w:0 }, quaternion: { x:0, y:0, z:0, w:0 },
position: { x:0, y:0, z:0 }, position: { x:0, y:0, z:0 },
scale: { x: 1, y:1, z:1 } scale: { x: 1, y: 1, z: 1 }
}, },
t: 1, t: opts.skipTransition ? 0 : 1,
f: resolve f: resolve
}) })
}) })
if (rotate){ if (opts.rotate){
engine.motionQueue.add(hudAnimation = { engine.motionQueue.add(hudAnimation = {
o: hudTarget, o: hudTarget,
a: { a: {
@@ -214,22 +213,28 @@ class DashBoard {
return result; return result;
} }
this.detach = object=>{ this.detach = (object, opts = {})=>{
engine.motionQueue.remove(hudAnimation); engine.motionQueue.remove(hudAnimation);
object._hud.parent.attach(object); object._hud.parent?.attach(object);
hud.rotation.y = 0; hud.rotation.y = 0;
hud.visible = false; hud.visible = false;
if (!opts.skipTransition){
engine.motionQueue.add({ engine.motionQueue.add({
o: object, o: object,
a: object._hud.placement, a: object._hud.placement,
t: 1 t: 1
}); });
}
delete object._hud; delete object._hud;
occupied = false; occupied = false;
hudAnimation = null; hudAnimation = null;
} }
this.createProgressBar(); this.loading = function(progress){
loadingPlane.visible = progress > 0 && progress < 1;
loadingProgress.update(progress)
}
this.loading(1);
} }
get active(){ get active(){
@@ -241,4 +246,31 @@ class DashBoard {
} }
} }
class ProgressBar{
constructor(params = {}){
this.object = new Group();
const geometry = new CylinderGeometry( 0.5, 0.5, 1, 3, 1, false, 0, Math.PI );
const staticCylinder = new Mesh( geometry, new MeshStandardMaterial({
roughness: 0, metalness:0.1, transparent: true, opacity:0.52, color: 0x55ff00, side: DoubleSide
}) );
staticCylinder.rotation.set(-Math.PI/2, 0, Math.PI/2,)
staticCylinder.position.x = 0.5;
this.object.add( staticCylinder );
const progressCylinder = new Mesh( geometry, new MeshStandardMaterial({
roughness: 0, metalness:0.1, transparent: true, opacity:0.77, color: 0x11ff00
}) );
progressCylinder.rotation.set(Math.PI/2, 0, Math.PI/2,)
this.object.add( progressCylinder );
this.update = function(value){
progressCylinder.visible = !!value;
progressCylinder.scale.y = value;
progressCylinder.position.x = progressCylinder.scale.y / 2
}
this.update(0)
}
}
export { DashBoard }; export { DashBoard };
+5
View File
@@ -31,7 +31,12 @@ class MotionEngine {
this.add = function (a) { this.add = function (a) {
a = Array.isArray(a) ? a : [a]; a = Array.isArray(a) ? a : [a];
a.forEach(e => { a.forEach(e => {
if (e.t == 0){
e.t = 1;
this.animate(e, 1)
}else{
aq.push(e); aq.push(e);
}
}); });
}; };
+31 -12
View File
@@ -3,6 +3,7 @@ import { VideoPlayer } from '@/components/InteractiveObjects/VideoPlayer';
import { GameEngine } from '@/lib/GameEngine'; import { GameEngine } from '@/lib/GameEngine';
import { Hero } from '@/lib/Hero'; import { Hero } from '@/lib/Hero';
import { autoScale, getBoundingBox, getBoundingBoxCenterPoint, getBoundingBoxSize } from '@/lib/MeshUtils'; import { autoScale, getBoundingBox, getBoundingBoxCenterPoint, getBoundingBoxSize } from '@/lib/MeshUtils';
import Utils from '@/lib/Utils';
let gameEngine = null; let gameEngine = null;
export default { export default {
@@ -117,7 +118,13 @@ export default {
async loadEnvironment(scene, target){ async loadEnvironment(scene, target){
//await gameEngine.loadPanorama(`/asset/default/43.webp`); //await gameEngine.loadPanorama(`/asset/default/43.webp`);
gameEngine.clearScene(); gameEngine.clearScene();
gameEngine.activeObjects.visible = false;
gameEngine.dashboard?.loading(0);
await this.expandScenarioData(scene); await this.expandScenarioData(scene);
gameEngine.dashboard?.loading(0.1);
//this is needed cause when mounted canvas has different size
this.resize();
target.objects = target.objects || {}; target.objects = target.objects || {};
let l = target.objects; let l = target.objects;
if (this.scene.data.$environment){ if (this.scene.data.$environment){
@@ -136,15 +143,9 @@ export default {
} }
gameEngine.activeObjects.add(env.scene); gameEngine.activeObjects.add(env.scene);
} }
if (this.scene.data.$intro && this.env == 'GamePlaying'){ if (this.scene.data.items){
let intro = await new VideoPlayer(gameEngine, this.scene.data.$intro.asset.name, gameEngine.assetPath); let loaded = 0;
gameEngine.activeObjects.add(intro.object); for (let i of this.scene.data.items) {
intro.video.addEventListener('pause',()=>{
intro.object.removeFromParent();
});
intro.video.play();
}
for (let i of this.scene.data.items || []) {
let io = await new InteractiveObject(gameEngine, i.data) let io = await new InteractiveObject(gameEngine, i.data)
//let gltf = await gameEngine.load(`/asset/default/${i.data.$go.asset.name}`); //let gltf = await gameEngine.load(`/asset/default/${i.data.$go.asset.name}`);
@@ -168,17 +169,35 @@ export default {
}) })
} }
} }
io.onfinish=()=>{
gameEngine.dashboard?.addPoints(i.data.points)
i.data.points = 0;
} }
} }
loaded += 1/this.scene.data.items.length
gameEngine.dashboard?.loading(0.1 + 0.89*loaded);
}
}
if (this.scene.data.$intro && this.env == 'GamePlaying'){
let intro = await new VideoPlayer(gameEngine, {$go: this.scene.data.$intro, skipTransition: true, playInHud: true});
gameEngine.activeObjects.add(intro.object);
intro.video.addEventListener('pause',()=>{
intro.object.removeFromParent();
gameEngine.activeObjects.visible = true;
});
intro.video.play();
}else{
gameEngine.activeObjects.visible = true;
}
gameEngine.dashboard?.loading(1)
// let camera = new gameEngine.$.PerspectiveCamera(); // let camera = new gameEngine.$.PerspectiveCamera();
// let cameraHelper = new gameEngine.$.CameraHelper(camera); // let cameraHelper = new gameEngine.$.CameraHelper(camera);
// gameEngine.activeObjects.add(cameraHelper); // gameEngine.activeObjects.add(cameraHelper);
// gameEngine.activeObjects.add(camera); // gameEngine.activeObjects.add(camera);
// this.setObjectAttributes(l, { id: 'camera', 'title': 'Main camera' }, { scene: camera }) // this.setObjectAttributes(l, { id: 'camera', 'title': 'Main camera' }, { scene: camera })
// cameraHelper.update(); // cameraHelper.update();
//this is needed cause when mounted canvas has different size
this.resize();
}, },
targetPointerDown(){ targetPointerDown(){