mazegame
This commit is contained in:
@@ -0,0 +1,333 @@
|
||||
import { Scene, Clock, PointLight, Group, TextGeometry, MeshStandardMaterial, MeshBasicMaterial, PlaneGeometry, Mesh, TextureLoader, sRGBEncoding,
|
||||
AnimationMixer, LoopPingPong, Vector3, DirectionalLight, Matrix4, LoopRepeat, EquirectangularReflectionMapping, EquirectangularRefractionMapping } from 'three';
|
||||
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
||||
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
|
||||
import {FontLoader} from 'three/examples/jsm/loaders/FontLoader';
|
||||
import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils';
|
||||
import { Clickable } from '../lib/Clickable';
|
||||
import { Draggable } from '../lib/Draggable';
|
||||
import { GameObject } from '../lib/GameObject';
|
||||
import { MotionEngine } from '../lib/MotionEngine';
|
||||
|
||||
class Maze {
|
||||
constructor(context) {
|
||||
const scene = new Scene();
|
||||
const motionEngine = new MotionEngine();
|
||||
this.context = context;
|
||||
context.motionEngine = motionEngine;
|
||||
context.scene = scene;
|
||||
const clickable = new Clickable(2);
|
||||
context.clickable = clickable;
|
||||
context.draggable = new Draggable(2);
|
||||
|
||||
if (context.dashboard) context.dashboard.onpoints = context.onpoints;
|
||||
|
||||
context.wallSize = context.wallSize || .65;
|
||||
context.tubeSize = context.tubeSize || .8;
|
||||
context.wallDepth = context.wallDepth || .1;
|
||||
context.fontPath = context.fontPath || './assets/fonts/ZapfChanceryC.otf';
|
||||
scene.background = new TextureLoader().load('./assets/textures/room/a3-2.jpg');
|
||||
scene.background.encoding = sRGBEncoding;
|
||||
scene.background.mapping = EquirectangularRefractionMapping;
|
||||
|
||||
const _tf = {
|
||||
rotation: {
|
||||
r: 3 * Math.PI / 2, f: 0, l: Math.PI / 2, b: Math.PI
|
||||
},
|
||||
position: {
|
||||
r: [-context.wallSize, context.wallSize],
|
||||
f: [0, 2 * context.wallSize],
|
||||
l: [context.wallSize, context.wallSize]
|
||||
},
|
||||
pNext: {
|
||||
r: [-context.wallSize - (context.wallSize + context.wallDepth) / 2, context.wallSize],
|
||||
f: [0, 2 * context.wallSize + context.wallSize / 2],
|
||||
l: [context.wallSize + (context.wallSize + context.wallDepth) / 2, context.wallSize]
|
||||
}
|
||||
};
|
||||
|
||||
const _l = new PointLight(0xffffff, 1, 10, 0.5);
|
||||
let pLoad = [];
|
||||
const mixers = [];
|
||||
context.mixers = mixers;
|
||||
context.assets = {};
|
||||
const clock = new Clock();
|
||||
context.activate = function (what) {
|
||||
scene.traverse(o => {
|
||||
if (o.name == what) {
|
||||
o.visible = true;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
context.gameObject = function (name) {
|
||||
let result;
|
||||
scene.traverse(o => {
|
||||
if (o.name == name) {
|
||||
result = o;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
const areas = [];
|
||||
context.areas = areas;
|
||||
let mazeGeometries = [];
|
||||
const cameraNear = .2;
|
||||
let heroDistance = .2;
|
||||
let o = {};
|
||||
var loader = new GLTFLoader().setPath(context.path);
|
||||
loader.setDRACOLoader(new DRACOLoader().setDecoderPath('./lib/draco/'));
|
||||
const fontLoader = new FontLoader().setPath(context.path);
|
||||
pLoad.push(new Promise((resolve, reject) => {
|
||||
loader.load('maze2.gltf', function (gltf) {
|
||||
console.log(gltf);
|
||||
gltf.scene.traverse(function (object) {
|
||||
if (object.isMesh || object.isObject3D) {
|
||||
//object.castShadow = true;
|
||||
//object.receiveShadow = true;
|
||||
}
|
||||
if (object.name) {
|
||||
context.assets[object.name] = object;
|
||||
}
|
||||
});
|
||||
['tunnel', 'wall', 'door', 'floor'].forEach(e => {
|
||||
o[e] = gltf.scene.getObjectByName(e);
|
||||
});
|
||||
resolve(gltf);
|
||||
});
|
||||
}));
|
||||
|
||||
pLoad.push(new Promise((resolve, reject) => {
|
||||
loader.setPath(context.path + 'human2/');
|
||||
loader.load('human.gltf', function (gltf) {
|
||||
console.log(gltf);
|
||||
gltf.scene.traverse(function (object) {
|
||||
if (object.isMesh || object.isObject3D) {
|
||||
//object.castShadow = true;
|
||||
//object.receiveShadow = true;
|
||||
object.frustumCulled = false;
|
||||
}
|
||||
});
|
||||
o.hero = gltf.scene;
|
||||
o.hero.scale.set(.033, .033, .033);
|
||||
o.hero.position.y = 0;
|
||||
let mixer = new AnimationMixer(gltf.scene);
|
||||
mixers.push(mixer);
|
||||
o.hero.actionWalk = mixer.clipAction(gltf.animations.find(a => a.name == 'walk'));
|
||||
o.hero.actionIdle = mixer.clipAction(gltf.animations.find(a => a.name == 'idle'));
|
||||
o.hero.actionIdle.play();
|
||||
resolve(gltf);
|
||||
});
|
||||
loader.setPath(context.path);
|
||||
}));
|
||||
|
||||
pLoad.push(new Promise((resolve, reject) => {
|
||||
fontLoader.load('font.json', function (font) {
|
||||
context.fontMaterial = new MeshBasicMaterial({
|
||||
color: 0x885e2c,
|
||||
transparent: true,
|
||||
opacity: 0.73
|
||||
});
|
||||
context.font = font;
|
||||
resolve(font);
|
||||
});
|
||||
}));
|
||||
|
||||
const staticThings = new Group();
|
||||
const staticPointer = new Mesh(
|
||||
new PlaneGeometry(.010, .010),
|
||||
new MeshStandardMaterial({
|
||||
map: new TextureLoader().load('./assets/maze/x.png'),
|
||||
alphaTest: .5,
|
||||
})
|
||||
);
|
||||
staticPointer.frustumCulled = false;
|
||||
staticPointer.material.map.encoding = sRGBEncoding;
|
||||
staticThings.add(staticPointer);
|
||||
|
||||
scene.add(staticThings);
|
||||
|
||||
Promise.all(pLoad).then(function () {
|
||||
loadScene();
|
||||
context.onload && context.onload();
|
||||
});
|
||||
|
||||
var that = this;
|
||||
function loadScene() {
|
||||
mazeObject(context.maze, scene);
|
||||
scene.add(o.hero);
|
||||
scene.add(new Mesh(BufferGeometryUtils.mergeBufferGeometries(mazeGeometries, false), o.tunnel.material));
|
||||
scene.add(_l);
|
||||
that.ready = true;
|
||||
}
|
||||
|
||||
function between(a, b, x) {
|
||||
return (a < b && a < x && x < b) || (b < a && b < x && x < a);
|
||||
}
|
||||
|
||||
function checkArea(x, z) {
|
||||
let allowed = areas.filter(e => e.type != 'deny' && between(e.a[0].x, e.a[1].x, x) && between(e.a[0].z, e.a[1].z, z));
|
||||
let denied = areas.filter(e => e.type == 'deny' && between(e.a[0].x, e.a[1].x, x) && between(e.a[0].z, e.a[1].z, z));
|
||||
return { area: allowed, block: allowed.length == 0 || denied.length > 0 };
|
||||
}
|
||||
|
||||
this.scene = scene;
|
||||
const _vector = new Vector3();
|
||||
let heroState = 0;
|
||||
let lastCamera = new Vector3(0, .5, 0.5);
|
||||
this.update = function (camera) {
|
||||
let currentArea = checkArea(camera.position.x, camera.position.z);
|
||||
if (currentArea.block) {
|
||||
if (!checkArea(camera.position.x, lastCamera.z).block) {
|
||||
camera.position.z = lastCamera.z;
|
||||
} else if (!checkArea(lastCamera.x, camera.position.z).block) {
|
||||
camera.position.x = lastCamera.x;
|
||||
} else {
|
||||
camera.position.copy(lastCamera);
|
||||
}
|
||||
currentArea = checkArea(camera.position.x, camera.position.z);
|
||||
}
|
||||
_l.position.copy(camera.position);
|
||||
context.onupdate && context.onupdate();
|
||||
motionEngine.update();
|
||||
let delta = clock.getDelta();
|
||||
mixers.forEach(m => m.update(delta));
|
||||
if (o.hero) {
|
||||
let dst = Math.abs(camera.position.distanceTo(lastCamera));
|
||||
if (dst <= 0.005 && heroState == 1) {
|
||||
o.hero.actionWalk.crossFadeTo(o.hero.actionIdle.play(), .5);
|
||||
heroState = 0;
|
||||
}
|
||||
else if (dst <= 0.0005 && heroState == 0 && o.hero.actionWalk.isRunning()) {
|
||||
o.hero.actionWalk.stop();
|
||||
} else if (dst > 0.005 && heroState == 0) {
|
||||
o.hero.actionIdle.crossFadeTo(o.hero.actionWalk.reset().play(), .5);
|
||||
heroState = 1;
|
||||
} else if (dst > 0.007 && heroState == 1 && o.hero.actionIdle.isRunning()) {
|
||||
o.hero.actionIdle.stop();
|
||||
}
|
||||
_vector.setFromMatrixColumn(camera.matrix, 0);
|
||||
_vector.crossVectors(camera.up, _vector);
|
||||
|
||||
o.hero.position.copy(camera.position);
|
||||
o.hero.position.addScaledVector(_vector, heroDistance);
|
||||
o.hero.position.y = 0;
|
||||
o.hero.rotation.y = camera.rotation.y - Math.PI;
|
||||
|
||||
if (checkArea(o.hero.position.x, o.hero.position.z).block) {
|
||||
o.hero.visible = false;
|
||||
} else {
|
||||
o.hero.visible = true;
|
||||
}
|
||||
|
||||
staticThings.position.copy(camera.position);
|
||||
camera.getWorldDirection(_vector);
|
||||
staticThings.position.addScaledVector(_vector, .6);
|
||||
staticThings.rotation.copy(camera.rotation);
|
||||
}
|
||||
lastCamera.copy(camera.position);
|
||||
};
|
||||
|
||||
this.onclick = function (camera, mouse, event) {
|
||||
mouse = mouse || new Vector3(0, 0, 0);
|
||||
clickable.update(mouse, camera, event);
|
||||
};
|
||||
|
||||
this.onpointer = function (camera, mouse, action) {
|
||||
mouse = mouse || new Vector3(0, 0, 0);
|
||||
context.draggable.update(mouse, camera, action);
|
||||
};
|
||||
|
||||
var mazeObject = function (def, room, step = 0) {
|
||||
let offsetZ = 0, e;
|
||||
def.len = def.len || 0;
|
||||
if (step == 0) {
|
||||
e = o.door.geometry.clone();
|
||||
e.rotateY(_tf.rotation.f);
|
||||
mazeGeometries.push(e);
|
||||
}
|
||||
for (let i = 0; i < def.len; i++) {
|
||||
let t = o.tunnel.geometry.clone();
|
||||
t.translate(0, 0, i * context.tubeSize + context.wallDepth / 2);
|
||||
def.matrix && t.applyMatrix4(def.matrix);
|
||||
|
||||
mazeGeometries.push(t);
|
||||
}
|
||||
offsetZ = context.wallDepth + def.len * context.tubeSize - context.tubeSize / 2;
|
||||
if (!def.len) offsetZ = -.275;
|
||||
areas.push({
|
||||
a: [
|
||||
room.localToWorld(new Vector3(-context.tubeSize / 2 + cameraNear, 0, -context.tubeSize / 2 - cameraNear)),
|
||||
room.localToWorld(new Vector3(context.tubeSize / 2 - cameraNear, 0, offsetZ + cameraNear))
|
||||
]
|
||||
});
|
||||
if (def.type == 'area') {
|
||||
def.area.forEach(ar => {
|
||||
areas.push({
|
||||
a: [
|
||||
room.localToWorld(new Vector3(ar[0] + cameraNear, 0, offsetZ + ar[1] - cameraNear)),
|
||||
room.localToWorld(new Vector3(ar[2] - cameraNear, 0, offsetZ + ar[3] + cameraNear))
|
||||
]
|
||||
});
|
||||
});
|
||||
} else {
|
||||
if (def.noRoom) {
|
||||
// e = o.wall.geometry.clone();
|
||||
// e.rotateY(_tf.rotation.f);
|
||||
// e.translate(0,0,offsetZ + 0);
|
||||
// def.matrix && e.applyMatrix4(def.matrix);
|
||||
// mazeGeometries.push(e);
|
||||
} else {
|
||||
e = [o.floor.geometry.clone(), o.door.geometry.clone(), o[def.r ? 'door' : 'wall'].geometry.clone(),
|
||||
o[def.f ? 'door' : 'wall'].geometry.clone(), o[def.l ? 'door' : 'wall'].geometry.clone()];
|
||||
e[0].translate(0, 0, offsetZ + context.wallSize);
|
||||
|
||||
e[1].rotateY(_tf.rotation.b);
|
||||
e[2].rotateY(_tf.rotation.r);
|
||||
e[3].rotateY(_tf.rotation.f);
|
||||
e[4].rotateY(_tf.rotation.l);
|
||||
|
||||
e[1].translate(0, 0, offsetZ + 0);
|
||||
e[2].translate(-context.wallSize, 0, offsetZ + context.wallSize);
|
||||
e[3].translate(0, 0, offsetZ + context.wallSize * 2);
|
||||
e[4].translate(context.wallSize, 0, offsetZ + context.wallSize);
|
||||
|
||||
e.forEach(g => {
|
||||
def.matrix && g.applyMatrix4(def.matrix);
|
||||
mazeGeometries.push(g);
|
||||
});
|
||||
areas.push({
|
||||
a: [
|
||||
room.localToWorld(new Vector3(-context.wallSize + cameraNear, 0, offsetZ + cameraNear)),
|
||||
room.localToWorld(new Vector3(context.wallSize - cameraNear, 0, offsetZ + context.wallSize * 2 - cameraNear))
|
||||
]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
def.objects && def.objects.forEach(obj => {
|
||||
obj.room = room;
|
||||
let go = new GameObject(obj, context);
|
||||
go.ready.then(mesh => {
|
||||
room.add(mesh);
|
||||
});
|
||||
});
|
||||
def.room = room;
|
||||
['r', 'f', 'l'].forEach((d, i) => {
|
||||
if (!def[d]) return;
|
||||
let mtx = new Matrix4();
|
||||
mtx.makeRotationY(_tf.rotation[d]);
|
||||
mtx.setPosition(_tf.pNext[d][0], 0, _tf.pNext[d][1] + offsetZ);
|
||||
let rr = new Group();
|
||||
scene.add(rr);
|
||||
def[d].matrix = mtx.premultiply(def.matrix || new Matrix4());
|
||||
rr.applyMatrix4(def[d].matrix);
|
||||
rr.updateMatrixWorld();
|
||||
mazeObject(def[d], rr, step + 1);
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export { Maze };
|
||||
@@ -0,0 +1,147 @@
|
||||
import { Group, Vector3, Matrix4, Mesh, DoubleSide } from 'three';
|
||||
import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js';
|
||||
import { TextObject } from '../TextObject';
|
||||
|
||||
class MazeObject {
|
||||
constructor(engine, def, params = {}){
|
||||
let room = new Group();
|
||||
let scene = room;
|
||||
this.object = room;
|
||||
let context = {};
|
||||
context.wallSize = params.wallSize || .65;
|
||||
context.tubeSize = params.tubeSize || .8;
|
||||
context.wallDepth = params.wallDepth || .1;
|
||||
context.fontPath = params.fontPath || '/static/fonts/ZapfChanceryC.otf';
|
||||
|
||||
const cameraNear = .2;
|
||||
|
||||
this.context = context;
|
||||
let _tf = {
|
||||
rotation: {
|
||||
r: 3 * Math.PI / 2, f: 0, l: Math.PI / 2, b: Math.PI
|
||||
},
|
||||
position: {
|
||||
r: [-context.wallSize, context.wallSize],
|
||||
f: [0, 2 * context.wallSize],
|
||||
l: [context.wallSize, context.wallSize]
|
||||
},
|
||||
pNext: {
|
||||
r: [-context.wallSize - (context.wallSize + context.wallDepth) / 2, context.wallSize],
|
||||
f: [0, 2 * context.wallSize + context.wallSize / 2],
|
||||
l: [context.wallSize + (context.wallSize + context.wallDepth) / 2, context.wallSize]
|
||||
}
|
||||
};
|
||||
|
||||
let o = {};
|
||||
let mazeGeometries = [], areas = [];
|
||||
|
||||
this.mazeObject = function(def, room, step = 0) {
|
||||
let offsetZ = 0, e;
|
||||
def.len = def.len || 0;
|
||||
if (step == 0) {
|
||||
e = o.door.geometry.clone();
|
||||
e.rotateY(_tf.rotation.f);
|
||||
mazeGeometries.push(e);
|
||||
}
|
||||
for (let i = 0; i < def.len; i++) {
|
||||
let t = o.tunnel.geometry.clone();
|
||||
t.translate(0, 0, i * context.tubeSize + context.wallDepth / 2);
|
||||
def.matrix && t.applyMatrix4(def.matrix);
|
||||
|
||||
mazeGeometries.push(t);
|
||||
}
|
||||
offsetZ = context.wallDepth + def.len * context.tubeSize - context.tubeSize / 2;
|
||||
if (!def.len) offsetZ = -.275;
|
||||
areas.push({
|
||||
a: [
|
||||
room.localToWorld(new Vector3(-context.tubeSize / 2 + cameraNear, 0, -context.tubeSize / 2 - cameraNear)),
|
||||
room.localToWorld(new Vector3(context.tubeSize / 2 - cameraNear, 0, offsetZ + cameraNear))
|
||||
]
|
||||
});
|
||||
if (def.type == 'area') {
|
||||
def.area.forEach(ar => {
|
||||
areas.push({
|
||||
a: [
|
||||
room.localToWorld(new Vector3(ar[0] + cameraNear, 0, offsetZ + ar[1] - cameraNear)),
|
||||
room.localToWorld(new Vector3(ar[2] - cameraNear, 0, offsetZ + ar[3] + cameraNear))
|
||||
]
|
||||
});
|
||||
});
|
||||
} else {
|
||||
if (def.noRoom) {
|
||||
// e = o.wall.geometry.clone();
|
||||
// e.rotateY(_tf.rotation.f);
|
||||
// e.translate(0,0,offsetZ + 0);
|
||||
// def.matrix && e.applyMatrix4(def.matrix);
|
||||
// mazeGeometries.push(e);
|
||||
} else {
|
||||
e = [o.floor.geometry.clone(), o.door.geometry.clone(), o[def.r ? 'door' : 'wall'].geometry.clone(),
|
||||
o[def.f ? 'door' : 'wall'].geometry.clone(), o[def.l ? 'door' : 'wall'].geometry.clone()];
|
||||
e[0].translate(0, 0, offsetZ + context.wallSize);
|
||||
|
||||
e[1].rotateY(_tf.rotation.b);
|
||||
e[2].rotateY(_tf.rotation.r);
|
||||
e[3].rotateY(_tf.rotation.f);
|
||||
e[4].rotateY(_tf.rotation.l);
|
||||
|
||||
e[1].translate(0, 0, offsetZ + 0);
|
||||
e[2].translate(-context.wallSize, 0, offsetZ + context.wallSize);
|
||||
e[3].translate(0, 0, offsetZ + context.wallSize * 2);
|
||||
e[4].translate(context.wallSize, 0, offsetZ + context.wallSize);
|
||||
|
||||
e.forEach(g => {
|
||||
def.matrix && g.applyMatrix4(def.matrix);
|
||||
mazeGeometries.push(g);
|
||||
});
|
||||
areas.push({
|
||||
a: [
|
||||
room.localToWorld(new Vector3(-context.wallSize + cameraNear, 0, offsetZ + cameraNear)),
|
||||
room.localToWorld(new Vector3(context.wallSize - cameraNear, 0, offsetZ + context.wallSize * 2 - cameraNear))
|
||||
]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
def.objects && def.objects.forEach(obj => {
|
||||
obj.room = room;
|
||||
// let go = new GameObject(obj, context);
|
||||
let go = new TextObject(obj, context)
|
||||
room.add(go.mesh);
|
||||
// go.ready.then(mesh => {
|
||||
// room.add(mesh);
|
||||
// });
|
||||
});
|
||||
|
||||
def.room = room;
|
||||
['r', 'f', 'l'].forEach((d, i) => {
|
||||
if (!def[d]) return;
|
||||
let mtx = new Matrix4();
|
||||
mtx.makeRotationY(_tf.rotation[d]);
|
||||
mtx.setPosition(_tf.pNext[d][0], 0, _tf.pNext[d][1] + offsetZ);
|
||||
let rr = new Group();
|
||||
scene.add(rr);
|
||||
def[d].matrix = mtx.premultiply(def.matrix || new Matrix4());
|
||||
rr.applyMatrix4(def[d].matrix);
|
||||
rr.updateMatrixWorld();
|
||||
this.mazeObject(def[d], rr, step + 1);
|
||||
});
|
||||
};
|
||||
|
||||
this.load = async function(){
|
||||
let mazeAsset = await engine.load('/static/meshes/maze-reed.gltf');
|
||||
['tunnel', 'wall', 'door', 'floor'].forEach(e => {
|
||||
o[e] = mazeAsset.scene.getObjectByName(e);
|
||||
o[e].frustumCulled = false;
|
||||
});
|
||||
o.tunnel.material.depthWrite = false
|
||||
this.mazeObject(def, room);
|
||||
mazeGeometries.forEach(mg=>{
|
||||
scene.add(new Mesh(mg, o.tunnel.material));
|
||||
})
|
||||
//scene.add(new Mesh(BufferGeometryUtils.mergeGeometries(mazeGeometries, false), o.tunnel.material));
|
||||
console.log(room);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { MazeObject }
|
||||
@@ -0,0 +1,48 @@
|
||||
import { MazeObject } from "./MazeObject";
|
||||
|
||||
class MazeQuizGame {
|
||||
constructor(engine, context, questions) {
|
||||
let def = this.generate(questions);
|
||||
console.log(def)
|
||||
this.mazeObject = new MazeObject(engine, def)
|
||||
}
|
||||
|
||||
async load(){
|
||||
await this.mazeObject.load();
|
||||
this.object = this.mazeObject.object;
|
||||
return this.object;
|
||||
}
|
||||
|
||||
generate(questions, idx = 0){
|
||||
let cq = questions[idx]
|
||||
if (!cq) return {};
|
||||
let len = Math.round(Math.random()*4) + 2;
|
||||
let lr = Math.round(Math.random()*4) + 2;
|
||||
let lrv = Math.random() > 0.5;
|
||||
return {
|
||||
len,
|
||||
objects:[
|
||||
{
|
||||
type: 'text', text: cq.s, position:[0,.4,len], rotation:[0,Math.PI, 0]
|
||||
}
|
||||
],
|
||||
[lrv?'r':'l']:{
|
||||
len: 10 - lr,
|
||||
[lrv?'l':'r']: {
|
||||
len: lr,
|
||||
objects:[
|
||||
{
|
||||
type: 'text', text: cq.h, position:[0,.4,lr], rotation:[0,Math.PI, 0]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
[lrv?'l':'r']:{
|
||||
len: lr,
|
||||
[lrv?'r':'l']: this.generate(questions, idx + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
export {MazeQuizGame}
|
||||
Reference in New Issue
Block a user