From 12d9d0044a08b5fb99e2d86d919c815c4cb6a453 Mon Sep 17 00:00:00 2001 From: Kevin Ngo Date: Thu, 20 Sep 2018 05:12:09 -0700 Subject: [PATCH] press button on non-active hand to swap raycaster in menu mode (fixes #9) --- package-lock.json | 6 +- package.json | 2 +- src/components/debug-controller.js | 141 +++++++++++++++++------------ src/components/hand-swapper.js | 32 +++++++ src/components/pauser.js | 29 ++++++ src/components/saber-controls.js | 52 +++++------ src/index.html | 58 ++++++++---- src/state/index.js | 45 ++++----- 8 files changed, 234 insertions(+), 131 deletions(-) create mode 100644 src/components/hand-swapper.js create mode 100644 src/components/pauser.js diff --git a/package-lock.json b/package-lock.json index 53002f6..9c0054f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -120,9 +120,9 @@ "integrity": "sha1-jX/8ViYbozraC6izQf3eif8rhlQ=" }, "aframe-haptics-component": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/aframe-haptics-component/-/aframe-haptics-component-1.4.1.tgz", - "integrity": "sha512-ZZxD95DeMN7/NpLOe7Es3WCXNB37aIqttqI3meaTm00+OY0Q5q/1NkVsupthIlECO99iQg1evBhzRBfxj4wFWg==" + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/aframe-haptics-component/-/aframe-haptics-component-1.4.2.tgz", + "integrity": "sha512-am5PqjW6LB11gbgzuMHAwM54X7X9qgV9ow8cRFHNW7XfzvelZllj9JJ8BjhiqdOQz6bRy+14RlY/e54kk2ZCpg==" }, "aframe-layout-component": { "version": "5.2.0", diff --git a/package.json b/package.json index ce74b7f..022d217 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "aframe-event-decorators": "^1.0.2", "aframe-event-set-component": "^4.0.1", "aframe-gltf-part-component": "1.1.0", - "aframe-haptics-component": "^1.4.1", + "aframe-haptics-component": "^1.4.2", "aframe-layout-component": "^5.2.0", "aframe-orbit-controls": "^1.2.0", "aframe-particle-system-component": "^1.0.11", diff --git a/src/components/debug-controller.js b/src/components/debug-controller.js index 73ac16e..414621f 100644 --- a/src/components/debug-controller.js +++ b/src/components/debug-controller.js @@ -3,38 +3,40 @@ * Position controller in front of camera. */ AFRAME.registerComponent('debug-controller', { - schema: { - enabled: { default: false }, - }, - - init: function() { + init: function () { var primaryHand; var secondaryHand; - if (!this.data.enabled && !AFRAME.utils.getUrlParameter('debug')) { - return; - } + if (!AFRAME.utils.getUrlParameter('debug')) { return; } console.log('%c debug-controller enabled ', 'background: #111; color: red'); + this.isCloning = false; + this.isDeleting = false; this.isTriggerDown = false; - primaryHand = document.getElementById('leftGloveContainer'); - secondaryHand = document.getElementById('rightGloveContainer'); + primaryHand = document.getElementById('rightHand'); + secondaryHand = document.getElementById('leftHand'); - primaryHand.setAttribute('position', { x: 0.2, y: 1.5, z: -0.5 }); - secondaryHand.setAttribute('position', { x: -0.2, y: 1.5, z: -0.5 }); - primaryHand.setAttribute('rotation', { x: 0, y: 0, z: 0 }); - secondaryHand.setAttribute('rotation', { x: 0, y: 0, z: 0 }); + if (AFRAME.utils.getUrlParameter('debug') === 'oculus') { + primaryHand.emit('controllerconnected', {name: 'oculus-touch-controls'}); + secondaryHand.emit('controllerconnected', {name: 'oculus-touch-controls'}); + primaryHand.setAttribute('controller', 'controllerType', 'oculus-touch-controls'); + secondaryHand.setAttribute('controller', 'controllerType', 'oculus-touch-controls'); + } else { + primaryHand.emit('controllerconnected', {name: 'vive-controls'}); + secondaryHand.emit('controllerconnected', {name: 'vive-controls'}); + primaryHand.setAttribute('controller', 'controllerType', 'vive-controls'); + secondaryHand.setAttribute('controller', 'controllerType', 'vive-controls'); + } document.addEventListener('keydown', evt => { var primaryPosition; var primaryRotation; var secondaryPosition; + var secondaryRotation; - if (!evt.shiftKey) { - return; - } + if (!evt.shiftKey) { return; } // for trigger. if (evt.keyCode === 32) { @@ -48,6 +50,18 @@ AFRAME.registerComponent('debug-controller', { return; } + // for secondary trigger. + if (evt.keyCode === 81) { + if (this.isSecondaryTriggerDown) { + secondaryHand.emit('triggerup'); + this.isSecondaryTriggerDown = false; + } else { + secondaryHand.emit('triggerdown'); + this.isSecondaryTriggerDown = true; + } + return; + } + // secondary grip. if (evt.keyCode === 78) { if (this.secondaryGripDown) { @@ -70,48 +84,63 @@ AFRAME.registerComponent('debug-controller', { } } + // Menu button <1>. + if (evt.keyCode === 49) { + secondaryHand.emit('menudown'); + } + // Position bindings. - if (!evt.ctrlKey) { - primaryPosition = primaryHand.object3D.position; - if (evt.keyCode === 72) { - primaryPosition.x -= 0.01; - } // h. - if (evt.keyCode === 74) { - primaryPosition.y -= 0.01; - } // j. - if (evt.keyCode === 75) { - primaryPosition.y += 0.01; - } // k. - if (evt.keyCode === 76) { - primaryPosition.x += 0.01; - } // l. - if (evt.keyCode === 59 || evt.keyCode === 186) { - primaryPosition.z -= 0.01; - } // ;. - if (evt.keyCode === 222) { - primaryPosition.z += 0.01; - } // ;. + if (evt.ctrlKey) { + secondaryPosition = secondaryHand.getAttribute('position'); + if (evt.keyCode === 72) { secondaryPosition.x -= 0.01 } // h. + if (evt.keyCode === 74) { secondaryPosition.y -= 0.01 } // j. + if (evt.keyCode === 75) { secondaryPosition.y += 0.01 } // k. + if (evt.keyCode === 76) { secondaryPosition.x += 0.01 } // l. + if (evt.keyCode === 59 || evt.keyCode === 186) { secondaryPosition.z -= 0.01 } // ;. + if (evt.keyCode === 222) { secondaryPosition.z += 0.01 } // ;. + secondaryHand.setAttribute('position', AFRAME.utils.clone(secondaryPosition)); } else { - secondaryPosition = secondaryHand.object3D.position; - if (evt.keyCode === 72) { - secondaryPosition.x -= 0.01; - } // h. - if (evt.keyCode === 74) { - secondaryPosition.y -= 0.01; - } // j. - if (evt.keyCode === 75) { - secondaryPosition.y += 0.01; - } // k. - if (evt.keyCode === 76) { - secondaryPosition.x += 0.01; - } // l. - if (evt.keyCode === 59 || evt.keyCode === 186) { - secondaryPosition.z -= 0.01; - } // ;. - if (evt.keyCode === 222) { - secondaryPosition.z += 0.01; - } // ;. + primaryPosition = primaryHand.getAttribute('position'); + if (evt.keyCode === 72) { primaryPosition.x -= 0.01 } // h. + if (evt.keyCode === 74) { primaryPosition.y -= 0.01 } // j. + if (evt.keyCode === 75) { primaryPosition.y += 0.01 } // k. + if (evt.keyCode === 76) { primaryPosition.x += 0.01 } // l. + if (evt.keyCode === 59 || evt.keyCode === 186) { primaryPosition.z -= 0.01 } // ;. + if (evt.keyCode === 222) { primaryPosition.z += 0.01 } // ;. + primaryHand.setAttribute('position', AFRAME.utils.clone(primaryPosition)); + } + + // Rotation bindings. + if (evt.ctrlKey) { + secondaryRotation = secondaryHand.getAttribute('rotation'); + if (evt.keyCode === 89) { secondaryRotation.x -= 10 } // y. + if (evt.keyCode === 79) { secondaryRotation.x += 10 } // o. + if (evt.keyCode === 85) { secondaryRotation.y -= 10 } // u. + if (evt.keyCode === 73) { secondaryRotation.y += 10 } // i. + secondaryHand.setAttribute('rotation', AFRAME.utils.clone(secondaryRotation)); + } else { + primaryRotation = primaryHand.getAttribute('rotation'); + if (evt.keyCode === 89) { primaryRotation.x -= 10 } // y. + if (evt.keyCode === 79) { primaryRotation.x += 10 } // o. + if (evt.keyCode === 85) { primaryRotation.y -= 10 } // u. + if (evt.keyCode === 73) { primaryRotation.y += 10 } // i. + primaryHand.setAttribute('rotation', AFRAME.utils.clone(primaryRotation)); } }); }, + + play: function () { + var primaryHand; + var secondaryHand; + + if (!AFRAME.utils.getUrlParameter('debug')) { return; } + + primaryHand = document.getElementById('rightHand'); + secondaryHand = document.getElementById('leftHand'); + + secondaryHand.object3D.position.set(-0.2, 1.5, -0.5); + primaryHand.object3D.position.set(0.2, 1.5, -0.5); + secondaryHand.setAttribute('rotation', {x: 35, y: 0, z: 0}); + primaryHand.setAttribute('rotation', {x: 35, y: 0, z: 0}); + } }); diff --git a/src/components/hand-swapper.js b/src/components/hand-swapper.js new file mode 100644 index 0000000..a26b673 --- /dev/null +++ b/src/components/hand-swapper.js @@ -0,0 +1,32 @@ +const events = [ + 'triggerdown', + 'gripdown', + 'abuttondown', + 'bbuttondown', + 'xbuttondown', + 'ybuttondown', + 'trackpaddown' +]; + +/** + * Swap left or right-handed mode. + */ +AFRAME.registerComponent('hand-swapper', { + schema: { + enabled: {default: false} + }, + + init: function () { + this.swapHand = this.swapHand.bind(this); + events.forEach(event => { + this.el.addEventListener(event, this.swapHand); + }); + }, + + swapHand: function () { + if (!this.data.enabled) { return; } + + // Handled via state. + this.el.sceneEl.emit('activehandswap', null, false); + } +}); diff --git a/src/components/pauser.js b/src/components/pauser.js new file mode 100644 index 0000000..c98275c --- /dev/null +++ b/src/components/pauser.js @@ -0,0 +1,29 @@ +const events = [ + 'menudown', + 'abuttondown', + 'bbuttondown', + 'xbuttondown', + 'ybuttondown' +]; + +/** + * Tell app to pause game if playing. + */ +AFRAME.registerComponent('pauser', { + schema: { + enabled: {default: true} + }, + + init: function () { + this.pauseGame = this.pauseGame.bind(this); + + events.forEach(event => { + this.el.addEventListener(event, this.pauseGame); + }); + }, + + pauseGame: function () { + if (!this.data.enabled) { return; } + this.el.sceneEl.emit('pausegame', null, false); + } +}); diff --git a/src/components/saber-controls.js b/src/components/saber-controls.js index fa9b314..755ea34 100644 --- a/src/components/saber-controls.js +++ b/src/components/saber-controls.js @@ -1,6 +1,7 @@ AFRAME.registerComponent('saber-controls', { schema: { - hand: {default: 'left', oneOf: ['left', 'right']}, + activeHand: {default: 'right'}, + hand: {default: 'right', oneOf: ['left', 'right']}, bladeEnabled: {default: true} }, @@ -13,11 +14,26 @@ AFRAME.registerComponent('saber-controls', { var el = this.el; var data = this.data; el.addEventListener('controllerconnected', this.initSaber.bind(this)); - el.setAttribute('oculus-touch-controls', {hand: data.hand, model: false}); - el.setAttribute('vive-controls', {hand: data.hand, model: true}); - el.setAttribute('windows-motion-controls', {hand: data.hand, model: false}); + + const hand = {hand: data.hand, model: false}; + el.setAttribute('oculus-touch-controls', hand); + el.setAttribute('vive-controls', hand); + el.setAttribute('windows-motion-controls', hand); }, - + + update: function () { + if (!this.bladeEl) { return; } + this.bladeEl.object3D.visible = this.data.bladeEnabled; + if (this.data.bladeEnabled) { + this.bladeEl.emit('drawblade'); + } + }, + + tick: function () { + if (!this.bladeEl || !this.bladeEl.getObject3D('mesh')) { return; } + this.boundingBox.setFromObject(this.bladeEl.getObject3D('mesh')); + }, + initSaber: function (evt) { var el = this.el; var saberHandleEl = document.createElement('a-entity'); @@ -33,12 +49,12 @@ AFRAME.registerComponent('saber-controls', { bladeEl.setAttribute('material', {shader: 'flat', color: this.colors[this.data.hand]}); bladeEl.setAttribute('geometry', {primitive: 'box', height: 0.9, depth: 0.020, width: 0.020}); bladeEl.setAttribute('position', '0 -0.55 0'); - bladeEl.setAttribute('play-sound', {event: 'draw', sound: "#saberDraw"}); + bladeEl.setAttribute('play-sound', {event: 'drawblade', sound: "#saberDraw"}); bladeEl.object3D.visible = this.data.bladeEnabled; - // For blade saber draw animation + // For blade saber draw animation. bladeElPivot.appendChild(bladeEl); - bladeElPivot.setAttribute('animation', 'property: scale; from: 0 0 0; to: 1.0 1.0 1.0; dur: 2000; easing: easeOutCubic; startEvents: draw'); + bladeElPivot.setAttribute('animation', 'property: scale; from: 0 0 0; to: 1.0 1.0 1.0; dur: 2000; easing: easeOutCubic; startEvents: drawblade'); saberHandleEl.setAttribute('material', {shader: 'flat', color: '#151515'}); saberHandleEl.setAttribute('geometry', {primitive: 'box', height: 0.2, depth: 0.025, width: 0.025}); @@ -62,23 +78,8 @@ AFRAME.registerComponent('saber-controls', { this.controllerConnected = true; this.controllerType = evt.detail.name; - if (this.data.hand === 'left') { return; } - el.setAttribute('cursor', controllerConfig.cursor || {}); - el.setAttribute('raycaster', 'objects: [raycastable]; far: 20; enabled: true'); - el.setAttribute('line', {opacity: 0.75, color: 'pink', end: {x: 0, y: 0, z: -20}}); - }, - update: function () { - if (!this.bladeEl) { return; } - this.bladeEl.object3D.visible = this.data.bladeEnabled; - if (this.data.bladeEnabled) { - this.bladeEl.emit('draw'); - } - }, - - tick: function () { - if (!this.bladeEl) { return; } - this.boundingBox.setFromObject(this.bladeEl.getObject3D('mesh')); + el.querySelector('.raycaster').setAttribute('cursor', controllerConfig.cursor || {}); }, config: { @@ -117,5 +118,4 @@ AFRAME.registerComponent('saber-controls', { }, } } - -}); \ No newline at end of file +}); diff --git a/src/index.html b/src/index.html index 65b7657..21f1070 100644 --- a/src/index.html +++ b/src/index.html @@ -12,6 +12,7 @@ bind__song-preview-system="selectedChallengeId: menuSelectedChallenge.id" bind-toggle__overlay="menu.active" console-shortcuts + debug-controller effect-bloom="strength: 0.7" proxy-event="event: menuchallengeselect; to: #searchResultsContainer, #menuDifficultiesGroup" overlay="object: #menu, #keyboard, #rightHand, #leftHand" @@ -27,20 +28,19 @@ - - + + - - + {% include './templates/stage.html' %} @@ -48,27 +48,47 @@ {% include './templates/menu.html' %} + + - + saber-controls="hand: left" + haptics="events: mouseenter; dur: 35; force: 0.075"> + + + + + - + {% if not IS_PRODUCTION %} + + {% endif %} diff --git a/src/state/index.js b/src/state/index.js index d3e05ba..45d5581 100644 --- a/src/state/index.js +++ b/src/state/index.js @@ -4,8 +4,19 @@ const challengeDataStore = {}; const hasInitialChallenge = !!AFRAME.utils.getUrlParameter('challenge'); const SEARCH_PER_PAGE = 6; +/** + * State handler. + * + * 1. `handlers` is an object of events that when emitted to the scene will run the handler. + * + * 2. The handler function modifies the state. + * + * 3. Entities and components that are `bind`ed automatically update: + * `bind__=": some.item.in.state"` + */ AFRAME.registerState({ initialState: { + activeHand: localStorage.getItem('hand') || 'right', challenge: { author: '', difficulty: '', @@ -36,9 +47,6 @@ AFRAME.registerState({ score: 0, streak: 0 }, - // screen: keep track of layers or depth. Like breadcrumbs. - screen: hasInitialChallenge ? 'challenge' : 'home', - screenHistory: [], search: { active: true, page: 0, @@ -50,6 +58,14 @@ AFRAME.registerState({ }, handlers: { + /** + * Swap left-handed or right-handed mode. + */ + activehandswap: state => { + state.activeHand = state.activeHand === 'right' ? 'left' : 'right'; + localStorage.setItem('activeHand', state.activeHand); + }, + beatloaderfinish: (state) => { state.challenge.isLoading = false; }, @@ -151,29 +167,6 @@ AFRAME.registerState({ // computeState: (state) => { } }); -/** - * Push screen onto history and set to current. - */ -function setScreen (state, screen) { - if (state.screen === screen) { return; } - state.screenHistory.push(screen); - state.screen = screen; -} - -/** - * Pop screen off history. - * Set new current screen if any. - */ -function popScreen (state) { - var prevScreen; - prevScreen = state.screenHistory.pop(); - if (state.screenHistory.length === 0) { - state.screen = ''; - return; - } - state.screen = state.screenHistory[state.screenHistory.length - 1]; -} - function computeSearchPagination (state) { let numPages = Math.ceil(state.search.results.length / SEARCH_PER_PAGE); state.search.hasPrev = state.search.page > 0;