From a2f9f73e85a41a0c7cb3ffbe99bbbbe0714cc27f Mon Sep 17 00:00:00 2001 From: goynov Date: Thu, 13 Nov 2025 16:32:55 +0200 Subject: [PATCH] objects locking system --- src/components/AppHeader.vue | 2 +- .../InteractiveObjects/GenenricObject.js | 14 ------ .../InteractiveObjects/InteractiveObject.js | 45 ++++++++++++++++++- .../MazeQuizGame/MazeQuizGame.vue | 3 +- .../InteractiveObjects/VideoPlayer.js | 2 +- src/lib/Clickable.js | 8 +++- src/lib/Dashboard.js | 22 ++++----- src/lib/GameEngine.js | 31 ++++++++----- src/lib/MeshUtils.js | 20 ++++++--- src/mixins/GameEnvironmentMixin.js | 28 ++++++++---- 10 files changed, 119 insertions(+), 56 deletions(-) diff --git a/src/components/AppHeader.vue b/src/components/AppHeader.vue index a10db61..6082565 100644 --- a/src/components/AppHeader.vue +++ b/src/components/AppHeader.vue @@ -22,7 +22,7 @@ - + diff --git a/src/components/InteractiveObjects/GenenricObject.js b/src/components/InteractiveObjects/GenenricObject.js index e059fc6..e807497 100644 --- a/src/components/InteractiveObjects/GenenricObject.js +++ b/src/components/InteractiveObjects/GenenricObject.js @@ -9,20 +9,6 @@ class GenericObject extends EventDispatcher{ this.object = centerOrigin(this.source.scene) if (!data.exclude){ - const bckGeometry = new SphereGeometry(getBoundingBoxMaxLength(this.object.userData.bbox)/2,8,8) - const bckMesh = new Mesh(bckGeometry, new MeshStandardMaterial({ - transparent: true, - opacity:1, color: 0xaaaaaa, - alphaMap: await engine.loadTexture('locked.webp', '/static/textures/'), - })) - this.object.add(bckMesh) - engine.motionQueue.add( - { o: bckMesh, a:{ - material: { opacity: k=>0.11 + 0.52*Math.sin(k*Math.PI)}, - rotation: { y: k=>k*Math.PI*2}, - scale: (k,s)=>s.setScalar(1+0.11*Math.sin(2*k*Math.PI)) - }, r: true, t: 3 }, - ) engine.clickable.add(this.object, async e=>{ this.object._active = !this.object._active; if (engine.dashboard){ diff --git a/src/components/InteractiveObjects/InteractiveObject.js b/src/components/InteractiveObjects/InteractiveObject.js index c85e7d4..167a1ce 100644 --- a/src/components/InteractiveObjects/InteractiveObject.js +++ b/src/components/InteractiveObjects/InteractiveObject.js @@ -1,4 +1,4 @@ -import { Group, EventDispatcher } from "three"; +import { Group, EventDispatcher, MeshStandardMaterial, Mesh, SphereGeometry } from "three"; import { GenericObject } from "./GenenricObject"; import { TextObject } from "./TextObject"; @@ -13,7 +13,8 @@ import { PuzzleGame4 } from "./PuzzleGame4"; // import { Game6 } from "./games/Game6"; import { MazeQuizGame } from "./MazeQuizGame/MazeQuizGame"; import { Particles } from "./Particles"; -import { assignMaterial, assignParams } from "@/lib/MeshUtils"; +import { assignMaterial, assignParams, wrapInGroup, getBoundingBoxMaxLength } from "@/lib/MeshUtils"; +import { GameEngine } from "@/lib/GameEngine"; const InteractiveObjectsImports = { GenericObject, CharacterObject, TextObject, ImageObject, VideoPlayer, Particles, @@ -72,12 +73,52 @@ class InteractiveObject extends EventDispatcher{ this.io.addEventListener?.('finish', this.dispatchEvent.bind(this)) break; } + if (obj.shouldBeLocked){ + this.object = wrapInGroup(this.object) + this.locker = new Locker(gameEngine, this.object); + this.locker.lock(); + } assignParams(this.object, obj); resolve(this); }); } } +class Locker{ + static materialLocked = new MeshStandardMaterial({ + transparent: true, opacity:1, color: 0xaaaaaa + }) + constructor(engine, group){ + const bckGeometry = new SphereGeometry(getBoundingBoxMaxLength(group.userData.bbox)/2,8,8) + const bckMesh = new Mesh(bckGeometry, Locker.materialLocked); + bckMesh.visible = false; + group.add(bckMesh) + function animate(){ + engine.motionQueue.add( + { o: bckMesh, a:{ + material: { opacity: k=>0.11 + 0.52*Math.sin(k*Math.PI)}, + rotation: { y: k=>k*Math.PI*2}, + scale: (k,s)=>s.setScalar(1+0.11*Math.sin(2*k*Math.PI)) + }, r: true, t: 3 }, + ) + } + this.object = bckMesh; + this.lock = function(){ + bckMesh.visible = true; + //bckMesh.material.needsUpdate = true; + engine.motionQueue.clear(bckMesh); + group.__locked = true; + animate(); + } + this.unlock = function(){ + bckMesh.visible = false; + engine.motionQueue.clear(bckMesh); + group.__locked = false; + } + } +} +GameEngine.loadTexture('locked.webp', '/static/textures/', null, [Locker.materialLocked, 'alphaMap']) + const InteractiveObjectTypes = [ { id: 'CharacterObject', name: 'Character' diff --git a/src/components/InteractiveObjects/MazeQuizGame/MazeQuizGame.vue b/src/components/InteractiveObjects/MazeQuizGame/MazeQuizGame.vue index 7b72bdd..6ee5bc2 100644 --- a/src/components/InteractiveObjects/MazeQuizGame/MazeQuizGame.vue +++ b/src/components/InteractiveObjects/MazeQuizGame/MazeQuizGame.vue @@ -47,7 +47,8 @@ export default { mounted(){ this.modelValue.questions ??= []; this.modelValue.questionPoints ??= 10; - this.modelValue.questionPenalty ??= 0 + this.modelValue.questionPenalty ??= 0; + this.modelValue.exclude = true; }, methods:{ addQuestion(){ diff --git a/src/components/InteractiveObjects/VideoPlayer.js b/src/components/InteractiveObjects/VideoPlayer.js index 3eecf96..4fcd49d 100644 --- a/src/components/InteractiveObjects/VideoPlayer.js +++ b/src/components/InteractiveObjects/VideoPlayer.js @@ -44,7 +44,7 @@ class VideoPlayer extends EventDispatcher { //material.opacity = 0.5; if (data.playInHud){ engine.dashboard?.detach(plane, { - skipTransition: !data.skipTransition + skipTransition: data.skipTransition }); } }) diff --git a/src/lib/Clickable.js b/src/lib/Clickable.js index 81db71a..7235978 100644 --- a/src/lib/Clickable.js +++ b/src/lib/Clickable.js @@ -24,7 +24,13 @@ class Clickable { this.update = function (mouse, camera, event) { raycaster.setFromCamera(mouse, camera); let forExecute = []; - objects.forEach(o => { + objects.filter(o=>{ + do { + if (o.__locked) return false; + o = o.parent; + } while (o); + return true; + }).forEach(o => { o.getWorldPosition(v); if (camera.position.distanceTo(v) <= o._clickable.distance && o.visible) { const intersects = raycaster.intersectObject(o); diff --git a/src/lib/Dashboard.js b/src/lib/Dashboard.js index 66a663d..460264e 100644 --- a/src/lib/Dashboard.js +++ b/src/lib/Dashboard.js @@ -5,6 +5,7 @@ import { import { Text } from "troika-three-text"; class DashBoard { + #points = 0; constructor(engine) { let svg = p=>` @@ -25,7 +26,6 @@ class DashBoard { let updating = false; let params = {} let occupied = false; - let points = 0; img.addEventListener('load', function () { ctx.drawImage(img, 0, 0, engine.w, engine.h); @@ -152,13 +152,13 @@ class DashBoard { this.levelProgress = levelProgress; this.addPoints = function(p){ - points += p; + this.#points += p; engine.motionQueue.add({ o: pointsText, a: {rotation:{y: Math.PI}, scale:{x:1.5, y:1.5, z:1.5}}, t: 0.25, f:()=>{ - pointsText.text = points; + pointsText.text = this.#points; pointsText.sync(); engine.motionQueue.add({ o: pointsText, @@ -242,13 +242,11 @@ class DashBoard { object._hud.parent?.attach(object); hud.rotation.y = 0; hud.visible = false; - if (!opts.skipTransition){ - engine.motionQueue.add({ - o: object, - a: object._hud.placement, - t: 1 - }); - } + engine.motionQueue.add({ + o: object, + a: object._hud.placement, + t: opts.skipTransition ? 0 : 1 + }); delete object._hud; occupied = false; hudAnimation = null; @@ -268,6 +266,10 @@ class DashBoard { set active(v){ this.group.visible = v; } + + get points(){ + return this.#points; + } } class ProgressBar{ diff --git a/src/lib/GameEngine.js b/src/lib/GameEngine.js index 895c369..0dab93e 100644 --- a/src/lib/GameEngine.js +++ b/src/lib/GameEngine.js @@ -16,6 +16,8 @@ import { Clickable } from './Clickable.js'; import { DashBoard } from './Dashboard.js'; import { MotionEngine } from './MotionEngine.js'; +THREE.Cache.enabled = true + const assetPath = '/asset/default/'; class GameEngine extends THREE.EventDispatcher{ @@ -161,7 +163,7 @@ class GameEngine extends THREE.EventDispatcher{ domNode.appendChild(renderer.domElement); - let texture = await this.loadTexture('/static/textures/bck.webp', ''); + let texture = await GameEngine.loadTexture('/static/textures/bck.webp', ''); // let bck = await this.loadTexture('/static/textures/bck.webp'); // bck.premultiplyAlpha = true; texture.mapping = THREE.EquirectangularReflectionMapping; @@ -415,18 +417,8 @@ class GameEngine extends THREE.EventDispatcher{ }) } - async loadTexture(url, path = assetPath, progress) { - return new Promise((resolve, reject) => { - new THREE.TextureLoader().load(`${path}${url}`, texture => { - //texture.encoding = THREE.sRGBEncoding; - texture.colorSpace = THREE.SRGBColorSpace; - resolve(texture) - }, progress, reject) - }) - } - async loadPanorama(url, path = assetPath) { - let t = await this.loadTexture(url, path); + let t = await GameEngine.loadTexture(url, path); t.mapping = THREE.EquirectangularReflectionMapping; this.scene.background = t; this.scene.environment = t; @@ -567,6 +559,21 @@ class GameEngine extends THREE.EventDispatcher{ this.clickable.removeAll(); this.motionQueue.clearAll(); } + + static textureLoader = new THREE.TextureLoader(); + static async loadTexture(url, path = assetPath, progress, assignTo) { + return new Promise((resolve, reject) => { + GameEngine.textureLoader.load(`${path}${url}`, texture => { + texture.colorSpace = THREE.SRGBColorSpace; + if (assignTo){ + assignTo[0][assignTo[1]] = texture; + } + resolve(texture) + }, progress, reject) + }) + } + + loadTexture = GameEngine.loadTexture } export { GameEngine } \ No newline at end of file diff --git a/src/lib/MeshUtils.js b/src/lib/MeshUtils.js index 47cde5e..5bc6b74 100644 --- a/src/lib/MeshUtils.js +++ b/src/lib/MeshUtils.js @@ -55,16 +55,24 @@ function autoScale(object, mk = 1) { object.scale.multiplyScalar(mk / k); } +function wrapInGroup(object){ + if (object.isWrapper) return object; + let group = new Group(); + group.userData.bbox = getBoundingBox(object); + group.add(object); + group.userData.object = object; + group.isWrapper = true; + return group; +} + function centerOrigin(object){ - let result = new Group(); - result.userData.bbox = getBoundingBox(object); - let position = getBoundingBoxCenterPoint(result.userData.bbox, object.position).negate(); + let group = wrapInGroup(object); + let position = getBoundingBoxCenterPoint(group.userData.bbox, object.position).negate(); object.position.copy(position) - result.add(object); - return result; + return group; } export { - assignParams, assignMaterial, autoScale, centerOrigin, + assignParams, assignMaterial, autoScale, centerOrigin, wrapInGroup, getBoundingBox, getBoundingBoxSize, getBoundingBoxMaxLength, getBoundingBoxCenterPoint } \ No newline at end of file diff --git a/src/mixins/GameEnvironmentMixin.js b/src/mixins/GameEnvironmentMixin.js index 3159bfc..7422558 100644 --- a/src/mixins/GameEnvironmentMixin.js +++ b/src/mixins/GameEnvironmentMixin.js @@ -146,7 +146,13 @@ export default { if (this.scene.data.items){ let loaded = 0; for (let i of this.scene.data.items) { + if (this.env == 'GamePlaying'){ + if (i.data.activationTriggers?.length || i.data.activationScore){ + i.data.shouldBeLocked = true; + } + } let io = await new InteractiveObject(gameEngine, i.data) + i.__io = io; this.setObjectAttributes(l, i.data, io.object, io.source, 1); gameEngine.activeObjects.add(io.object); if (this.env == 'GamePlaying'){ @@ -167,8 +173,20 @@ export default { } } io.addEventListener('finish', ()=>{ - gameEngine.dashboard?.addPoints(i.data.points) - i.data.points = 0; + if (!i.data.pointsGiven){ + gameEngine.dashboard?.addPoints(i.data.points) + } + i.data.pointsGiven = true; + this.scene.data.items.forEach(di=>{ + if (di.data.activationTriggers?.includes(i.data.id)){ + let idx = di.data.activationTriggers.indexOf(i.data.id) + di.data.activationTriggers.splice(idx, 1); + } + if (!di.data.activationTriggers?.length && di.__io.object.__locked && + gameEngine.dashboard.points > di.data.activationScore){ + di.__io.locker.unlock(); + } + }) }) } loaded += 1/this.scene.data.items.length @@ -189,12 +207,6 @@ export default { } gameEngine.dashboard?.loading(1) - // let camera = new gameEngine.$.PerspectiveCamera(); - // let cameraHelper = new gameEngine.$.CameraHelper(camera); - // gameEngine.activeObjects.add(cameraHelper); - // gameEngine.activeObjects.add(camera); - // this.setObjectAttributes(l, { id: 'camera', 'title': 'Main camera' }, { scene: camera }) - // cameraHelper.update(); }, targetPointerDown(){