January 2016

Volume 31 Number 1

ゲーム開発 - Babylon.js: 初めての Web ゲームを強化する高度な機能

Julie Lerman

12 月号のコラムでは、WebGL ベースの 3D ゲーム エンジン、Babylon.js の基本的なビルディング ブロックを調べるところからチュートリアルを始めました (msdn.com/magazine/mt595753)。そこでは、Babylon.js が提供するツールを使用して、ごくシンプルなボウリング ゲームの設計に着手しました。今手元には、ゲームに必要なオブジェクトとしてボウリングのボール、レーン、ガーター、10 本のピンが揃っています。

今回は、ボールを投げる方法、ピンへの衝突、オーディオ効果の追加、さまざまなカメラ ビューの追加など、このゲームに命を吹き込みます。

当然、今回は、初回に使用したコードを利用し、これを拡張します。前回と区別するために、今回使用するコードは新しい JavaScript ファイルを作成して、そこに含めます。特定のオブジェクト (シーンやメイン カメラなど) の機能を拡張する場合でも、前回オブジェクトを作成した関数に機能を拡張するコードを実装することはしません。唯一拡張するのは init 関数です。この関数は、前回と今回の両方に必要な変数をすべて保持しているためです。

説明を簡単にするために、こうした作業は済ませてあります。もちろん、独自のゲームを実装するときは、好みのプログラミング パラダイムや構文を使用してください。お勧めは、TypeScript と、そのオブジェクト指向スタイルのプログラミングです。これは、プロジェクトの整理に威力を発揮します。

本稿執筆中に、Babylon.js の新しいバージョンがリリースされましたが、引き続きバージョン 2.1 を使用します。Babylon.js 2.2 の更新内容をチェックする方は、bit.ly/1RC9k6e (英語) をご覧ください。

ネイティブの衝突検出

ゲームで使用するメイン カメラは Free Camera で、プレイヤーはマウスとキーボードを使用して 3D シーン全体を移動できるようになっています。ただし、特定の変更を加えなければ、カメラがシーン中で浮かび上がったり、壁をすり抜けたり、床やレーンを通り抜けたりすることになります。リアリティのあるゲームにするには、プレイヤーが地面とレーンの上しか移動できないようにします。

これを可能にするため、Babylon 内部の衝突検出システムを使います。衝突システムは、2 つのオブジェクトが互いに重なり合うのを防ぎます。また、プレイヤーが上を向きながら前に歩いたときに空中に浮かんでしまうのを防ぐ、重力機能も備わっています。

まず、衝突検出と重力を有効にしましょう。

function enableCameraCollision(camera, scene) {
  // Enable gravity on the scene. Should be similar to earth's gravity. 
  scene.gravity = new BABYLON.Vector3(0, -0.98, 0);
  // Enable collisions globally. 
  scene.collisionsEnabled = true;
  // Enable collision detection and gravity on the free camera. 
  camera.checkCollisions = true;
  camera.applyGravity = true;
  // Set the player size, the camera's ellipsoid. 
  camera.ellipsoid = new BABYLON.Vector3(0.4, 0.8, 0.4);
}

上記の関数は、ゲームのシーンとカメラの衝突システムを有効にし、カメラの楕円を設定しています。この楕円は、プレイヤーの大きさと考えることができます。今回の場合、これはサイズが 0.8x1.6x0.8 単位の直方体で、おおよその平均的な人間のサイズを表しています。カメラはメッシュではないので、この直方体が必要になります。Babylon.js の衝突システムは、メッシュどうしの衝突のみを調べます。そのため、カメラ用にメッシュのシミュレーションを行う必要があります。楕円はオブジェクトの中心を定義するので、上記で指定している 0.4 がサイズ 0.8 に変換されます。シーンの重力も有効にしています。この重力は、カメラの移動に適用します。

カメラの衝突を有効にしたら、地面とレーンの衝突の検査を有効にする必要があります。これを行うため、互いに衝突させるメッシュそれぞれに簡単なブール型フラグを設定します。

function enableMeshesCollision(meshes) {
  meshes.forEach(function(mesh) {
    mesh.checkCollisions = true;
  });
}

最後の手順として、init 関数に以下の 2 つの関数呼び出しを追加します。

// init function from the first part of the tutorial.
  ...
  enableCameraCollision(camera, scene);
  enableMeshesCollision[[floor, lane, gutters[0], gutters[1]]);
}

衝突検出の唯一の仕事は、メッシュが互いに重なり合うのを防ぐことです。重力機能は、カメラが地面から離れないようにするために実装しました。特定のメッシュどうし (今回はボールとピン) の実際の物理的接触を作り出すには、さらに複雑な物理運動エンジンが必要です。

ボールの投球: Babylon.js での物理運動統合

このゲームの主なアクションは、ピンに向けてボールを投げることです。その要件は次のように実にシンプルです。プレイヤーが、ボールを投げる方向と強さを決めます。特定のピンにボールが当たれば、そのピンが倒れます。そのピンが別のピンにぶつかれば、ぶつかったピンも同じように倒れます。プレイヤーの投げたボールがレーンの端にいくと、ガーターに落ちます。ピンは、ボールがぶつかった速度に応じて倒れるかどうかが決まります。

これはまさに、物理運動エンジンの領域です。物理運動エンジンは、メッシュ本体の運動力学をリアルタイムに計算し、加えられた力に応じて次の動きを計算します。簡単に言えば、物理運動エンジンは、あるメッシュが別のメッシュとぶつかったとき、またはメッシュがユーザーにより動かされたときに何が起きるかを決定します。その際、メッシュの現在の速度、重さ、形状などが考慮されます。

剛体力学 (空間でのメッシュの次の動き) をリアルタイムに計算するため、物理運動エンジンはメッシュを単純化しなければなりません。そのため、それぞれのメッシュに擬似表現 (インポスター) を用意します。このインポスターは、境界を形成する単純なメッシュ (通常は球または直方体) になります。これにより計算精度は落ちますが、オブジェクトを動かす物理運動力の計算は速くなります。物理運動エンジンの機能の詳細については、bit.ly/1S9AIsU (英語) を参照してください。

Babylon.js には物理運動エンジンが含まれていません。このフレームワークの開発者は、1 つのエンジンにすべてを託すのではなく、さまざまな物理運動エンジンとのインターフェイスを実装して、開発者が使用するエンジンを決められるようにしています。現状、Babylon.js には、2 つの物理運動エンジンとのインターフェイスが用意されています。1 つは Cannon.js (cannonjs.org、英語)、もう 1 つは Oimo.js (github.com/lo-th/Oimo.js、英語) です。どちらも優れた物理運動エンジンです。今回のボウリング ゲームには Oimo を統合する方がわずかに適していると考え、こちらを使用しています。Cannon.js とのインターフェイスは、Babylon.js 2.3 向けて完全に作り直され、現在はアルファ段階です。多くのバグが解決され、複雑な高さマップを含む新しいインポスターが用意されて、最新バージョンの Cannon.js をサポートするようになっています。Babylon.js 2.3 以降を使用する場合は、こちらを試してみることをお勧めします。

以下の簡単なコードを使用して、物理運動エンジンを有効にします。

scene.enablePhysics(new BABYLON.Vector3(0, -9.8, 0), new BABYLON.OimoJSPlugin());

これにより、シーンの重力を設定し、使用する物理運動エンジンを定義します。Oimo.js を Cannon.js に置き換えるには、2 番目の引数を以下のように変更するだけです。

new BABYLON.CannonJSPlugin()

次に、すべてのオブジェクトのインポスターを定義します。このためには、メッシュの setPhysicsState 関数を使用します。たとえば、レーンの場合は以下のように定義します。

lane.setPhysicsState(BABYLON.PhysicsEngine.BoxImpostor, {
  mass: 0,
  friction: 0.5,
  restitution: 0
});

最初の変数はインポスターの型です。レーンは完全な直方体なので、Box インポスターを使用します。2 番目の変数は、物体の物理運動の定義で、物体の重量 (kg)、摩擦、反発係数を定義します。レーンの質量は 0 とし、その場所から動かないようにします。インポスターの質量を 0 にすると、そのオブジェクトは現在位置から動かなくなります。

球の場合は、Sphere インポスターを使います。ボウリング ボールの重量は約 6.8 kg で、通常は非常になめらかなので、摩擦は生じません。

ball.setPhysicsState(BABYLON.PhysicsEngine.SphereImpostor, {
  mass: 6.8,
  friction: 0,
  restitution: 0
});

ポンドではなくキログラムを使用しているのは、プロジェクトを通してフィートではなくメートルを使用しているのと同じ理由で、物理運動エンジンがメートル法を採用しているためです。たとえば、既定の重力定義は (0, -9.8, 0) で、地球の重力の近似値です。使用している単位はメートル毎秒毎秒 (m/s2) です。

ここで、ボールを投げられるようにする必要があります。そのためには物理運動エンジンの別の機能を使用します。それは、特定のオブジェクトへの力積の適用です。力積とは、物理運動を有効にしているメッシュに適用される、特定方向への力です。たとえば、ボールを前に投げるには以下のコードを使用します。

ball.applyImpulse(new BABYLON.Vector3(0, 0, 20), ball.getAbsolutePosition());

最初の変数は力積のベクトルで、Z 軸方向へ 20 単位の力を加えています。Z 軸は、シーンがリセットされた時点の正面方向を表します。2 番目の変数は、力が作用するオブジェクトの場所を指定します。上記の場合は、ボールの中心を指定しています。ビリヤード ゲームでのトリック ショットを考えてみてください。キューはボールの中心だけではなく、さまざまな場所を突くことができます。このやり方で、ボールの動きのシミュレーションを行うことができます。

これで、ボールを前方に投げられるようになりました。図 1 は、ボールがピンにぶつかったときの様子を示しています。

ピンにぶつかるボウリングのボール
図 1 ピンにぶつかるボウリングのボール

ですが、まだ、力を加える方向と強さを指定していません。

方向を設定する方法はたくさんあります。選択肢として最も適切なのは 2 つあり、1 つは現在のカメラが向いている方向を使う方法、もう 1 つはポインターのタップ位置を使う方法です。今回は、2 つ目の選択肢を使います。

pointer-down イベントと pointer-up イベントから送られる PickingInfo オブジェクトを使用して、ユーザーが空間をタッチした位置を特定します。PickingInfo オブジェクトには、タッチされたメッシュ、タッチされたメッシュ上の位置、その位置までの距離など、イベントが発生した位置に関する情報が含まれています。どのメッシュもタッチされていなければ、PickingInfo オブジェクトの変数 hit が false になります。Babylon.js のシーンには、タッチした位置情報を取得できる、onPointerUp と onPointerDown という 2 つの便利なコールバック関数があります。これらの 2 つのコールバックは、ポインター イベントが発生すると呼び出され、それぞれ以下のシグネチャをもちます。

function(evt: PointerEvent, pickInfo: PickingInfo) => void

変数 evt は、トリガーされた元々の JavaScript イベントです。2 番目の変数は、トリガーされた各イベントの位置情報で、フレームワークによって生成される情報です。

これらのコールバックを以下のように使用して、ボールを特定の方向に投げることができます。

scene.onPointerUp = function(evt, pickInfo) {
  if (pickInfo.hit) {
    // Calculate the direction using the picked point and the ball's position. 
    var direction = pickInfo.pickedPoint.subtract(ball.position);
    // To be able to apply scaling correctly, normalization is required.
    direction = direction.normalize();
    // Give it a bit more power (scale the normalized direction).
    var impulse = direction.scale(20);
    // Apply the impulse (and throw the ball). 
    ball.applyImpulse(impulse, new BABYLON.Vector3(0, 0, 0));
  }
}

これで、ボールを特定の方向に投げられるようになりました。残っているのは投げる強さだけです。これを追加するには、pointer-down イベントが発生してから pointer-up イベントが発生するまでのフレーム数の差分を計算します。図 2 は、指定した力の強さでボールを投げる関数を示しています。

図 2 力と方向を定めた投球

var strengthCounter = 5;
var counterUp = function() {
  strengthCounter += 0.5;
}
// This function will be called on pointer-down events.
scene.onPointerDown = function(evt, pickInfo) {
  // Start increasing the strength counter. 
  scene.registerBeforeRender(counterUp);
}
// This function will be called on pointer-up events.
scene.onPointerUp = function(evt, pickInfo) {
  // Stop increasing the strength counter. 
  scene.unregisterBeforeRender(counterUp);
  // Calculate throw direction. 
  var direction = pickInfo.pickedPoint.subtract(ball.position).normalize();
  // Impulse is multiplied with the strength counter with max value of 25.
  var impulse = direction.scale(Math.min(strengthCounter, 25));
  // Apply the impulse.
  ball.applyImpulse(impulse, ball.getAbsolutePosition());
  // Register a function that will run before each render call 
  scene.registerBeforeRender(function ballCheck() {
    if (ball.intersectsMesh(floor, false)) {
      // The ball intersects with the floor, stop checking its position.  
      scene.unregisterBeforeRender(ballCheck);
      // Let the ball roll around for 1.5 seconds before resetting it. 
      setTimeout(function() {
        var newPosition = scene.activeCameras[0].position.clone();
        newPosition.y /= 2;
        resetBall(ball, newPosition);
      }, 1500);
    }
  });
  strengthCounter = 5;
}

ポインターのイベントに関して、ここでは「クリック」という表現を使用していません。Babylon.js は Pointer Events システムを使用しています。このシステムは、ブラウザーのマウス クリック機能を、タッチや、入力デバイスに応じたクリック、タッチ、ポイントの各機能に拡張します。これにより、スマートフォンのタッチ操作でも、デスクトップでのマウス クリックでも、同じイベントがトリガーされます。機能をサポートしていないブラウザーでこの拡張のシミュレーションを行うために、Babylon.js はポインター イベントのポリフィル、hand.js を使用します。このポリフィルは、今回のゲームの index.html にも含めてあります。詳細については、hand.js の GitHub ページ (bit.ly/iCaM2b、英語) を参照してください。Pointer Events のドラフトは、bit.ly/1PAdo9J (英語) で公開されています。今後のリリースでは、hand.js が jQuery PEP (bit.ly/1NDMyYa、英語) に置き換えられる予定です。

物理運動についてはこれだけです。ボウリング ゲームはかなり良くなりました。

オーディオ効果の追加

ゲームにオーディオ効果を追加すると、UX が大幅に向上します。オーディオによって、ゲームの雰囲気がそれらしくなり、ますますリアルになります。さいわい、Babylon.js のバージョン 2.0 からオーディオ エンジンが導入されています。オーディオ エンジンは、Internet Explorer を除くすべての主要ブラウザーでサポートされる、Web Audio API (bit.ly/1YgBWWQ、英語) が基盤です。使用可能なファイル形式は、ブラウザーによって異なります。

今回は、異なる 3 つのオーディオ効果を追加します。最初の効果は、周囲のシミュレーションを行う環境音です。ボウリング ゲームの場合、通常であればボウリング場の音がぴったりなのですが、今回はボウリング レーンを野外の草の上に作成したので、自然音の方が適しています。

環境音を追加するには、その音を読み込み、自動再生して、連続的にループ再生します。

var atmosphere = new BABYLON.Sound("Ambient", "ambient.mp3", scene, null, {
  loop: true,
  autoplay: true
});

この音は、読み込んだ時点から絶えることなく再生されます。

2 つ目のオーディオ効果は、ボールがレーンの上を転がる音です。この音はボールがレーン上にある限り再生され、ボールがレーンを離れた瞬間に停止します。

まず、ボールが転がる音を作成します。

var rollingSound = new BABYLON.Sound("rolling", "rolling.mp3", scene, null, {
  loop: true,
  autoplay: false
});

この音は読み込まれても、ボールの投球時に起動される play 関数が実行されるまで再生されません。図 2 の関数を拡張して、以下のコードを追加します。

...ball.applyImpulse(impulse, new BABYLON.Vector3(0, 0, 0));
// Start the sound.
rollingSound.play();
...

ボールがレーンから離れたら、音の再生を停止します。

...
If(ball.intersectsMesh(floor, false)) {
  // Stop the sound.
  rollingSound.stop();
  ...
}
...

Babylon.js では、特定のメッシュに音をアタッチできるようになっています。メッシュに音をアタッチすると、Babylon.js がそのメッシュの位置から音量とパニングを自動計算し、現実感のあるエクスペリエンスを生み出します。そのためには、音を作成するコードの後に以下の行を追加するだけです。

rollingSound.attachToMesh(ball);

これで、常にボールがある位置から音が聞こえるようになります。

最後に追加するサウンド効果は、ボールがピンにぶつかる音です。この音は、作成したら先頭のピンにアタッチします。

var hitSound = new BABYLON.Sound("hit", "hit.mp3", scene);
hitSound.attachToMesh(pins[0]);

この音はループ再生も、自動再生も行いません。

この音は、ボールがピンの 1 本にぶつかるたびに再生します。そのためは、ボールの投球後ボールがピンの 1 本に接触したかどうかを絶えず検出する関数を追加します。ボールがピンに接触したら、関数の登録を解除して、音を再生します。そのためには、図 2 の状態から、以下のコードを関数 scene.onPointerUp に追加します。

scene.registerBeforeRender(function ballIntersectsPins() {
  // Some will return true if the ball hit any of the pins.
  var intersects = pins.some(function (pin) {
    return ball.intersectsMesh(pin, false);
  });
  if (intersects) {
    // Unregister this function – stop inspecting the intersections.
    scene.unregisterBeforeRender(ballIntersectsPins);
    // Play the hit sound.
    hit.play();
  }
});

これで、追加予定のオーディオ効果をすべて追加できました。次は、スコアボードを追加して、ゲームをさらに強化します。

マテリアルの著作権により、ここで使用したオーディオ効果を本稿付随のプロジェクトに追加できませんでした。今回は、無料で入手でき、公開も許可しているオーディオ サンプルを見つけることができませんでした。そのため、このコードはコメント アウトしています。今回使用した 3 つのオーディオ サンプルを追加すれば、コードは動作します。

スコア表示の追加

プレイヤーがピンを倒した後、残りのピン数と倒したピン数を実際に確認できるようにしましょう。そこで、スコアボードを追加することにします。

スコアボード自体は、シンプルな黒の背景に白いテキストを描いたものです。これを作成するには、動的テクスチャ機能を使います。これは、基本的には、ゲーム内の 3D オブジェクトのテクスチャとして使用可能な 2D キャンバスです。

背景と動的テクスチャは、以下のように簡単に作成できます。

var scoreTexture = new BABYLON.DynamicTexture("scoreTexture", 512, scene, true);
var scoreboard = BABYLON.Mesh.CreatePlane("scoreboard", 5, scene);
// Position the scoreboard after the lane.
scoreboard.position.z = 40;
// Create a material for the scoreboard.
scoreboard.material = new BABYLON.StandardMaterial("scoradboardMat", scene);
// Set the diffuse texture to be the dynamic texture.
scoreboard.material.diffuseTexture = scoreTexture;

動的テクスチャにより、CanvasRenderingContext2D (mzl.la/1M2mz01、英語) を返す関数 getContext を使用して、基となるキャンバス上に直接描画できるようになります。動的テクスチャ オブジェクトは、キャンバスのコンテキストを直接処理したくない場合に利用できる便利な関数もいくつか提供しています。その関数の 1 つが drawText で、この関数を使用すると、キャンバスの最前面に特定のフォントで文字列を描画できます。倒れたピンの数が変わったら、毎回キャンバスを更新します。

var score = 0;
scene.registerBeforeRender(function() {
  var newScore = 10 - checkPins(pins, lane);
  if (newScore != score) {
    score = newScore;
    // Clear the canvas. 
    scoreTexture.clear();
    // Draw the text using a white font on black background.
    scoreTexture.drawText(score + " pins down", 40, 100,
      "bold 72px Arial", "white", "black");
  }
});

ピンが倒れたかどうかのチェックは簡単で、ピンの Y 軸の位置がすべてのピンの本来の Y 座標 (あらかじめ定義した変数 'pinYPosition') と等しいかどうかを確認するだけです。

function checkPins(pins) {
  var pinsStanding = 0;
  pins.forEach(function(pin, idx) {
    // Is the pin still standing on top of the lane?
    if (BABYLON.Tools.WithinEpsilon(pinYPosition, pin.position.y, 0.01)) {
      pinsStanding++;
    }
  });
  return pinsStanding;
}

動的テクスチャを図 3 に示します。

ゲームのスコアボード
図 3 ゲームのスコアボード

これで、足りないのはレーンとボードをリセットする機能だけです。そこで、キーボードの R キーを押したときに機能するアクション トリガーを追加します (図 4 参照)。

図 4 レーンとボードのリセット

function clear() {
  // Reset the score.
  score = 0;
  // Initialize the pins.
  initPins(scene, pins);
  // Clear the dynamic texture and draw a welcome string.
  scoreTexture.clear();
  scoreTexture.drawText("welcome!", 120, 100, "bold 72px Arial", "white", "black");
}
scene.actionManager.registerAction(new BABYLON.ExecuteCodeAction({
  trigger: BABYLON.ActionManager.OnKeyUpTrigger,
  parameter: "r"
}, clear));

R キーを押すと、シーンがリセット (初期化) されます。

Follow Camera の追加

ボールの投球後にボールを追いかけるカメラがあると面白いと考え、ゲームに追加することにしました。そこで、ピンに向かうボールと一緒に "進み"、ボールが元の位置に戻ったときに停止するカメラを用意します。このカメラは、Babylon.js のマルチビュー機能を使用して実現できます。

このチュートリアルの第 1 部では、以下のコードを使用して、シーンのアクティブ カメラとして Free Camera オブジェクトを設定しました。

scene.activeCamera = camera

アクティブ カメラの変数は、カメラがレンダリングするシーンを指示していますが、場合によっては複数のカメラを定義することもできます。今回のゲームで使用するカメラは 1 つなので、上記のコードで十分です。ただし、"ピクチャインピクチャ" 効果が必要な場合、カメラが 1 つでは不十分です。そこで、変数 scene.activeCameras の下に格納される、シーンのアクティブ カメラ配列を使用する必要があります。この配列内のカメラが、順番にレンダリングされることになります。scene.activeCameras が空の場合、scene.activeCameras は無視されます。

まず、配列に最初の Free Camera を追加します。これは、関数 init 内で簡単に行えます。scene.activeCamera = camera を以下に置き換えます。

scene.activeCameras.push(camera);

次に、ボール投球時に Follow Camera を作成します。

var followCamera = new BABYLON.FollowCamera("followCamera", ball.position, scene);
followCamera.radius = 1.5; // How far from the object should the camera be.
followCamera.heightOffset = 0.8; // How high above the object should it be.
followCamera.rotationOffset = 180; // The camera's angle. here - from behind.
followCamera.cameraAcceleration = 0.5 // Acceleration of the camera.
followCamera.maxCameraSpeed = 20; // The camera's max speed.

このコードはカメラを作成し、追従するオブジェクトの 1.5 単位後方かつ 0.8 単位上方に位置するようにそのカメラを設置します。追従するオブジェクトはボールですが、このボールは回転するため、カメラがそれに合わせて回転してしまうという問題があります。実現したいのは、オブジェクトを後から追跡する "経路" です。そこで、ボールの回転ではなく位置を取得する追従オブジェクトを作成します。

// Create a very small simple mesh.
var followObject = BABYLON.Mesh.CreateBox("followObject", 0.001, scene);
// Set its position to be the same as the ball's position.
followObject.position = ball.position;

その後、カメラのターゲットをこの followObject に設定します。

followCamera.target = followObject;

これで、カメラはボールと一緒に移動する追従オブジェクトを追うようになります。

カメラに必要な最後の構成は、ビューポートです。各カメラは、使用する画面領域を定義することができます。この定義には viewport 変数を使用します。この変数は以下のように定義します。

var viewport = new BABYLON.Viewport(xPosition, yPosition, width, height);

すべての値は 0 ~ 1 の間で、画面の高さと幅に対する比率 (1 = 100 %) になっています。最初の 2 つの値は、画面上のカメラ枠の始点で、width と height は、画面の実サイズとの比率で枠の幅と高さを定義します。ビューポートの既定の設定は、画面全体を覆う (0.0, 0.0, 1.0, 1.0) になっています。Follow Camera の場合、高さと幅を画面の 30% に設定します。

followCamera.viewport = new BABYLON.Viewport(0.0, 0.0, 0.3, 0.3);

図 5 は、ボールを投球後のゲームのようすを示しています。左下隅のビューに注目してください。

Follow Camera によるピクチャインピクチャ効果
図 5 Follow Camera によるピクチャインピクチャ効果

ポインター イベントやキーボード イベントなどの入力制御が行われる画面は、このチュートリアルの第 1 部で定義した Free Camera の画面です。これは、関数 init 内で既に定義しています。

ゲームをさらに進化させる方法

最初に触れたように、このゲームはプロトタイプです。これをリアルなゲームにするために実装できるものが、まだいくつかあります。

たとえば、GUI を追加して、ユーザー名の入力を求めることができます。構成オプションを用意することも可能です (重力を変えられるようにすると、火星でボウリング ゲームをプレイできるかもしれません)。他にもいろいろ考えられます。

Babylon.js にはネイティブに GUI を作成する方法はありませんが、すばらしい GUI を作成するために使用できる拡張機能をコミュニティのメンバーがいくつか開発しています。たとえば、CastorGUI (bit.ly/1M2xEhD、英語)、bGUi (bit.ly/1LCR6jk、英語)、ダイアログ拡張機能 (bit.ly/1MUKpXH、英語) などがあります。CastorGUI は HTML と CSS を使用して、3D キャンバスの上にレイヤーを追加します。それ以外の 2 つは、四角形のメッシュと動的テクスチャを使用して、シーン自体に 3D ダイアログを追加します。独自のソリューションを作成する前に、これらを試してみることをお勧めします。どれも使い方はとても簡単で、GUI 作成プロセスを大幅に簡略化できます。

他にメッシュを強化することも考えられます。今回のゲームのメッシュはすべて Babylon.js の内部関数を使用して作成しています。このようにコードだけで実現可能なものには限度があるのは明らかです。数多くのサイトで、無料/有料の 3D オブジェクトが提供されています。個人的に最もすばらしいと感じるのは、TurboSquid (bit.ly/1jSrTZy、英語) です。パフォーマンスを向上するには、低ポリゴンのメッシュを探します。

質の高いメッシュを追加したら、実際に投球する人間のオブジェクトを追加してはどうでしょう。そのためには、Babylon.js に統合されている骨格アニメーション機能とその機能に対応するメッシュが必要です。骨格アニメーションは、babylonjs.com/BONES のデモで確認できます。

最後の仕上げとして、ゲームを仮想現実対応にしてみましょう。必要なのは使用するカメラの変更だけです。FreeCamera を WebVRFreeCamera に置き換えるだけで、実に簡単に Google Cardboard をターゲットにすることができます。

また、ピンを見下ろすカメラの追加、マルチプレイヤー機能向けのレーン増設、ボール投球場所に応じたカメラの動きと位置の制限など、手を加えられる強化点は他にも多くあります。他にもさまざまな機能があるので、ご自分で探してみてください。

まとめ

このチュートリアルを執筆したいと考えたのと同じくらいに、読者の皆さんがこの内容を楽しみ、正しい方向に向かって Babylon.js を試されることを願っています。このフレームワークの他のデモについては、babylonjs.com (英語) を参照してください。繰り返しになりますが、サポート フォーラムに参加して、抱えている疑問をためらわずに質問してみてください。ばかげた回答はあっても、ばかげた質問はありません。


Raanan Weber は、IT コンサルタントであり、フルスタック開発者であり、夫であり、父親でもあります。また、余暇には、Babylon.js などのオープン ソース プロジェクトに貢献しています。ブログは blog.raananweber.com (英語) で公開されています。

この記事のレビューに協力してくれた技術スタッフの David Catuhe に心より感謝いたします。