Chapter 25

3-07: 村を歩き回る (A Walk Around The Village)

ちょまど (千代田まどか)
ちょまど (千代田まどか)
2022.05.01に更新

このページの原文: https://doc.babylonjs.com/start/chap3/walkpath

🙍 3-07: 村を歩き回る (A Walk Around The Village)

「前に進む」について: movePOV

メッシュの便利なプロパティ movePOV (move point of view (視点)) があります。これにより、メッシュをその視点に関連して移動できます。(相対移動)
通常、新しく作成されたメッシュは負の z 方向を向いていると見なされ、これがそのモデルの視点の方向になります。

movePOV
// movePOV の引数 3 つ
movePOV(
    amountRight,  // 右→ (-x) への移動量
    amountUp,     // 上↑ (+y) への移動量
    amountForward // 前↓ (-z) への移動量
)

https://doc.babylonjs.com/typedoc/classes/babylon.abstractmesh#movepov

例えば、メッシュを使用する視点の方向に 6 単位 前方に移動するには

mesh.movePOV(0, 0, 6)

パラメータは、順番に、右、上、前に移動する距離です。
通常、これらは、メッシュのローカル空間での負の x 軸、正の y 軸、および負の z 軸になります。

Babylon.js では、次のフレームをレンダリングする前に実行されるコードを次のように記述できます:

次のフレームをレンダリングする前に実行されるコード
scene.onBeforeRenderObservable.add(() => {
    // 実行するコードが入る
});

このようにして、オブジェクトのプロパティをレンダリングフレームごとに変更できます。

🔼 例)三角形の辺に沿って移動する球

球が三角形のエッジの周りを移動し続けるという単純なケースを考えてみましょう。

まず、二等辺直角三角形の周りをスライドする球を取り上げます。

球と三角形
// 直径 0.25 の球
const sphere = BABYLON.MeshBuilder.CreateSphere("sphere", {diameter: 0.25});

// 三角形
const points = []; // 頂点の入る配列
points.push(new BABYLON.Vector3(2, 0, 2));
points.push(new BABYLON.Vector3(2, 0, -2));
points.push(new BABYLON.Vector3(-2, 0, -2));
points.push(points[0]); // 三角形を終わらせる;
BABYLON.MeshBuilder.CreateLines("triangle", {points: points})

また、他のメソッド rotate (回転) も見てみましょう。
このメソッドは、メッシュを指定された軸を中心に、指定された角度(ラジアン)で回転させます。

rotate
mesh.rotate(axis, angle, BABYLON.Space.LOCAL);

毎フレームが描画される前にアニメーションを生成するため、球は 0.05 の距離を移動します。
移動距離が 4 より大きい場合、ターン (回転/方向転換) させ、8 より大きい場合、またターンして、リセットしてまた最初からスタートします。

プロパティ turndistance (距離) を使用してオブジェクトの track (追跡) 配列を作ります。
指定された合計距離を移動した後、球は指定された回転値だけ回転します。

const slide = function (turn, dist) { 
    this.turn = turn; // 回転値
    this.dist = dist; // 距離
}
// ↓追跡用の配列の入れ物を作る
const track = [];
// ↓最初の辺の長さが4
track.push(new slide(Math.PI / 2, 4));  
// ↓二番目の辺を走り終えるときの距離は 4 + 4
track.push(new slide(3 * Math.PI / 4, 8)); 
// ↓すべての辺を走り終えるときの距離は 4 + 4 + 4 * sqrt(2)
track.push(new slide(3 * Math.PI / 4, 8 + 4 * Math.sqrt(2)));

必要な距離に達すると、ターン (方向転換,回転) が行われ、配列インデックスポインタ p が 1 ずつ増加します。

モジュロ演算子 % は、配列の最後でポインターをゼロにリセットするために使用されます。

if (distance > track[p].dist) {        
    sphere.rotate(BABYLON.Axis.Y, track[p].turn, BABYLON.Space.LOCAL);
    p +=1;
    p %= track.length;
}

浮動小数点エラーの蓄積を防ぐために、インデックスポインタが 0 にリセットされると、球の位置と回転もリセットされます。

https://playground.babylonjs.com/#N9IZ8M#1

👨 村を歩かせる

少しトリッキーですが、ターンと距離に少し試行錯誤 (trial and error) を加えることで、村のキャラクターのより複雑な歩行を実現できます。
また、度 (degree) を使用して、回転時にラジアン (radian) に変更する理由は、1 度や 2 度など微調整するのが簡単であるためです。

.babylon ファイルからインポートされたキャラクター dude は rotation ではなく rotationQuaternion を使用して回転が設定されているので、rotate メソッドを使用してキャラクターの向きをリセットします。
(dude が(ローカル座標で)変な方向に回転した状態で作られたものだからそれを補正しますみたいな内容。下記のコードだと dude.rotate(BABYLON.Axis.Y, BABYLON.Tools.ToRadians(-95), BABYLON.Space.LOCAL);.ToRadians(-95) で (↑Y軸に-95度) 調整が入っている)

dude.position = new BABYLON.Vector3(-6, 0, 0);
dude.rotate(BABYLON.Axis.Y, BABYLON.Tools.ToRadians(-95), BABYLON.Space.LOCAL);
const startRotation = dude.rotationQuaternion.clone(); //use clone so that variables are independent not linked copies
if (p === 0) {
    distance = 0;
    dude.position = new BABYLON.Vector3(-6, 0, 0);
    dude.rotationQuaternion = startRotation.clone();
}

https://playground.babylonjs.com/#KBS9I5#81

createScene 全体のコードはこのようになります

const createScene = function () {
    
    const scene = new BABYLON.Scene(engine);

    const camera = new BABYLON.ArcRotateCamera("camera", -Math.PI / 1.5, Math.PI / 2.2, 15, new BABYLON.Vector3(0, 0, 0));
    camera.attachControl(canvas, true);
    const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(1, 1, 0));

    BABYLON.SceneLoader.ImportMeshAsync("", "https://assets.babylonjs.com/meshes/", "village.glb");
    
    const walk = function (turn, dist) {
        this.turn = turn;
        this.dist = dist;
    }
    
    const track = [];
    track.push(new walk(86, 7));
    track.push(new walk(-85, 14.8));
    track.push(new walk(-93, 16.5));
    track.push(new walk(48, 25.5));
    track.push(new walk(-112, 30.5));
    track.push(new walk(-72, 33.2));
    track.push(new walk(42, 37.5));
    track.push(new walk(-98, 45.2));
    track.push(new walk(0, 47));

    // Dude
    BABYLON.SceneLoader.ImportMeshAsync("him", "/scenes/Dude/", "Dude.babylon", scene).then((result) => {
        var dude = result.meshes[0];
        dude.scaling = new BABYLON.Vector3(0.008, 0.008, 0.008);
            
        dude.position = new BABYLON.Vector3(-6, 0, 0);
        dude.rotate(BABYLON.Axis.Y, BABYLON.Tools.ToRadians(-95), BABYLON.Space.LOCAL);
        const startRotation = dude.rotationQuaternion.clone();    
            
        scene.beginAnimation(result.skeletons[0], 0, 100, true, 1.0);

        let distance = 0;
        let step = 0.015;
        let p = 0;

        scene.onBeforeRenderObservable.add(() => {
		    dude.movePOV(0, 0, step);
            distance += step;
              
            if (distance > track[p].dist) {
                    
                dude.rotate(BABYLON.Axis.Y, BABYLON.Tools.ToRadians(track[p].turn), BABYLON.Space.LOCAL);
                p +=1;
                p %= track.length; 
                if (p === 0) {
                    distance = 0;
                    dude.position = new BABYLON.Vector3(-6, 0, 0);
                    dude.rotationQuaternion = startRotation.clone();
                }
            }
			
        })
    });
    
    return scene;
};

☞ 次は

3 章が終わりました、お疲れ様です!
これで村を歩き回る車とキャラクターができました。
でも、車とキャラクターが衝突してしまうかもしれません。どのように防げるでしょうか?
次の 4 章でそれを見てみましょう。