dashboard and progressbar

This commit is contained in:
2025-10-30 18:54:28 +02:00
parent fb9c5c66e9
commit 6253fc32d7
7 changed files with 123 additions and 71 deletions
Binary file not shown.
@@ -245,6 +245,11 @@ export default {
a: ['Миграционният път на птиците, минаващ покрай Бургаските езера', 'Рядък вид птица', 'Местност в гр. Бургас'], a: ['Миграционният път на птиците, минаващ покрай Бургаските езера', 'Рядък вид птица', 'Местност в гр. Бургас'],
h: 'Грешен отговор. Via Pontica наричаме миграционния път на птиците' h: 'Грешен отговор. Via Pontica наричаме миграционния път на птиците'
}, },
{
q: 'What stands for "Via Pontica"?',
a: ['The migration route of birds passing by the Burgas lakes', 'A rare species of bird', 'Location in Burgas'],
h: 'Wrong answer. Via Pontica is the name given to the migratory route of birds.'
},
{ {
q: 'Къдроглавият пеликан...', q: 'Къдроглавият пеликан...',
a: ['...има огромен жълт клюн', '...има малък розов клюн', '...не се среща в България'], a: ['...има огромен жълт клюн', '...има малък розов клюн', '...не се среща в България'],
@@ -1,6 +1,6 @@
import { Group, Vector3, Matrix4, Mesh, Quaternion, PlaneGeometry, MeshStandardMaterial, DoubleSide } from 'three'; import { Group, Vector3, Matrix4, Mesh, Quaternion, PlaneGeometry, MeshStandardMaterial, DoubleSide, CanvasTexture, SRGBColorSpace } from 'three';
import { InteractiveObject } from '../InteractiveObject'; import { InteractiveObject } from '../InteractiveObject';
import { ActiveEvents } from '@dimforge/rapier3d'; import Utils from '@/lib/Utils';
class MazeObject { class MazeObject {
constructor(engine, def, params = {}){ constructor(engine, def, params = {}){
@@ -22,13 +22,17 @@ class MazeQuizGame {
this.mazeObject = new MazeObject(engine, def) this.mazeObject = new MazeObject(engine, def)
engine.addEventListener('collision', async e=>{ engine.addEventListener('collision', async e=>{
let ud = engine.phy.world.getCollider(e.handle2)?.parent()?.userData; let ud = engine.phy.world.getCollider(e.handle2)?.parent()?.userData;
if (ud?.finish && engine.hero?.animationsMap?.win){ if (ud?.finish){
if (e.started){ if (e.started){
engine.dashboard.updateProgress(1)
engine.hero.animationsMap._idle = engine.hero.animationsMap.idle engine.hero.animationsMap._idle = engine.hero.animationsMap.idle
if ( engine.hero.animationsMap?.win){
engine.hero.animationsMap.idle = engine.hero.animationsMap.win engine.hero.animationsMap.idle = engine.hero.animationsMap.win
engine.hero.characterControls.cameraDelta = Math.PI; }
await Utils.wait(1000); await Utils.wait(1000);
engine.hero.characterControls.cameraDelta = Math.PI;
engine.hero.characterControls.direction += Math.PI; engine.hero.characterControls.direction += Math.PI;
//engine.hero.model.rotation.y += Math.PI;
await Utils.wait(10000); await Utils.wait(10000);
this.onfinish?.() this.onfinish?.()
}else{ }else{
@@ -36,6 +40,12 @@ class MazeQuizGame {
engine.hero.characterControls.cameraDelta = 0 engine.hero.characterControls.cameraDelta = 0
} }
} }
if (ud.qid !== undefined && e.started){
engine.dashboard.update({
hint: ud.question.q
})
engine.dashboard.updateProgress(ud.qid / questions.length)
}
//console.log(e, ud, engine.hero?.animationsMap); //console.log(e, ud, engine.hero?.animationsMap);
}) })
} }
+73 -55
View File
@@ -1,70 +1,88 @@
import { MeshBasicMaterial, TextureLoader, LinearFilter, import { PlaneGeometry, CylinderGeometry, CanvasTexture, Group, Mesh, MeshStandardMaterial, DoubleSide } from "three";
Mesh, import Utils from "./Utils";
OrthographicCamera,
PlaneGeometry,
RGBAFormat,
Scene } from 'three';
import { Text } from 'troika-three-text';
class DashBoard { class DashBoard {
constructor(renderer, width, height) { constructor(engine) {
let svg = p=>`<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<g>
<rect x="0" y="85%" width="100%" height="15%" opacity="0.73" fill="#98d696"/>
<rect x="80%" y="0" width="20%" height="15%" opacity="0.73" fill="#98d696"/>
<rect x="0" y="0" width="20%" height="15%" opacity="0.73" fill="#98d696" visibility="hidden"/>
<circle r="10%" cx="11%" cy="85%" opacity="0.73" fill="#98d696" visibility="hidden"/>
</g>
<text id="hint" text-anchor="middle" x="50%" y="92%" font-family="MyriadPro-Regular, &apos;Myriad Pro&apos;" font-size="150%">${p.hint || ''}</text>
<text x="90%" text-anchor="middle" y="8%" font-family="MyriadPro-Regular, &apos;Myriad Pro&apos;" font-size="150%">Points</text>
</svg>`;
let _camera = new OrthographicCamera(width / -2, width / 2, height / 2, height / -2, 0, 1); let img = new Image(), url, progressCylinder;
let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d');
let texture = new CanvasTexture(canvas)
let updating = false;
let params = {}
let _scene = new Scene(); img.addEventListener('load', function () {
ctx.drawImage(img, 0, 0, engine.w, engine.h);
URL.revokeObjectURL(url);
texture.needsUpdate = true;
updating = false;
})
//let _params = { minFilter: LinearFilter, magFilter: LinearFilter, format: RGBAFormat, stencilBuffer: true }; const dash = new Group();
this.points = 0;
let _texture = new TextureLoader().load('./assets/maze/x.png'); const dashGeometry = new PlaneGeometry(engine.aspect, 1);
const dashMesh = new Mesh(dashGeometry, new MeshStandardMaterial({
roughness: 0, metalness:0.1, transparent: true,
map: texture
}))
let _material = new MeshBasicMaterial({ dash.add(dashMesh);
map: _texture, engine.scene.add(dash)
alphaTest: .5
});
// _mesh = new Mesh( new PlaneGeometry( width * 0.015, width * 0.015 ), _material ); engine.addEventListener('beforeRender', ()=>{
let _text = new Text(); dash.quaternion.copy(engine.camera.quaternion)
_text.font = '/static/fonts/Montserrat-Regular.ttf'; dash.position.copy(engine.camera.position)
_text.text = 'Точки: 0'; dash.translateZ(-1.2 * engine.camera.zoom);
_text.anchorX = 'right'; })
_text.anchorY = 'top';
_text.fontSize = width * 0.015;
_text.position.set(width * .48, height * .47, 0);
_text.color = 0xffffff;
_text.outlineColor = 0x222222;
_text.outlineWidth = '5%';
_text.outlineBlur = '5%';
//_scene.add( _mesh );
_scene.add(_text);
_text.sync();
this.render = function (scene, camera) { this.update = function(p = {}){
renderer.render(_scene, _camera); Object.assign(params, p);
}; if (updating) return false;
updating = true;
canvas.width = engine.w;
canvas.height = engine.h;
url = URL.createObjectURL(new Blob([svg(params)],{ type:"image/svg+xml;charset=utf-8" }));
img.src = url;
}
this.setSize = function (width, height) { this.createProgressBar = function(){
_camera.left = width / -2; const padLeft = engine.aspect/30;
_camera.right = width / 2; const progressGeometry = new CylinderGeometry( 0.5, 0.5, 1, 3, 1, false, 0, Math.PI );
_camera.top = height / 2; const staticCylinder = new Mesh( progressGeometry, new MeshStandardMaterial({
_camera.bottom = height / -2; roughness: 0, metalness:0.1, transparent: true, opacity:0.52, color: 0x55ff00, side: DoubleSide
_camera.updateProjectionMatrix(); }) );
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 );
_text.position.set(width * .48, height * .47, 0); progressCylinder = new Mesh( progressGeometry, new MeshStandardMaterial({
}; roughness: 0, metalness:0.1, transparent: true, opacity:0.77, color: 0x11ff00
}) );
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.addPoints = function (points) { this.updateProgress = function(value){
this.onpoints && this.onpoints(this.points + points, this.points); const padLeft = engine.aspect/30;
this.points += points; progressCylinder.scale.y = engine.aspect/3 * value;
_text.text = 'точки: ' + this.points; progressCylinder.position.x = padLeft - engine.aspect/2 + progressCylinder.scale.y/2
}; }
this.dispose = function () {
if (_mesh) _mesh.geometry.dispose();
if (_material) _material.dispose();
};
this.createProgressBar();
this.update();
} }
} }
+9 -9
View File
@@ -1,11 +1,8 @@
import * as THREE from 'three'; import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/Addons.js'; import { GLTFLoader, DRACOLoader, OrbitControls } from 'three/examples/jsm/Addons.js';
import { DRACOLoader } from 'three/examples/jsm/Addons.js';
import { OrbitControls } from 'three/examples/jsm/Addons.js';
//import { Controller as OrbitControls } from './3rd-party/phy/3TH/Controller.js'; //import { Controller as OrbitControls } from './3rd-party/phy/3TH/Controller.js';
import { ViewportGizmo } from "three-viewport-gizmo"; import { ViewportGizmo } from "three-viewport-gizmo";
import Stats from 'three/examples/jsm/libs/stats.module'; import Stats from 'three/examples/jsm/libs/stats.module';
//import { AnaglyphEffect } from './three/AnaglyphEffect';
import { AnaglyphEffect } from 'three/addons/effects/AnaglyphEffect.js'; import { AnaglyphEffect } from 'three/addons/effects/AnaglyphEffect.js';
import { StereoEffect } from 'three/addons/effects/StereoEffect.js'; import { StereoEffect } from 'three/addons/effects/StereoEffect.js';
import { MapControls } from 'three/addons/controls/MapControls.js'; import { MapControls } from 'three/addons/controls/MapControls.js';
@@ -25,7 +22,7 @@ class GameEngine extends THREE.EventDispatcher{
this.opts = opts; this.opts = opts;
const gameEngine = this; const gameEngine = this;
this.perspectiveCamera = new THREE.PerspectiveCamera(45, this.aspect, 0.01, 250); this.perspectiveCamera = new THREE.PerspectiveCamera(45, this.aspect, 0.01, 200);
this.raycaster = new THREE.Raycaster(); this.raycaster = new THREE.Raycaster();
this.perspectiveCamera.position.set(0, 0, 10); this.perspectiveCamera.position.set(0, 0, 10);
@@ -99,7 +96,7 @@ class GameEngine extends THREE.EventDispatcher{
this.stereo = new StereoEffect(renderer); this.stereo = new StereoEffect(renderer);
this.stereo.setSize(this.w, this.h); this.stereo.setSize(this.w, this.h);
const dashboard = new DashBoard(renderer, this.w, this.h); const dashboard = new DashBoard(this);
this.dashboard = dashboard; this.dashboard = dashboard;
this.activeObjects = new THREE.Group(); this.activeObjects = new THREE.Group();
@@ -132,13 +129,16 @@ class GameEngine extends THREE.EventDispatcher{
gameEngine.hero?.update(); gameEngine.hero?.update();
gameEngine.mixers.forEach(m => m.update(delta)); gameEngine.mixers.forEach(m => m.update(delta));
gameEngine.handleXrAction(gameEngine, delta) gameEngine.handleXrAction(gameEngine, delta)
gameEngine.dispatchEvent({type: 'beforeRender'})
gameEngine.render(scene, gameEngine.camera); gameEngine.render(scene, gameEngine.camera);
if (!renderer.xr.isPresenting) { if (!renderer.xr.isPresenting) {
gameEngine.gizmo?.render(); gameEngine.gizmo?.render();
} }
renderer.autoClear = false; // renderer.autoClear = false;
dashboard.render(); // dashboard.render();
renderer.autoClear = true; // renderer.autoClear = true;
} }
renderer.setAnimationLoop(animate); renderer.setAnimationLoop(animate);
+19
View File
@@ -60,6 +60,25 @@ export default {
.sort((a, b) => a.sort - b.sort).map(({ value }) => value) .sort((a, b) => a.sort - b.sort).map(({ value }) => value)
}, },
drawOnCanvas(svg, width, height){
return new Promise((resolve, reject)=>{
let url = URL.createObjectURL(new Blob([svg],{ type:"image/svg+xml;charset=utf-8" }));
let img = new Image();
let canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
let ctx = canvas.getContext('2d');
img.addEventListener('load', function () {
ctx.drawImage(this, 0, 0, canvas.width, canvas.height);
URL.revokeObjectURL(url);
resolve(canvas);
}, { once: true })
img.src = url;
})
},
async wait(ms){ async wait(ms){
await new Promise((resolve, reject)=>{ await new Promise((resolve, reject)=>{
setTimeout(resolve, ms) setTimeout(resolve, ms)