babylon.jsでOculus Questのコントローラーを簡単に扱えるAbstractOculusQuestControllerをtypescriptで書いた
AbstractOculusQuestController
code:typescript
import * as BABYLON from '@babylonjs/core';
import { WebXRProfiledMotionController } from "@babylonjs/core";
import { WebXRInputSource } from "@babylonjs/core";
export default abstract class AbstractOculusQuestController{
xrHelper: any;
rightInputSource: WebXRInputSource;
leftInputSource: WebXRInputSource;
rightController: WebXRProfiledMotionController;
leftController: WebXRProfiledMotionController;
constructor(xrHelper){
this.xrHelper = xrHelper;
xrHelper.input.onControllerAddedObservable.add((inputSource:WebXRInputSource) => {
inputSource.onMotionControllerInitObservable.add((controller: WebXRProfiledMotionController) => {
if(controller.handness === "right"){
this.rightInputSource = inputSource;
this.rightController = controller;
// 右手にしかないボタン
const aButtonComponent = controller.getComponent("a-button");
aButtonComponent.onButtonStateChangedObservable.add((component) => {
if(component.value > 0.8 && component.pressed) {
this.onAButtonPressed(controller, component);
}
});
const bButtonComponent = controller.getComponent("b-button");
bButtonComponent.onButtonStateChangedObservable.add((component) => {
if(component.value > 0.8 && component.pressed) {
this.onBButtonPressed(controller, component)
}
});
}else{
this.leftInputSource = inputSource;
this.leftController = controller;
// 左手にしかないボタン
const xButtonComponent = controller.getComponent("x-button");
xButtonComponent.onButtonStateChangedObservable.add((component) => {
if(component.value > 0.8 && component.pressed) {
this.onXButtonPressed(controller, component)
}
});
const yButtonComponent = controller.getComponent("y-button");
yButtonComponent.onButtonStateChangedObservable.add((component) => {
if(component.value > 0.8 && component.pressed) {
this.onYButtonPressed(controller, component)
}
});
}
// 両手にあるボタン
const mainTriggerComponent = controller.getComponent("xr-standard-trigger");
mainTriggerComponent.onButtonStateChangedObservable.add((component) => {
if(component.value > 0.8 && component.pressed) {
this.onAnyTriggered(controller, component)
if(controller.handness === "right"){
this.onRightTriggered(controller, component)
}else{
this.onLeftTriggered(controller, component)
}
}
});
const squeezeComponent = controller.getComponent("xr-standard-squeeze");
squeezeComponent.onButtonStateChangedObservable.add((component) => {
if(component.value > 0.8 && component.pressed) {
this.onAnySqueezed(controller, component)
if(controller.handness === "right"){
this.onRightSqueezed(controller, component)
}else{
this.onLeftSqueezed(controller, component)
}
}
});
const thumbStickComponent = controller.getComponent("xr-standard-thumbstick");
thumbStickComponent.onButtonStateChangedObservable.add((component) => {
if(component.value > 0.8 && component.pressed) {
this.onAnyThumbStickPressed(controller, component)
if(controller.handness === "right"){
this.onRightThumbStickPressed(controller, component)
}else{
this.onLeftThumbStickPressed(controller, component)
}
}
});
});
});
}
abstract onAButtonPressed(controller, component)
abstract onBButtonPressed(controller, component)
abstract onXButtonPressed(controller, component)
abstract onYButtonPressed(controller, component)
abstract onAnyTriggered(controller, component)
abstract onRightTriggered(controller, component)
abstract onLeftTriggered(controller, component)
abstract onAnySqueezed(controller, component)
abstract onRightSqueezed(controller, component)
abstract onLeftSqueezed(controller, component)
abstract onAnyThumbStickPressed(controller, component)
abstract onRightThumbStickPressed(controller, component)
abstract onLeftThumbStickPressed(controller, component)
getControllerPosition(controller){
if(controller.rootMesh === undefined){
return new BABYLON.Vector3(0, 0, 0);
}
return controller.rootMesh.absolutePosition;
}
getControllerDirection(controller){
let ray: BABYLON.Ray = new BABYLON.Ray(new BABYLON.Vector3(), new BABYLON.Vector3());
if(controller.handness === "right"){
this.rightInputSource.getWorldPointerRayToRef(ray, true);
}else{
this.leftInputSource.getWorldPointerRayToRef(ray, true);
}
return ray.direction;
}
getMeshUnderControllerPointer(controller){
if(controller.handness === "right"){
return this.xrHelper.pointerSelection.getMeshUnderPointer(this.rightInputSource.uniqueId);
}else {
return this.xrHelper.pointerSelection.getMeshUnderPointer(this.leftInputSource.uniqueId);
}
}
getRightControllerPosition(){
return this.getControllerPosition(this.rightController);
}
getRightControllerDirection(){
return this.getControllerDirection(this.rightController);
}
getMeshUnderRightControllerPointer(){
return this.getMeshUnderControllerPointer(this.rightController);
}
getLeftControllerPosition(){
return this.getControllerPosition(this.leftController);
}
getLeftControllerDirection(){
return this.getControllerDirection(this.leftController);
}
getMeshUnderLeftContollerPointer(){
return this.getMeshUnderControllerPointer(this.leftController);
}
}
使用例
これを適当に継承するとこんな感じで簡単にコントローラーの各ボタンに対してイベントが実装できて便利
(constructorにはこのクラスで使いそうな引数を適当に追加して一行目でsuper(xrHelper)を呼ぶんやぞ)
使いたくないボタンのメソッドは空にしておけばOK
code:typescript
class OculusQuestController extends AbstractOculusQuestController {
scene: BABYLON.Scene;
physicsRoot: BABYLON.Mesh;
textBlock: GUI.TextBlock;
constructor(xrHelper, scene, physicsRoot, textBlock){
super(xrHelper);
this.scene = scene;
this.physicsRoot = physicsRoot;
this.textBlock = textBlock;
}
// なにか押されたらとにかく球体を発射する
shoot(controller){
var sphere = BABYLON.MeshBuilder.CreateSphere("sphere"+(new Date().getTime()), {diameter:0.1}, this.scene);
// 球体の初期位置はコントローラーと同じにする
sphere.position = this.getControllerPosition(controller);
// 物理演算するように指定
sphere.physicsImpostor = new BABYLON.PhysicsImpostor(sphere, BABYLON.PhysicsImpostor.SphereImpostor, { mass: 5 }, this.scene);
this.physicsRoot.addChild(sphere);
// コントローラーとおなじ方向に発射したいのでコントローラーの向きを得る
let direction = this.getControllerDirection(controller);
// なぜかやや上向きなので微調整する
var forceDirection = new BABYLON.Vector3(direction.x, direction.y-0.5, direction.z);
var forceMagnitude = 30;
// 球体に力を加えて発射する
sphere.physicsImpostor.applyImpulse(forceDirection.scale(forceMagnitude), sphere.getAbsolutePosition());
}
onAButtonPressed(controller, component) {
this.textBlock.text = "A Button Pressed";
this.shoot(controller);
}
onBButtonPressed(controller, component) {
this.textBlock.text = "B Button Pressed";
this.shoot(controller);
}
onXButtonPressed(controller, component) {
this.textBlock.text = "X Button Pressed";
this.shoot(controller);
}
onYButtonPressed(controller, component) {
this.textBlock.text = "Y Button Pressed";
this.shoot(controller);
}
onAnyTriggered(controller, component) {
this.shoot(controller);
}
onRightTriggered(controller, component) {
this.textBlock.text = "Right triggered";
}
onLeftTriggered(controller, component) {
this.textBlock.text = "Left triggered";
}
onAnySqueezed(controller, component) {
this.shoot(controller);
}
onRightSqueezed(controller, component) {
this.textBlock.text = "Right squeezed";
}
onLeftSqueezed(controller, component) {
this.textBlock.text = "Left squeezed";
}
onAnyThumbStickPressed(controller, component) {
this.shoot(controller);
}
onRightThumbStickPressed(controller, component) {
this.textBlock.text = "Right Thumb Stick Pressed";
}
onLeftThumbStickPressed(controller, component) {
this.textBlock.text = "Left Thumb Stick Pressed";
}
}