diff --git a/src/components/GameDesigner/GameDesigner.vue b/src/components/GameDesigner/GameDesigner.vue index 7bfc423..713ced2 100644 --- a/src/components/GameDesigner/GameDesigner.vue +++ b/src/components/GameDesigner/GameDesigner.vue @@ -121,9 +121,9 @@ export default { async mounted(){ gameEngine = new GameEngine(); //this.gameEngine = gameEngine; - await gameEngine.init(this.$refs.target, {gizmo: true}); + await gameEngine.init(this.$refs.target, { xr: true, gizmo: true, designMode: true, depthSense: false }); gameEngine.scene.add(gameEngine.transformControls.getHelper()); - gameEngine.scene.add(new gameEngine.$.GridHelper(100,100)); + //gameEngine.scene.add(new gameEngine.$.GridHelper(100,100)); this.resize(); //gameEngine.setCamera(gameEngine.orthographicCamera) //gameEngine.setCameraOrthographic(); diff --git a/src/lib/Hero.js b/src/lib/Hero.js index f3769aa..14e5e86 100644 --- a/src/lib/Hero.js +++ b/src/lib/Hero.js @@ -103,7 +103,7 @@ class Hero{ if (this.ready) { if (this.canJump) { //walking - this.mixer.update(this.distance / 100) + this.mixer.update(this.distance / 10) } else { //were in the air this.mixer.update(this.delta) @@ -138,7 +138,7 @@ class Hero{ inputVelocity.x = 1 } - inputVelocity.setLength(this.delta * 100) + inputVelocity.setLength(this.delta * 10) // apply camera rotation to inputVelocity euler.y = this.gameEngine.cameraYaw.rotation.y; diff --git a/src/lib/MotionEngine.js b/src/lib/MotionEngine.js new file mode 100644 index 0000000..d31ba98 --- /dev/null +++ b/src/lib/MotionEngine.js @@ -0,0 +1,95 @@ +import { Clock } from 'three'; + +class MotionEngine { + constructor() { + const aq = []; + const clock = new Clock(); + + this.animationQueue = aq; + + function calcValues(target, values, initial, k = 1, mode = 'offset') { + Object.entries(values).forEach(([key, value]) => { + if (value && typeof value === 'object') { + if (!target[key]) { + target[key] = {}; + } + calcValues(target[key], value, initial[key], k, mode); + return; + } + if (mode == 'offset') { + target[key] = (initial[key] || 0) + value * k; + } else if (typeof (value) == 'function') { + target[key] = value(k); + } else { + target[key] = (initial[key] || 0) + (value - (initial[key] || 0)) * k; + } + }); + return target; + } + + this.add = function (a) { + a = Array.isArray(a) ? a : [a]; + a.forEach(e => { + aq.push(e); + }); + }; + + this.clear = function (object) { + for (var i = aq.length - 1; i >= 0; i--) { + if (object && aq[i].o == object || !object && aq[i].ct == aq[i].t) { + aq.splice(i, 1); + } + } + }; + + this.remove = function (a) { + let idx = aq.indexOf(a); + if (idx > -1) { + a.ct = 0; + this.animate(a, 0); + aq.splice(idx, 1); + } + }; + + this.update = () => { + let t = clock.getDelta(); + if (aq.length) { + aq.forEach(e => this.animate(e, t)); + this.clear(); + } + }; + + this.animate = function (e, t) { + if (e.d && (e.dt || 0) < e.d) { + e.dt = (e.dt || 0) + t; + return; + } + if (e.ct === undefined) { + e.ct = 0; + e.iv = calcValues({}, e.a, e.o, 0); + } + e.ct = e.ct + t; + if (e.ct > e.t) { + e.ct = e.t; + e.f && e.f(); + } + calcValues(e.o, e.a, e.iv, e.ct / e.t, e.m || 'value'); + if (e.ct == e.t && e.r) { + e.ct = e.m == 'offset' ? undefined : 0; + if (e.rd) { + e.dt = 0; + } + } + }; + + this.isActive = function (object) { + return aq.find(e => e.o == object); + }; + + this.isIdle = function () { + return aq.length == 0; + }; + } +} + +export {MotionEngine} \ No newline at end of file diff --git a/src/lib/gameEngine.js b/src/lib/gameEngine.js index 71d797f..e06270e 100644 --- a/src/lib/gameEngine.js +++ b/src/lib/gameEngine.js @@ -19,6 +19,7 @@ class GameEngine { async init(domNode, opts = {}) { this.w = domNode.clientWidth || 1200, this.h = domNode.clientHeight || 800; this.aspect = this.w / this.h + this.opts = opts; const gameEngine = this; this.perspectiveCamera = new THREE.PerspectiveCamera(45, this.aspect, 0.01, 1000); @@ -120,7 +121,9 @@ class GameEngine { gameEngine.mixers.forEach(m => m.update(delta)); gameEngine.handleXrAction(gameEngine, delta) gameEngine.render(scene, gameEngine.camera); - gameEngine.gizmo?.render(); + if (!renderer.xr.isPresenting) { + gameEngine.gizmo?.render(); + } } renderer.setAnimationLoop(animate); @@ -290,19 +293,30 @@ class GameEngine { } } if (gp.buttons[4]?.pressed) { - // gameEngine.setCameraOrthographic(); - // gameEngine.renderer.xr.updateCamera(gameEngine.orthographicCamera); - let session = gameEngine.renderer.xr.getFrame().session; - console.log(session); - session.pauseDepthSensing(); - } - if (gp.buttons[5]?.pressed) { - // gameEngine.setCameraOrthographic(); - // gameEngine.renderer.xr.updateCamera(gameEngine.orthographicCamera); - let session = gameEngine.renderer.xr.getFrame().session; - console.log(session); - session.resumeDepthSensing(); + let x = performance.now() % 1000; + if (x < 333) { + gameEngine.transformControls.setMode('translate'); + }else if (x < 666) { + gameEngine.transformControls.setMode('scale'); + }else{ + gameEngine.transformControls.setMode('rotate'); + } + // let nextMode = { + // 'translate': 'scale', + // 'scale': 'rotate', + // 'rotate': 'translate' + // } + // if(gameEngine.opts.designMode){; + // gameEngine.transformControls.setMode(nextMode[gameEngine.transformControls.getMode()]); + // } } + // if (gp.buttons[5]?.pressed) { + // // gameEngine.setCameraOrthographic(); + // // gameEngine.renderer.xr.updateCamera(gameEngine.orthographicCamera); + // let session = gameEngine.renderer.xr.getFrame().session; + // console.log(session); + // session.resumeDepthSensing(); + // } } } @@ -311,51 +325,57 @@ class GameEngine { //event.type !== 'move' && console.log(event) const controller = event.target; //console.log(event) - if (!controller.userData) return - if (controller.userData.active === false) return; - this.transformControls.getRaycaster().setFromXRController(controller); - switch (event.type) { - case 'selectstart': - this.transformControls.pointerDown(null); - break; - case 'selectend': - this.transformControls.pointerUp(null); - break; - case 'move': - this.transformControls.pointerHover(null); - this.transformControls.pointerMove(null); - break; + + if (this.opts.designMode){ + if (!controller.userData) return + if (controller.userData.active === false) return; + this.transformControls.getRaycaster().setFromXRController(controller); + switch (event.type) { + case 'selectstart': + this.transformControls.pointerDown(null); + break; + case 'selectend': + this.transformControls.pointerUp(null); + break; + case 'move': + this.transformControls.pointerHover(null); + this.transformControls.pointerMove(null); + break; + } } } onSelect(event) { const controller = event.target; - this.xrController1.userData.active = false; - this.xrController2.userData.active = false; - if (controller === this.xrController1) { - this.xrController1.userData.active = true; - this.xrController1.add(this.controllerLine); - } + if (this.opts.designMode){ + this.xrController1.userData.active = false; + this.xrController2.userData.active = false; - if (controller === this.xrController2) { - this.xrController2.userData.active = true; - this.xrController2.add(this.controllerLine); - } - - this.raycaster.setFromXRController(controller); - const intersects = this.raycaster.intersectObjects(this.activeObjects.children, true); - - intersects.forEach(o => { - while (o.object && !this.activeObjects.children.includes(o.object)) { - o.object = o.object.parent; + if (controller === this.xrController1) { + this.xrController1.userData.active = true; + this.xrController1.add(this.controllerLine); } - }) - if (intersects.length > 0) { - setTimeout(() => { - this.transformControls.attach(intersects[0].object); - }, 100); + if (controller === this.xrController2) { + this.xrController2.userData.active = true; + this.xrController2.add(this.controllerLine); + } + + this.raycaster.setFromXRController(controller); + const intersects = this.raycaster.intersectObjects(this.activeObjects.children, true); + + intersects.forEach(o => { + while (o.object && !this.activeObjects.children.includes(o.object)) { + o.object = o.object.parent; + } + }) + + if (intersects.length > 0) { + setTimeout(() => { + this.transformControls.attach(intersects[0].object); + }, 100); + } } } diff --git a/src/lib/mini-games/Game1.js b/src/lib/mini-games/Game1.js new file mode 100644 index 0000000..bf13551 --- /dev/null +++ b/src/lib/mini-games/Game1.js @@ -0,0 +1,96 @@ +import { BoxGeometry, Mesh, MeshBasicMaterial, Group, sRGBEncoding } from 'three'; +import { TextureLoader } from 'three/src/loaders/TextureLoader'; +import { MotionEngine } from '../MotionEngine'; + +class Game1 { + constructor(context, image, w, h) { + this.game = new Group(); + const aq = new MotionEngine(); + const pr = [[0, -1], [0, 1], [1, 0], [-1, 0], [0, 0], [0, 2]]; + let d = 1.2; + + let bm = new BoxGeometry(1, 1, 1); + let uv = bm.getAttribute('uv'); + + for (let i = 0; i < 6; i++) { + let s = [(i % w) / w, (i % h) / h]; + //top left + uv.array[8 * i] = s[0]; + uv.array[8 * i + 1] = s[1] + 1 / h; + //top right + uv.array[8 * i + 2] = s[0] + 1 / w; + uv.array[8 * i + 3] = s[1] + 1 / h; + //bottom left + uv.array[8 * i + 4] = s[0]; + uv.array[8 * i + 5] = s[1]; + //bottom right + uv.array[8 * i + 6] = s[0] + 1 / w; + uv.array[8 * i + 7] = s[1]; + } + + let material = new MeshBasicMaterial({ + map: new TextureLoader().load(image) + }); + material.map.encoding = sRGBEncoding; + + for (let i = 0; i < 6; i++) { + let b = bm.clone(); + let mesh = new Mesh(b, material); + mesh.position.set((i % w) * d, (i % h) * d, 0); + let ri; + do { + ri = Math.floor(Math.random() * 6); + } while (ri == this.game.children.length); + mesh.rotation.set(pr[ri][0] * Math.PI / 2, pr[ri][1] * Math.PI / 2, 0); + mesh._ri = ri; + this.game.add(mesh); + } + + this.game.children[0].onBeforeRender = () => { + this.update(); + }; + + var check = () => { + if (!this.game.children.length) return false; + let i = 0; + for (let c of this.game.children) { + if (Math.abs(c.rotation.x - pr[i][0] * Math.PI / 2) > 0.0001 || Math.abs(c.rotation.y - pr[i][1] * Math.PI / 2) > 0.0001) return false; + i++; + } + return true; + }; + + let clickFn = (i) => { + if (!this.done && !aq.isActive(i.object)) { + i.object._ri = (i.object._ri + 1) % 6; + aq.add({ + o: i.object, + a: { rotation: { x: pr[i.object._ri][0] * Math.PI / 2, y: pr[i.object._ri][1] * Math.PI / 2 } }, + t: .5 + }); + } + }; + + this.game.children.forEach(c => { + context.clickable.add(c, clickFn); + }); + + this.update = () => { + aq.update(); + if (aq.isIdle() && !this.done && check()) { + this.done = true; + this.game.children.forEach((c, i) => { + aq.add({ + o: c, + a: { position: { x: i % w, y: i % h } }, + t: 1, + f: i == 0 && this.onfinish + }); + }); + context.dashboard.addPoints(10); + } + }; + } +} + +export {Game1} \ No newline at end of file diff --git a/src/lib/mini-games/Game2.js b/src/lib/mini-games/Game2.js new file mode 100644 index 0000000..47deb0d --- /dev/null +++ b/src/lib/mini-games/Game2.js @@ -0,0 +1,146 @@ +import { BoxGeometry, Mesh, MeshBasicMaterial, Group, sRGBEncoding } from 'three'; +import { TextureLoader } from 'three/src/loaders/TextureLoader'; +import { MotionEngine } from '../MotionEngine'; + +class Game2 { + constructor(context, image, w, h) { + const texture = new TextureLoader().load(image); + texture.encoding = sRGBEncoding; + const material = new MeshBasicMaterial({ + map: texture + }); + const aq = new MotionEngine(); + const m2 = new MeshBasicMaterial({ + map: texture, + transparent: true, + opacity: 0.37 + }); + let last, lidx = w - 1; + + this.game = new Group(); + let d = 1.2, p = []; + + function check() { + if (p.length) { + for (let i = 0; i < p.length; i++) { + if (p[i] != i) return false; + } + return true; + } + return false; + } + + function xch(x, y) { + let temp = p[x]; + p[x] = p[y]; + p[y] = temp; + } + + this.shuffle = function () { + function getMoves() { + let m = [], pl = p[lidx]; + let xl = pl % w, yl = ~~(pl / h); + if (xl > 0) m.push(p.indexOf(pl - 1)); + if (xl < w - 1) m.push(p.indexOf(pl + 1)); + if (yl > 0) m.push(p.indexOf(pl - w)); + if (yl < h - 1) m.push(p.indexOf(pl + w)); + return m; + } + for (let i = 0; i < w * h; i++) { + p[i] = i; + } + + for (let iter = 0; iter < 73 * w * h; iter++) { + let m = getMoves(); + xch(m[Math.floor(Math.random() * m.length)], lidx); + } + // while (p.length<9){ + // let n = Math.floor(Math.random()*9); + // if (p.indexOf(n) == -1) p.push(n); + // } + p.forEach((e, i) => { + let x = e % w, y = ~~(e / h); + this.game.children[i].position.set(x * d, y * d, 0); + }); + }; + + for (let i = 0; i < w * h; i++) { + let x = i % w, xp = x / w; + let y = ~~(i / h), yp = y / h; + let bg = new BoxGeometry(1, 1, 1); + let uv = bg.getAttribute('uv'); + for (let k = 0; k < 6; k++) { + //top left + uv.array[8 * k] = xp; + uv.array[8 * k + 1] = yp + 1 / h; + //top right + uv.array[8 * k + 2] = xp + 1 / w; + uv.array[8 * k + 3] = yp + 1 / h; + //bottom left + uv.array[8 * k + 4] = xp; + uv.array[8 * k + 5] = yp; + //bottom right + uv.array[8 * k + 6] = xp + 1 / w; + uv.array[8 * k + 7] = yp; + } + let mesh = new Mesh(bg, i != lidx ? material : m2); + mesh.position.set(x * d, y * d, 0); + this.game.add(mesh); + } + last = this.game.children[lidx]; + + this.game.children[0].onBeforeRender = () => { + this.update(); + }; + + this.shuffle(); + + let clickFn = (i) => { + if (!this.done && !aq.isActive(i.object)) { + let idx = this.game.children.indexOf(i.object); + if (idx == lidx) return; //we ignore the empty cell + let xc = p[idx] % w, yc = ~~(p[idx] / h); + let xl = p[lidx] % w, yl = ~~(p[lidx] / h); + if (Math.abs(xc - xl) + Math.abs(yc - yl) == 1) { + aq.add({ + o: i.object, + a: { position: { x: (xl - xc) * d, y: (yl - yc) * d } }, + t: .3, + m: 'offset' + }); + aq.add({ + o: last, + a: { position: { x: (xc - xl) * d, y: (yc - yl) * d } }, + t: .3, + m: 'offset' + }); + xch(idx, lidx); + } + } + }; + + this.game.children.forEach(c => { + context.clickable.add(c, clickFn); + }); + + this.update = () => { + aq.update(); + if (aq.isIdle() && !this.done && check()) { + this.done = true; + this.game.children.forEach((c, i) => { + last.material = material; + aq.add({ + o: c, + a: { position: { x: i % w, y: ~~(i / h) } }, + t: 1, + f: i == 0 && this.onfinish + }); + }); + context.dashboard.addPoints(10); + } + }; + + } +} + +export { Game2 }; \ No newline at end of file