Webブラウザで玉転がしを作成してみる（1）

## はじめに
この記事は　[Babylon.js Advent Calendar 2024](https://qiita.com/advent-calendar/2024/babylonjs) 　
の、15日目の記事です

何度かに分けて、スマホのセンサーにアクセスして玉転がしをするページを作成してみます。


## 今回の内容。
スマートフォンには、デバイスの動きや向きを検知する **ジャイロセンサー** や **加速度センサー** が搭載されており、これらのセンサーの値はブラウザからでもアクセスすることができます。
その値を画面に表示する方法についてまとめました。


### デバイスのセンサー類にアクセスする
まずはジャイロセンサーにアクセスしてみます。

**DeviceOrientationEvent** が、デバイスの方向の情報を取得するイベントです。方向が変更されるたびにイベントが発火し、以下の情報を取得できます。
１．alpha（スマホの回転）
２．beta（スマホの上下の傾き）
３．gamma（スマホの左右の傾き）
図にすると以下のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594551/aa7e093c-225a-8f27-3ad9-f4e57decf50a.png)


では、ジャイロセンサーにアクセスしてみましょう。
ジャイロセンサーにアクセスするため、DeviceOrientationEventイベントを登録してイベントの発火でセンサーの値を表示するようにしてみます。
DeviceOrientationEventをリスナーに登録して、発火したeventから **alpha、beta、gamma** の値を作用しているだけです。

``` javascript:ジャイロセンサーアクセス部分
//deviceorientationイベントにhandleOrientation関数を登録し
window.addEventListener('deviceorientation', handleOrientation);

//センサーの値をテキスト欄に表示する。値はeventから収集
const handleOrientation = (event) => {
    const alpha = (event.alpha || 0);
    const beta = (event.beta || 0) ;
    const gamma = (event.gamma || 0) ;

    alphaText.text = `alpha: ${alpha.toFixed(2)}°`;
    betaText.text = `beta: ${beta.toFixed(2)}°`;
    gammaText.text = `gamma: ${gamma.toFixed(2)}°`;
};
    
```
### ジャイロセンサーへのアクセス確認
playgroundに登録したので、スマートフォンで以下のURLにアクセスしてみます。
https://playground.babylonjs.com/#2S9H94#18

以下のように画面が表示されるはずです。
センサー読み取り開始ボタンを押して処理を始めましょう。
<img width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594551/a3cd7053-838d-c763-8041-a7801044702e.png">

iPhoneのSafariでは、センサーへのアクセス許可を求めるダイアログが表示されるので許可します。
（Androidではこの確認は不要らしいですが持ってないので未確認です｡｡｡）
<img width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594551/2de3c08e-5e8e-8c07-7825-ef08df2b434d.png">

アクセスの許可をすると、画面の上部に表示している、**alpha,beta,gamma** の値が変わるようになりました。アクセス成功です。


<img width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594551/a071923c-6371-7d94-de26-00511a60c9f2.png">


意図通りに値が取れているか確認してみましょう。
スマートフォンを立ててみると、**beta** の値が90度に近づいていきました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594551/470c36d2-3d4e-f523-7eb1-1d2f2c564599.png)

次にスマートフォンを横向きに傾けてみます。
**gamma** の値が-90度に近づいていきました。
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594551/6e154dcd-c701-691f-8ffe-1a27b2f3739b.png">

画面の傾きを検出出来ていることが確認できましたね。

## 最後に
ジャイロセンサーの傾きの値にアクセスする方法について記載しました。
次回は、傾きによってボールを操作してみようと思います。




### ここまでのPlaygroundに記載したソースは以下です。
``` javascript:ジャイロセンサーの表示処理（全文）　

const createScene = function () {
    const scene = new BABYLON.Scene(engine);
    const camera = new BABYLON.FreeCamera("camera", new BABYLON.Vector3(0, 0, -1), scene);
    camera.setTarget(BABYLON.Vector3.Zero());

    // GUI設定
    const advancedTexture = BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI("UI");
    // 開始ボタンを作成
    const btnStart = BABYLON.GUI.Button.CreateSimpleButton("btnStart", "センサー読み取り開始");
    btnStart.width = "600px"; // ボタンの幅
    btnStart.height = "80px"; // ボタンの高さ
    btnStart.color = "white"; // テキストの色
    btnStart.fontSize = "24px"; // フォントサイズ
    btnStart.background = "green"; // ボタンの背景色
    btnStart.cornerRadius = 10; // 角の丸み
    btnStart.horizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_CENTER;
    btnStart.verticalAlignment = BABYLON.GUI.Control.VERTICAL_ALIGNMENT_BOTTOM;
    btnStart.onPointerClickObservable.add(() => {
        // ボタンクリック センサーイベント登録
        init();
    });
    advancedTexture.addControl(btnStart);   //ボタンを追加
   
    // 表示用パネルの作成
    const pnlInfo = new BABYLON.GUI.StackPanel();
    pnlInfo.verticalAlignment = BABYLON.GUI.Control.VERTICAL_ALIGNMENT_TOP;
    pnlInfo.horizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_CENTER;
    pnlInfo.background = "rgba(0, 0, 0, 1.0)";
    pnlInfo.width = "300px";
    advancedTexture.addControl(pnlInfo);
    const createTextBlock = (name, text) => {
        const textBlock = new BABYLON.GUI.TextBlock();
        textBlock.name = name;
        textBlock.text = text;
        textBlock.color = "white";
        textBlock.fontSize = 24;
        textBlock.height = "32px";
        pnlInfo.addControl(textBlock);
        return textBlock;
    };
    //表示用のText領域を生成
    const alphaText = createTextBlock("alpha", "Alpha: 0°");
    const betaText = createTextBlock("beta", "Beta: 0°");
    const gammaText = createTextBlock("gamma", "Gamma: 0°");


    const init = async () => {

        //DeviceOrientationEventを登録
        try {
            if (typeof DeviceOrientationEvent.requestPermission === 'function') {
                await DeviceOrientationEvent.requestPermission();
            }
            window.addEventListener('deviceorientation', handleOrientation);
        } catch (error) {
            console.log(' permission denied');
        }
    };

    //センサーの値を表示する
    const handleOrientation = (event) => {
        const alpha = (event.alpha || 0);
        const beta = (event.beta || 0) ;
        const gamma = (event.gamma || 0) ;

        alphaText.text = `alpha: ${alpha.toFixed(2)}°`;
        betaText.text = `beta: ${beta.toFixed(2)}°`;
        gammaText.text = `gamma: ${gamma.toFixed(2)}°`;

    };

    window.addEventListener("resize", () => {
        engine.resize();
        shaderMaterial.setVector2("resolution", new BABYLON.Vector2(window.innerWidth, window.innerHeight));
    });

    return scene;
};
```