classic puzzle v1

This commit is contained in:
2025-11-14 18:41:57 +02:00
parent 8eea84b697
commit 3d86e2574a
10 changed files with 2754 additions and 5 deletions
Binary file not shown.
File diff suppressed because it is too large Load Diff
+5 -1
View File
@@ -20,7 +20,11 @@
<v-btn icon="mdi-walk" @click="control"></v-btn> <v-btn icon="mdi-walk" @click="control"></v-btn>
</v-navigation-drawer> </v-navigation-drawer>
<div class="container my-3 position-relative game-designer-canvas"> <div class="container my-3 position-relative game-designer-canvas">
<div ref="target" @click="targetClick" @pointerdown="targetPointerDown"></div> <div ref="target" @click="targetClick"
@mousedown="targetPointer($event, 'start')"
@mousemove="targetPointer($event, 'drag')"
@mouseup="targetPointer($event, 'end')"
@pointerdown="targetPointerDown"></div>
</div> </div>
<!-- <v-toolbar density="compact"> <!-- <v-toolbar density="compact">
<v-slide-group show-arrows> <v-slide-group show-arrows>
@@ -0,0 +1,75 @@
import { Color, Group, EventDispatcher, DoubleSide } from "three"
import { centerOrigin } from "@/lib/MeshUtils";
class ClassicPuzzle extends EventDispatcher {
emits = ['finish']
constructor(engine, data, gltfName, objPrefix='Plane'){
super();
const container = new Group();
const that = this;
return new Promise(async (resolve, reject)=>{
let gltf = await engine.load('puzzle-5x4/puzzle-5x4.gltf', '/static/meshes/');
let dragZone = gltf.scene.getObjectByName('DragZone');
dragZone.material.side = DoubleSide;
let eventsFn= {
start(){},
drag(){},
end(e){
if (Math.abs(e.o.position.x)<.1 && Math.abs(e.o.position.y)<.1){
e.o.position.set(0,0,0);
engine.draggable.remove(e.o);
e.o.material = doneMaterial;
done++;
if (done == pCount){
doneMaterial.emissiveIntensity = .5;
engine.motionQueue.add({
o: doneMaterial,
t:1.5,
d:1,
a:{ emissiveIntensity:0 },
f:()=>{
that.dispatchEvent({type:'finish'})
}
});
}
}
}
}
let pCount = 0;
dragZone.visible = false;
gltf.scene.children.forEach((o, i)=>{
if (o.name.startsWith(objPrefix)){
let pp = o.clone();
container.add(pp);
pp.position.set(2*Math.random()-1, 2*Math.random() - 1, 0.01*(i+1));
engine.draggable.add(pp, dragZone, eventsFn);
pCount++;
}
});
let defaultMaterial = container.children[0].material;
defaultMaterial.emissiveIntensity=.05
let doneMaterial = defaultMaterial.clone();
doneMaterial.emissive = new Color(10,114,10);
defaultMaterial.emissive = new Color(114,10,10);
engine.motionQueue.add(
{
o: defaultMaterial,
r: true,
t:1.5,
d:1,
rd:true,
a:{emissiveIntensity:function(k){return .05*Math.sin(k*3.14);}}
});
let done0 = container.children[Math.floor(Math.random() * container.children.length)];
let done = 1;
done0.material = doneMaterial;
done0.position.set(0,0,0);
engine.draggable.remove(done0)
container.add(dragZone);
this.object = centerOrigin(container);
resolve(this);
})
}
}
export { ClassicPuzzle }
@@ -0,0 +1,38 @@
<template>
<div v-if="modelValue.go">
<v-number-input density="compact" label="Width" v-model="modelValue.w"></v-number-input>
<v-number-input density="compact" label="Height" v-model="modelValue.h"></v-number-input>
<v-img :src="`/asset/thumb/${modelValue.go}.webp`" />
<div class="text-caption text-center">{{ modelValue.title }}</div>
</div>
<asset-selector @select="assignTexture" :type="['Texture']">
<template v-slot:activator="props">
<v-btn v-bind="props" prepend-icon="mdi-video-box" color="deep-orange-darken-4" block>Choose image object</v-btn>
</template>
</asset-selector>
</template>
<script>
export default {
data(){
return {
active: false
}
},
mounted(){
this.active = true;
},
props:{
modelValue: Object
},
methods:{
assignTexture(e){
this.modelValue.go = e.id;
this.modelValue.title = e.name
// this.modelValue.w = 2;
// this.modelValue.h = 3;
}
}
}
</script>
@@ -9,6 +9,7 @@ import { PuzzleGame1 } from "./PuzzleGame1";
import { PuzzleGame2 } from "./PuzzleGame2"; import { PuzzleGame2 } from "./PuzzleGame2";
// import { Game3 } from "./games/Game3"; // import { Game3 } from "./games/Game3";
import { PuzzleGame4 } from "./PuzzleGame4"; import { PuzzleGame4 } from "./PuzzleGame4";
import { ClassicPuzzle } from "./ClassicPuzzle";
// import { Game5 } from "./games/Game5"; // import { Game5 } from "./games/Game5";
// import { Game6 } from "./games/Game6"; // import { Game6 } from "./games/Game6";
import { MazeQuizGame } from "./MazeQuizGame/MazeQuizGame"; import { MazeQuizGame } from "./MazeQuizGame/MazeQuizGame";
@@ -18,7 +19,7 @@ import { GameEngine } from "@/lib/GameEngine";
const InteractiveObjectsImports = { const InteractiveObjectsImports = {
GenericObject, CharacterObject, TextObject, ImageObject, VideoPlayer, Particles, GenericObject, CharacterObject, TextObject, ImageObject, VideoPlayer, Particles,
PuzzleGame1, PuzzleGame2, PuzzleGame4, MazeQuizGame PuzzleGame1, PuzzleGame2, PuzzleGame4, MazeQuizGame, ClassicPuzzle
}; };
class InteractiveObject extends EventDispatcher{ class InteractiveObject extends EventDispatcher{
@@ -66,6 +67,7 @@ class InteractiveObject extends EventDispatcher{
case 'PuzzleGame1': case 'PuzzleGame1':
case 'PuzzleGame2': case 'PuzzleGame2':
case 'MazeQuizGame': case 'MazeQuizGame':
case 'ClassicPuzzle':
case 'Particles': case 'Particles':
this.io = await new InteractiveObjectsImports[obj.type](gameEngine, obj); this.io = await new InteractiveObjectsImports[obj.type](gameEngine, obj);
this.source = this.io.source || this.io; this.source = this.io.source || this.io;
@@ -131,6 +133,8 @@ const InteractiveObjectTypes = [
id: 'PuzzleGame2', name: 'Puzzle Game 2' id: 'PuzzleGame2', name: 'Puzzle Game 2'
},{ },{
id: 'MazeQuizGame', name: 'Maze Quiz Game' id: 'MazeQuizGame', name: 'Maze Quiz Game'
},{
id: 'ClassicPuzzle', name: 'Classic Puzzle Game'
},{ },{
id: 'VideoPlayer', name: 'Video Player' id: 'VideoPlayer', name: 'Video Player'
},{ },{
+6 -3
View File
@@ -43,20 +43,23 @@
<script> <script>
import SvgIcon from './SvgIcon.vue'; import SvgIcon from './SvgIcon.vue';
import Utils from '@/lib/Utils';
import VideoPlayer from '../InteractiveObjects/VideoPlayer.vue'; import VideoPlayer from '../InteractiveObjects/VideoPlayer.vue';
import PuzzleGame1 from '../InteractiveObjects/PuzzleGame1.vue'; import PuzzleGame1 from '../InteractiveObjects/PuzzleGame1.vue';
import PuzzleGame2 from '../InteractiveObjects/PuzzleGame2.vue'; import PuzzleGame2 from '../InteractiveObjects/PuzzleGame2.vue';
import MazeQuizGame from '../InteractiveObjects/MazeQuizGame/MazeQuizGame.vue'; import MazeQuizGame from '../InteractiveObjects/MazeQuizGame/MazeQuizGame.vue';
import ClassicPuzzle from '../InteractiveObjects/ClassicPuzzle.vue';
import Particles from '../InteractiveObjects/Particles.vue'; import Particles from '../InteractiveObjects/Particles.vue';
import GenericObject from '../InteractiveObjects/GenericObject.vue'; import GenericObject from '../InteractiveObjects/GenericObject.vue';
import CharacterObject from '../InteractiveObjects/CharacterObject.vue'; import CharacterObject from '../InteractiveObjects/CharacterObject.vue';
import OffsetLine from './OffsetLine.vue'; import OffsetLine from './OffsetLine.vue';
export default { export default {
emits:['target', 'preview'], emits:['target', 'preview'],
components: { SvgIcon, OffsetLine, GenericObject, CharacterObject, VideoPlayer, PuzzleGame1, PuzzleGame2, MazeQuizGame, Particles, }, components: {
SvgIcon, OffsetLine, GenericObject, CharacterObject, VideoPlayer,
PuzzleGame1, PuzzleGame2, MazeQuizGame, Particles, ClassicPuzzle
},
data(){ data(){
return { return {
active: false active: false
+53
View File
@@ -0,0 +1,53 @@
import { Raycaster, Vector3 } from "three"
class Draggable{
constructor(defaultDistance){
const objects = [];
const raycaster = new Raycaster();
let v = new Vector3;
let dragging = null;
this.add = function(object, dragZone, fn, distance){
objects.push(object);
object._draggable = {fn, dragZone, distance: distance || defaultDistance}
}
this.remove = function(object){
delete object._draggable;
objects.splice(objects.indexOf(object), 1);
}
this.update = function(pointer, camera, action){
raycaster.setFromCamera(pointer, camera);
if (action == 'start'){
let forExecute = [];
objects.forEach(o=>{
o.getWorldPosition(v);
if (camera.position.distanceTo(v) <= o._draggable.distance && o.visible){
const intersects = raycaster.intersectObject(o);
if (intersects[0]) forExecute.push({o, i:intersects[0]})
}
});
if (forExecute[0]) {
let s = forExecute.sort((a,b)=>a.i.distance-b.i.distance)[0];
s.o._draggable.fn.start && s.o._draggable.fn.start(s.i);
dragging = s;
dragging.zone = raycaster.intersectObject(s.o._draggable.dragZone)[0];
}
}else if (action == 'end' && dragging){
dragging.o._draggable.fn.end && dragging.o._draggable.fn.end(dragging);
dragging = null;
}else if(action == 'drag' && dragging){
const intersect = raycaster.intersectObject(dragging.o._draggable.dragZone)[0];
if (intersect?.uv && dragging.zone?.uv){
dragging.o.position.x += -4*(dragging.zone.uv.x - intersect.uv.x);
dragging.o.position.y += 4*(dragging.zone.uv.y - intersect.uv.y);
dragging.o._draggable.fn.drag && dragging.o._draggable.fn.drag(dragging);
dragging.zone = intersect;
}
}
}
}
}
export { Draggable }
+7
View File
@@ -15,6 +15,7 @@ import { Physics } from './Physics.js';
import { Clickable } from './Clickable.js'; import { Clickable } from './Clickable.js';
import { DashBoard } from './Dashboard.js'; import { DashBoard } from './Dashboard.js';
import { MotionEngine } from './MotionEngine.js'; import { MotionEngine } from './MotionEngine.js';
import { Draggable } from './Draggable.js';
THREE.Cache.enabled = true THREE.Cache.enabled = true
@@ -209,6 +210,7 @@ class GameEngine extends THREE.EventDispatcher{
} }
this.clickable = new Clickable(20); this.clickable = new Clickable(20);
this.draggable = new Draggable(20);
} }
initXrControllers() { initXrControllers() {
@@ -485,6 +487,11 @@ class GameEngine extends THREE.EventDispatcher{
this.hero?.characterControls?.idleReset(); this.hero?.characterControls?.idleReset();
} }
onPointer(mouseEvent, domElement, type){
let mouse = this.getMouseVector(mouseEvent, domElement);
this.draggable?.update(mouse, this.camera, type);
}
setCamera(camera) { setCamera(camera) {
//camera.updateProjectionMatrix(); //camera.updateProjectionMatrix();
this.camera = camera; this.camera = camera;
+7
View File
@@ -123,6 +123,8 @@ export default {
await this.expandScenarioData(scene); await this.expandScenarioData(scene);
gameEngine.dashboard?.loading(0.1); gameEngine.dashboard?.loading(0.1);
gameEngine.orbitControls.enableRotate = this.env == 'GameDesigner'
//this is needed cause when mounted canvas has different size //this is needed cause when mounted canvas has different size
this.resize(); this.resize();
target.objects = target.objects || {}; target.objects = target.objects || {};
@@ -212,6 +214,7 @@ export default {
gameEngine.activeObjects.add(intro.object); gameEngine.activeObjects.add(intro.object);
intro.video.addEventListener('pause',()=>{ intro.video.addEventListener('pause',()=>{
intro.object.removeFromParent(); intro.object.removeFromParent();
gameEngine.clickable.remove(intro.object); //TODO!!!!
gameEngine.activeObjects.visible = true; gameEngine.activeObjects.visible = true;
}); });
intro.video.play(); intro.video.play();
@@ -244,6 +247,10 @@ export default {
} }
}, },
targetPointer(e, t){
gameEngine.onPointer(e, this.$refs.target, t);
},
setObjectAttributes(l, data, object, source, autoScaleFactor = 1){ setObjectAttributes(l, data, object, source, autoScaleFactor = 1){
if (l[data.id]){ if (l[data.id]){
['position', 'scale', 'rotation'].forEach(p=>{ ['position', 'scale', 'rotation'].forEach(p=>{