finish events

This commit is contained in:
2025-10-29 13:34:54 +02:00
parent 93e03b4843
commit 127f71f345
8 changed files with 104 additions and 82 deletions
@@ -1,5 +1,6 @@
import { Group, Vector3, Matrix4, Mesh, Quaternion, PlaneGeometry, MeshStandardMaterial, DoubleSide } from 'three'; import { Group, Vector3, Matrix4, Mesh, Quaternion, PlaneGeometry, MeshStandardMaterial, DoubleSide } from 'three';
import { InteractiveObject } from '../InteractiveObject'; import { InteractiveObject } from '../InteractiveObject';
import { ActiveEvents } from '@dimforge/rapier3d';
class MazeObject { class MazeObject {
constructor(engine, def, params = {}){ constructor(engine, def, params = {}){
@@ -32,16 +33,21 @@ class MazeObject {
let o = {}; let o = {};
function addPhysics(matrix, position, size, placement = 'side'){ function addPhysics(matrix, position, size, placement = 'side', isSensor = false, userData){
let quat = new Quaternion().setFromRotationMatrix(matrix); let quat = new Quaternion().setFromRotationMatrix(matrix);
let v = new Vector3(...position).applyMatrix4(matrix); let v = new Vector3(...position).applyMatrix4(matrix);
if (typeof size == 'number'){
size = { width: 0.1, height:1, depth:size/2 }
}
let po = engine.phy.add( let po = engine.phy.add(
{position: v}, 'fixed', false, undefined, 'cuboid',{ width: 0.01, height:1, depth:size/2 } {position: v}, 'fixed', false, undefined, 'cuboid',
{ ...size, isSensor, userData }
) )
if (placement == 'front') { if (placement == 'front') {
quat.multiply(new Quaternion(0, 0.7071068, 0, 0.7071068)) //rotate by 90deg quat.multiply(new Quaternion(0, 0.7071068, 0, 0.7071068)) //rotate by 90deg
} }
po.rigidBody.setRotation(quat, true) po.rigidBody.setRotation(quat, true)
return po;
} }
function addRoom(elements, def, offsetZ){ function addRoom(elements, def, offsetZ){
@@ -105,6 +111,9 @@ class MazeObject {
// mazeMeshes.push(e); // mazeMeshes.push(e);
addRoom(['floor', 'wall', 'wall', 'door', 'wall'], def, -context.wallSize) addRoom(['floor', 'wall', 'wall', 'door', 'wall'], def, -context.wallSize)
} }
if (def.userData?.answer !== undefined){
addPhysics(def.matrix, [0,0,0], context.wallSize, 'front', true, def.userData)
}
for (let i = 0; i < def.len; i++) { for (let i = 0; i < def.len; i++) {
let t = o.tunnel.clone(); let t = o.tunnel.clone();
t.position.set(0, 0, i * context.tubeSize); t.position.set(0, 0, i * context.tubeSize);
@@ -125,8 +134,10 @@ class MazeObject {
addPhysics(def.matrix, [-context.tubeSize / 2, 0.6, offsetZ/2], offsetZ) addPhysics(def.matrix, [-context.tubeSize / 2, 0.6, offsetZ/2], offsetZ)
addRoom(['floor', 'door', def.r ? 'door' : 'wall', def.f ? 'door' : 'wall', def.l ? 'door' : 'wall'], def, offsetZ) addRoom(['floor', 'door', def.r ? 'door' : 'wall', def.f ? 'door' : 'wall', def.l ? 'door' : 'wall'], def, offsetZ)
if (def.userData?.qid !== undefined || def.userData?.finish){
console.log('loadingggg', def.objects) addPhysics(def.matrix, [0,0,offsetZ + context.wallSize/2], { width: context.wallSize/2, height: context.wallSize/2, depth: context.wallSize/2}, 'side', true, def.userData)
}
//console.log('loadingggg', def.objects)
def.objects?.forEach(async obj => { def.objects?.forEach(async obj => {
obj.room = room; obj.room = room;
// let go = new GameObject(obj, context); // let go = new GameObject(obj, context);
@@ -172,7 +183,7 @@ class MazeObject {
// //engine.phy.add(mesh, 'fixed') // //engine.phy.add(mesh, 'fixed')
// }) // })
console.log(bbox, 'bbox') //console.log(bbox, 'bbox')
const floorGeometry = new PlaneGeometry(bbox.r - bbox.l + 10*scale, bbox.f + 10*scale); const floorGeometry = new PlaneGeometry(bbox.r - bbox.l + 10*scale, bbox.f + 10*scale);
const floor = new Mesh(floorGeometry,new MeshStandardMaterial({ const floor = new Mesh(floorGeometry,new MeshStandardMaterial({
roughness: 0, metalness:1, color: 0x00ffff, side: DoubleSide roughness: 0, metalness:1, color: 0x00ffff, side: DoubleSide
@@ -20,6 +20,24 @@ class MazeQuizGame {
constructor(engine, context, questions) { constructor(engine, context, questions) {
let def = this.generate(questions); let def = this.generate(questions);
this.mazeObject = new MazeObject(engine, def) this.mazeObject = new MazeObject(engine, def)
engine.addEventListener('collision', async e=>{
let ud = engine.phy.world.getCollider(e.handle2)?.parent()?.userData;
if (ud?.finish && engine.hero?.animationsMap?.win){
if (e.started){
engine.hero.animationsMap._idle = engine.hero.animationsMap.idle
engine.hero.animationsMap.idle = engine.hero.animationsMap.win
engine.hero.characterControls.cameraDelta = Math.PI;
await Utils.wait(1000);
engine.hero.characterControls.direction += Math.PI;
await Utils.wait(10000);
this.onfinish?.()
}else{
engine.hero.animationsMap.idle = engine.hero.animationsMap._idle
engine.hero.characterControls.cameraDelta = 0
}
}
//console.log(e, ud, engine.hero?.animationsMap);
})
} }
async load(){ async load(){
@@ -32,7 +50,7 @@ class MazeQuizGame {
let question = questions[qid] let question = questions[qid]
if (!question) return { if (!question) return {
len:3, len:3,
finish: true, userData: { finish: true },
objects:[ objects:[
{ {
type: 'gltf', type: 'gltf',
@@ -52,7 +70,7 @@ class MazeQuizGame {
let directions = Utils.shuffleArray( ['l', 'r', 'f'] ) let directions = Utils.shuffleArray( ['l', 'r', 'f'] )
let mo = { let mo = {
len, question, qid, len, userData: { question, qid },
objects:[ objects:[
{ {
type: 'text', text: question.q, fontSize:0.033, width:0.5, position:[0,.33,len + .96], rotation:[0,Math.PI, 0] type: 'text', text: question.q, fontSize:0.033, width:0.5, position:[0,.33,len + .96], rotation:[0,Math.PI, 0]
@@ -74,11 +92,13 @@ class MazeQuizGame {
} }
if (i == 0){ if (i == 0){
mo[d] = { mo[d] = {
len: 3, answer: i,
len: 4,
[dd]: this.generate(questions, qid + 1, 3) [dd]: this.generate(questions, qid + 1, 3)
} }
}else{ }else{
mo[d] = { mo[d] = {
userData: { question, qid, answer: i },
len: 4, len: 4,
[dd]: { [dd]: {
len: 2, len: 2,
+11 -21
View File
@@ -1,6 +1,5 @@
import * as THREE from 'three' import * as THREE from 'three'
import { QueryFilterFlags } from '@dimforge/rapier3d';
export const CONTROLLER_BODY_RADIUS = 0.28;
export class CharacterControls { export class CharacterControls {
@@ -33,7 +32,7 @@ export class CharacterControls {
// // Automatically slide down on slopes smaller than 30 degrees. // // Automatically slide down on slopes smaller than 30 degrees.
// this.characterController.setMinSlopeSlideAngle(30 * Math.PI / 180); // this.characterController.setMinSlopeSlideAngle(30 * Math.PI / 180);
// this.characterController.enableAutostep(0.5, 0.2, true); // this.characterController.enableAutostep(0.5, 0.2, true);
// this.characterController.setApplyImpulsesToDynamicBodies(true); this.characterController.setApplyImpulsesToDynamicBodies(true);
// this.characterController.setCharacterMass(50); // this.characterController.setCharacterMass(50);
this.po = po; this.po = po;
this.pointerControls = pointerControls this.pointerControls = pointerControls
@@ -47,20 +46,14 @@ export class CharacterControls {
this.actionStart = 0; this.actionStart = 0;
this.cameraDelta = 0; this.cameraDelta = 0;
this.cameraIdleDelta = 0; this.cameraIdleDelta = 0;
//this.toggleRun = true
}
switchRunToggle() {
this.toggleRun = !this.toggleRun
} }
update(world, delta, pointerControls) { update(world, delta, pointerControls) {
const directionPressed = pointerControls.moving
let input = this.getInput(pointerControls) let input = this.getInput(pointerControls)
let play = this.currentAction || 'idle', velocity = this.walkVelocity; let play = this.currentAction || 'idle', velocity = this.walkVelocity;
this.fadeDuration = 0.2; this.fadeDuration = 0.2;
if (input[1] && this.toggleRun) { if (input[1] && pointerControls.running) {
play = 'run'; play = 'run';
velocity = this.runVelocity velocity = this.runVelocity
} else if (input[1] > 0) { } else if (input[1] > 0) {
@@ -111,23 +104,13 @@ export class CharacterControls {
this.walkDirection.x = this.walkDirection.y = this.walkDirection.z = 0 this.walkDirection.x = this.walkDirection.y = this.walkDirection.z = 0
if (directionPressed) { if (pointerControls.motion) {
this.directionVelocity = this.directionVelocity * 2.5 * Math.abs(input[0]) this.directionVelocity = this.directionVelocity * 2.5 * Math.abs(input[0])
this.direction += input[0] * delta * 2.5 //this.directionVelocity; this.direction += input[0] * delta * 2.5 //this.directionVelocity;
this.model.rotation.y = this.direction; this.model.rotation.y = this.direction;
this.walkDirection.set(0, 0, input[1]) this.walkDirection.set(0, 0, input[1])
this.walkDirection.applyAxisAngle(this.rotateAngle, this.direction) this.walkDirection.applyAxisAngle(this.rotateAngle, this.direction)
this.walkDirection.normalize(); this.walkDirection.normalize();
//let dst = Math.sqrt(Math.pow(cameraPosition.x - this.model.position.x, 2) + Math.pow(cameraPosition.z - this.model.position.z, 2));
//cameraPosition.y = 8 - dst;
//if (dst >0.52){
//}
//this.camera.zoom = dst
//this.camera.updateProjectionMatrix();
// run/walk velocity
} }
this.walkDirection.x = this.walkDirection.x * velocity * delta// + this.model.position.x this.walkDirection.x = this.walkDirection.x * velocity * delta// + this.model.position.x
@@ -138,8 +121,15 @@ export class CharacterControls {
this.characterController.computeColliderMovement( this.characterController.computeColliderMovement(
this.po.collider, // The collider we would like to move. this.po.collider, // The collider we would like to move.
this.walkDirection, // The movement we would like to apply if there wasnt any obstacle. this.walkDirection, // The movement we would like to apply if there wasnt any obstacle.
QueryFilterFlags['EXCLUDE_SENSORS']
); );
// for (let i = 0; i < this.characterController.numComputedCollisions(); i++) {
// let collision = this.characterController.computedCollision(i);
// //console.log(collision)
// // Do something with that collision information.
// }
let correctedMovement = this.characterController.computedMovement(); let correctedMovement = this.characterController.computedMovement();
let v = new THREE.Vector3(); let v = new THREE.Vector3();
+2 -32
View File
@@ -17,7 +17,7 @@ import { XRControllerModelFactory } from 'three/addons/webxr/XRControllerModelFa
import { Physics } from './Physics.js'; import { Physics } from './Physics.js';
import { Clickable } from './Clickable.js'; import { Clickable } from './Clickable.js';
class GameEngine { class GameEngine extends THREE.EventDispatcher{
async init(domNode, opts = {}) { async init(domNode, opts = {}) {
this.w = domNode.clientWidth || 1200, this.h = domNode.clientHeight || 800; this.w = domNode.clientWidth || 1200, this.h = domNode.clientHeight || 800;
this.aspect = this.w / this.h this.aspect = this.w / this.h
@@ -251,37 +251,7 @@ class GameEngine {
} }
async initPhysics() { async initPhysics() {
//const world = new CANNON.World() this.phy = new Physics(this);
//world.gravity.set(0, -9.82, 0)
// let gravity = { x: 0.0, y: -9.81, z: 0.0 };
// let world = new RAPIER.World(gravity);
// // Create Ground.
// let bodyDesc = RAPIER.RigidBodyDesc.fixed();
// let body = world.createRigidBody(bodyDesc);
// let colliderDesc = RAPIER.ColliderDesc.cuboid(100.0, 0.1, 100.0);
// world.createCollider(colliderDesc, body.handle);
// const groundMaterial = new CANNON.Material('groundMaterial')
// const slipperyMaterial = new CANNON.Material('slipperyMaterial')
// const slippery_ground_cm = new CANNON.ContactMaterial(
// groundMaterial,
// slipperyMaterial,
// {
// friction: 0,
// restitution: 0.3,
// contactEquationStiffness: 1e10,
// contactEquationRelaxation: 30,
// }
// )
// world.addContactMaterial(slippery_ground_cm)
// const planeShape = new CANNON.Plane()
// const planeBody = new CANNON.Body({ mass: 0, material: groundMaterial })
// planeBody.addShape(planeShape)
// planeBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2)
// world.addBody(planeBody)
this.phy = new Physics();
await this.phy.init(); await this.phy.init();
} }
+8 -8
View File
@@ -2,7 +2,6 @@ import { AnimationMixer } from 'three';
import { PointerControls } from './PointerControls'; import { PointerControls } from './PointerControls';
import { CharacterControls } from './CharacterControls'; import { CharacterControls } from './CharacterControls';
import * as THREE from 'three'; import * as THREE from 'three';
import * as RAPIER from '@dimforge/rapier3d'
class Hero{ class Hero{
@@ -18,9 +17,9 @@ class Hero{
gameEngine.camera.position.set(0,17,-30) gameEngine.camera.position.set(0,17,-30)
gameEngine.camera.lookAt(new THREE.Vector3(this.model.position.x, 5, this.model.position.z)) gameEngine.camera.lookAt(new THREE.Vector3(this.model.position.x, 5, this.model.position.z))
this.heroCamera = new THREE.Object3D() // this.heroCamera = new THREE.Object3D()
this.model.add(this.heroCamera) // this.model.add(this.heroCamera)
this.heroCamera.applyMatrix4(gameEngine.camera.matrix) // this.heroCamera.applyMatrix4(gameEngine.camera.matrix)
this.mixer = new AnimationMixer(this.model); this.mixer = new AnimationMixer(this.model);
gameEngine.mixers.push( this.mixer ); gameEngine.mixers.push( this.mixer );
@@ -35,6 +34,7 @@ class Hero{
let po = gameEngine.phy.add(this.model, 'kinematicPositionBased', false, undefined, 'capsule', { radius: 0.5, halfHeight: 1}) let po = gameEngine.phy.add(this.model, 'kinematicPositionBased', false, undefined, 'capsule', { radius: 0.5, halfHeight: 1})
po.collider.setTranslationWrtParent({ x: 0, y: 2.0, z: 0 }); po.collider.setTranslationWrtParent({ x: 0, y: 2.0, z: 0 });
//po.collider.setActiveEvents(RAPIER.ActiveEvents.COLLISION_EVENTS);
this.characterControls = new CharacterControls(this.model, this.mixer, this.characterControls = new CharacterControls(this.model, this.mixer,
this.animationsMap, gameEngine, 'idle', po, this.pointerControls) this.animationsMap, gameEngine, 'idle', po, this.pointerControls)
@@ -53,16 +53,16 @@ class Hero{
//return //return
if (this.gameEngine.renderer.xr.isPresenting) return; if (this.gameEngine.renderer.xr.isPresenting) return;
if (this.ready) { if (this.ready && !this.disableInput) {
let pc = this.pointerControls; let pc = this.pointerControls;
pc.update(); pc.update();
let dlt = this.clock.getDelta(); let dlt = this.clock.getDelta();
this.delta += dlt; this.delta += dlt;
if (this.delta > 0.00001){ //if (this.delta > 0.00001){
this.gameEngine.phy.step()
this.characterControls.update(this.gameEngine.phy.world, this.delta, pc) this.characterControls.update(this.gameEngine.phy.world, this.delta, pc)
this.gameEngine.phy.step()
this.delta = 0; this.delta = 0;
} //}
} }
+19 -12
View File
@@ -1,16 +1,7 @@
import * as RAPIER from '@dimforge/rapier3d' import * as RAPIER from '@dimforge/rapier3d'
// export type PhysicsObject = {
// mesh: THREE.Mesh
// collider: Rapier.Collider
// rigidBody: Rapier.RigidBody
// fn?: Function
// autoAnimate: boolean
// }
class Physics{ class Physics{
constructor(){ constructor(engine){
this.engine = engine
} }
init = async ()=>{ init = async ()=>{
@@ -25,6 +16,7 @@ class Physics{
world.createCollider(colliderDesc, body.handle); world.createCollider(colliderDesc, body.handle);
this.world = world; this.world = world;
this.physicsObjects = []; this.physicsObjects = [];
this.eventQueue = new RAPIER.EventQueue(true);
return this; return this;
} }
@@ -34,6 +26,7 @@ class Physics{
// * Responsible for collision response // * Responsible for collision response
const rigidBody = this.world.createRigidBody(rigidBodyDesc) const rigidBody = this.world.createRigidBody(rigidBodyDesc)
rigidBody.userData = colliderSettings.userData || {};
let colliderDesc let colliderDesc
@@ -73,6 +66,12 @@ class Physics{
console.error('Collider Mesh Error: convex mesh creation failed.') console.error('Collider Mesh Error: convex mesh creation failed.')
} }
if (colliderSettings.isSensor){
colliderDesc.setSensor(true);
colliderDesc.setActiveEvents(RAPIER.ActiveEvents.COLLISION_EVENTS);
colliderDesc.setActiveCollisionTypes(RAPIER.ActiveCollisionTypes.KINEMATIC_FIXED);
}
// * Responsible for collision detection // * Responsible for collision detection
const collider = this.world.createCollider(colliderDesc, rigidBody) const collider = this.world.createCollider(colliderDesc, rigidBody)
@@ -84,7 +83,7 @@ class Physics{
} }
step(){ step(){
this.world.step() this.world.step(this.eventQueue)
for (let po of this.physicsObjects) { for (let po of this.physicsObjects) {
const autoAnimate = po.autoAnimate const autoAnimate = po.autoAnimate
@@ -99,6 +98,14 @@ class Physics{
const fn = po.fn const fn = po.fn
fn && fn() fn && fn()
} }
this.eventQueue.drainCollisionEvents((handle1, handle2, started) => {
/* Handle the collision event. */
//console.log(this.world.getCollider(handle2), handle1, started)
this.engine.dispatchEvent({
type: 'collision', handle1, handle2, started
})
});
} }
} }
+13 -1
View File
@@ -139,7 +139,19 @@ class PointerControls {
} }
get moving(){ get moving(){
return this.moveForward || this.moveBackward || this.moveLeft || this.moveRight; return this.moveForward || this.moveBackward;
}
get rotating(){
return this.rotateLeft || this.rotateRight;
}
get motion(){
return this.moving || this.rotating;
}
get running(){
return this.moving && this.kb.ShiftLeft;
} }
} }
+12
View File
@@ -58,5 +58,17 @@ export default {
shuffleArray(arr){ shuffleArray(arr){
return arr.map(value => ({ value, sort: Math.random() })) return arr.map(value => ({ value, sort: Math.random() }))
.sort((a, b) => a.sort - b.sort).map(({ value }) => value) .sort((a, b) => a.sort - b.sort).map(({ value }) => value)
},
async wait(ms){
await new Promise((resolve, reject)=>{
setTimeout(resolve, ms)
})
},
async waitFor(expFn){
while (!expFn()){
await JsUtils.wait(200);
}
} }
} }