虎の穴開発室ブログ

虎の穴ラボ株式会社所属のエンジニアが書く技術ブログです

MENU

babylon.js でスイカ割れないゲームを作った🍉

こんにちは。虎の穴ラボの大場です。

この記事は「虎の穴ラボ夏のアドベントカレンダー」11日目の記事です。
10日目は磯江さんによる「アジャイルで生活習慣をカイゼンする」が投稿されました。
12日目ははっとりさんによる「いろいろなObserver APIの紹介」が投稿されます。こちらもぜひご覧ください。

はじめに

babylon.js は、Microsoftが開発したJavaScriptで3D表現ができるライブラリです。 www.babylonjs.com

今回は、ブラウザで3D表現ができる babylon.js を使い遊んでいたところ、スイカ割れないゲームが爆誕してしまったので、babylon.js の紹介と実装を解説致します。

触ろうと思ったきっかけ

connpassで日本コミュニティが開催しているイベントを偶然お見かけし、そこで babylon.js の存在を知り興味を持ちました。babylon.js 公式が公開しているサンプルを触ってみて、JavaScriptのライブラリを利用しブラウザ上でここまでの動きを表現できるのか、と驚きもありました。 babylonjs.connpass.com

とりあえず触ってみたい

言葉だけではよくわからないと思うので、実際どんなものか試してみたい人には、playGroud (Babylon.js専用のオンラインエディタ) がオススメです。ローカル環境の準備不要で、どんな事が出来るか実際にコードを触って試すことができます。

メニューから様々なサンプルを動かしたり、読み込んだサンプルのコードを直接変更して変化を追ってみたり、色々触ってみるのも面白いです。

スイカ割れないゲーム

HTMLとJavaScriptだけで構成されています。 HTML側はcanvasでbabylon.jsがレンダリングする場所を指定し、必要なライブラリとゲーム本体処理のjsを読み込んでいるだけです。JavaScript側でライブラリを利用してレンダリングする内容を記述していきます。

<canvas width="375" height="667" id="renderCanvas"></canvas>

1.必要なライブラリを読み込む

以下のリンクからbabylon.js 公式が提供しているライブラリを確認できます。 Framework Versions | Babylon.js Documentation

安定バージョンを提供しているCDNからminifyされたライブラリを直接インポートしています。

gui.jsは画面内のスコアやタイマー等のテキストを表示するために必要なライブラリです。3D表現を行いたいだけの場合、必須ではありません。

<script src="https://cdn.babylonjs.com/babylon.js"></script>
<script src="https://cdn.babylonjs.com/gui/babylon.gui.js"></script>

pep.jsに関しては、タッチスクリーンに対応させるために読み込んでいるようでした。試しにpep.jsを外して動かしてみたところ、クリック(タップ)時に画面が回転してしまい、意図しない挙動となりました。

<script src="https://code.jquery.com/pep/0.4.3/pep.js"></script>

2.シーンを作る

まずは、コンテンツを表示するための土台を作ります。その後、作成したシーンに対して、今回であればスイカやリセットボタンといったメッシュ(オブジェクト)を設置していくといった流れです。

  const canvas = document.getElementById("renderCanvas");
  const engine = new BABYLON.Engine(canvas);
  function createScene() {
    const scene = new BABYLON.Scene(engine);
    // この間でオブジェクトを色々追加する
    return scene;
  }

 const scene = createScene();

3.スコアとカウントダウンの表示

ライブラリ(babylon.gui.js)を読み込んだことで3Dの空間に2Dテキストを表示するといったGUI表現が可能となっています。 Babylon.GUIを使うために、AdvancedDynamicTextureオブジェクトが必要です。オブジェクト生成時にフルスクリーンモードとテクスチャモード(WebVRの場合に利用)の2つがありますが、今回はフルスクリーンモードでオブジェクトを作成しました。 その後、テキストブロックをインスタンス化し表示する文字列を設定したり、サイズや表示位置を調整しています。

    const advancedTexture =
    BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI("UI");

    // スコア表示
    const scoreBlock = new BABYLON.GUI.TextBlock();
    scoreBlock.text = point + "pt";
    scoreBlock.fontSize = 20;
    scoreBlock.top = -300;
    scoreBlock.left = -150;
    scoreBlock.color = "black";
    advancedTexture.addControl(scoreBlock);

    // カウントダウン表示
    const timerblock = new BABYLON.GUI.TextBlock();
    timerblock.text = "Time:" + timeLimit;
    timerblock.fontSize = 20;
    timerblock.top = -300;
    timerblock.left = 150;
    timerblock.color = "black";
    advancedTexture.addControl(timerblock);

4.ガイドテキストの表示

ゲーム開始前にclick watermelon to game start と表示している箇所です。 こちらも文字列を表示したいだけなのですが、先程のGUI表現とは異なりHTMLのpタグを生成して、cssのabsoluteで絶対位置指定を行っています。 先程設定したスコアとカウントダウンは画面に常に表示していますが、ガイドテキストはゲーム開始時に非表示(display:none)にしたかった為、pタグを利用しています。

    const guideblock = document.createElement("p");
    guideblock.textContent = "click watermelon to game start";
    guideblock.style.top = "17rem";
    guideblock.style.left = "4rem";
    guideblock.style.color = "white";
    guideblock.style.fontSize = "large";
    guideblock.style.position = "absolute";
    document.body.appendChild(guideblock);

5.カメラとライトの設定

シーン表示の視点位置を調整しています。ここあたりはあまり理解できてません。 upperBetaLimitで、一定位置より下から見上げることができないように制限しています。

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

6.スカイボックスの設定

空と周りの海(ほとんど見えませんが)の設定を行います。 実は、空と海に見えていたものは立方体の中に閉じ込めた中からみたテクスチャに過ぎません。 テクスチャは、BABYLON.CubeTextureでパスを指定しており、パスの後にファイル名としてnx, ny, nz, px, py, pz がそれぞれ付与され、ディレクトリ内にある6枚の画像が自動で読み込まれます。

    const skybox = BABYLON.MeshBuilder.CreateBox(
      "TropicalSunnyDay",
      { size: 150 },
      scene
    );
    const skyboxMaterial = new BABYLON.StandardMaterial(
      "TropicalSunnyDay",
      scene
    );
    skyboxMaterial.backFaceCulling = false;
    skyboxMaterial.reflectionTexture = new BABYLON.CubeTexture(
      "./textures/TropicalSunnyDay",
      scene
    );
    skyboxMaterial.reflectionTexture.coordinatesMode =
      BABYLON.Texture.SKYBOX_MODE;
    skyboxMaterial.diffuseColor = new BABYLON.Color3(0, 0, 0);
    skyboxMaterial.specularColor = new BABYLON.Color3(0, 0, 0);
    skybox.material = skyboxMaterial;

ゲーム内でひたすらピンチアウトしていくとカメラが立方体の外側にはみ出し、 元の位置は立方体の中だった事が伺えます。

7.砂浜

砂浜っぽいテクスチャを、新しく生成した地面に貼り付けています。 でこぼこを表現するために、CreateGroundFromHeightMapで渡している、subdivisionsやmaxHeightを調整しています。

    const sandyBeach = new BABYLON.StandardMaterial("sandyBeach");
    sandyBeach.diffuseTexture = new BABYLON.Texture("./textures/sandyBeach.jpeg");

    const largeGround = BABYLON.MeshBuilder.CreateGroundFromHeightMap(
      "ground",
      "./textures/sandyBeach.jpeg",
      { width: 25, height: 25, subdivisions: 20, minHeight: 0, maxHeight: 1 }
    );
    largeGround.material = sandyBeach;

subdivisions の値が大きいほど、グラデーションが細かく(凸凹が強く)なります。

8.スイカの生成とクリック(タップ)時の動作の設定

生成した球体に、スイカ皮柄のテクスチャを貼ってそれっぽい見た目にしています。 なお、テクスチャには素材ラボ(投稿者:lutwidge モンキチさん)投稿の画像を利用させていただきました。

タップ時の動作は、ActionManager.registerActionで登録可能です。 初回タップ時のゲーム開始のフラグ制御とガイド文字の非表示や、 スイカがランダムで動く動作とスコア加算処理を登録しています。

処理を単純に書き下しすぎて少しごちゃっとしています。

    const watermelon = BABYLON.MeshBuilder.CreateSphere("watermelon", {});
    watermelon.position.y = 1.05;

    const watermelonTexture = new BABYLON.StandardMaterial("watermelonTexture");
    watermelonTexture.diffuseTexture = new BABYLON.Texture(
      "./textures/waterMelonPattern.png"
    );
    watermelon.material = watermelonTexture;
    watermelon.actionManager = new BABYLON.ActionManager(scene);
    watermelon.actionManager.registerAction(
      new BABYLON.ExecuteCodeAction(
        BABYLON.ActionManager.OnPickTrigger,
        function (evt) {
          if (timeLimit > 0) startFlg = true;
          if (!startFlg) return;
          guideblock.style.display = "none";

          // 最大座標(9)の範囲内でスイカを動かす
          const maxCoordinate = 9;
          watermelon.position.x = getRandomCoordinate(maxCoordinate);
          watermelon.position.z = getRandomCoordinate(maxCoordinate);

          // スコア更新
          point++;
          scoreBlock.text = point + "pt";
          advancedTexture.unRegisterClipboardEvents();
        }
      )
    );

    function getRandomCoordinate(max) {
      return Math.round(Math.random() * max) - max / 2;
    }

まとめ

babylon.jsで簡単なスイカ割れないゲームを作ってみた事例を紹介しました。 本当は割りたかったのです。しかし、スイカが割れる表現を行うにはどうすれば良いかがわからず、結局ゲームという形に着地してしまいました。

普段はECサイトの開発に携わっているので、業務では触ることのない分野を知ることが出来、勉強になりました。公式に日本語ドキュメントが無いということと、検索してもあまり記事や事例が見当たらないので少し辛みがありましたが、HTMLとJSだけで手軽に3D表現が味わえる為、実装が楽しいです。

この記事をみて一人でもbabylon.jsに興味を持ってくれるエンジニアが増え、日本語のアウトプット記事が少しでも増えてくれると嬉しいです。

P.S.

虎の穴ラボでは、私たちと一緒に新しいオタク向けサービスを作る仲間を募集しています。
詳しい採用情報は以下をご覧ください。

yumenosora.co.jp