LoginSignup
9
5

More than 3 years have passed since last update.

Babylon.jsで3Dゲームを作成してみた(その1:迷路編)

Last updated at Posted at 2020-01-04

1.概要

3Dグラフィック・エンジンの一つであるBabylon.jsを使用して、迷路ゲームを作成してみましたので以下に説明します。 なお、Windows10の環境において Microsoft Edge (Ver. 11.0.17763.379)、 Firefox (Ver. 65.0.2/64 bit) 及び Google Chrome (Ver. 73.0.3683.86/64 bit) で動作を確認しました。 また、Androidの一部及びiOSの一部での動作も確認しました。 ここで使用している3Dキャラクターは「デフォルメ人物キャラクター(女の子)は PROJECT-6B」様から、音声データは、「Let's Play with Free Sound Effects !」からダウンロードさせて戴きました。 無料グラフィック等を提供されている各位に感謝いたします。

完成した迷路ゲームの実行」: 迷路ゲームの画像(例)は次の通りです。 
Maze_00.png

2.ファイル構成

ここで説明する内容に関するファイルは全て「GitHub Babylon.js_3D_Graphics」からダウンロード可能です。 また、そのファイル構成は次の通りです。
- css: メニュー表示用スタイルシートを保存したフォルダーです。
- scenes: キャラクター(プレイヤー)等の3Dグラフィック・データを保存したフォルダーです。
- texture: 「Babylon.js on GITHUB」からダウンロードしたテクスチュア・データならびに2Dグラフィック・データ等を保存したフォルダーです。
- index_Maze.html: 今回作成した迷路ゲーム用HTMLファイルの選択・実行用メニューです。
- BabylonJS_maze_01.html - BabylonJS_maze_07.html: 今回作成した迷路ゲーム用JavaScriptを含むHTMLファイル本体です。
- Maze_01.js: 迷路データ作成JavaScriptファイルです。

3.迷路の作成

ここでは、周囲の環境とグラウンドの表示から迷路の表示、迷路内を歩行するキャラクターの表示と制御等をステップバイステップで例を示します。 なお、Babylon.jsの使い方については、「Babylon.jsで3Dアニメーションを含むグラフィックを描画してみる」で説明していますのでそちらを参照ください。
index_Maze.html」: ここで説明するプログラム全てをメニューから選択して表示できます。 但し、このメニューはiOSでは動作しませんので悪しからず。

Step-1: 周囲の環境とグラウンドを表示する

BabylonJS_maze_01.html:次のような周囲の環境とグラウンドを表示
Maze_01.png
周囲の環境(skybox)として晴れた海上をグラウンドとしてタイル張りの床を表示させました。 また、ライトとカメラも設定してありますので、マウスとカーソル・キーでカメラの視点を変更できます。 全体のソースコードを以下に示します。

BabylonJS_maze_01.html
<!doctype html>
<html>
    <head>
    <meta charset="utf-8">
    <title> Babylon.js - Maze_01 Environment & Ground - 2019/12/15 by T. Fujita</title>
    <link rel = "stylesheet" type="text/css" href = "./css/babylon_menu.css" />

    <script src="https://code.jquery.com/pep/0.4.0/pep.min.js"></script>
    <script src="https://cdn.babylonjs.com/babylon.js"></script>

    </head>
    <body onLoad = "init()">

      <canvas id = "renderCanvas"></canvas>
      <script type = "text/javascript">
    "use strict";

        var engine;
        var scene;
    var canvas = document.getElementById("renderCanvas");
    var temp_Environment = "./textures/TropicalSunnyDay";
    var camera;
    var Maze_size_X = 33;               // The row size of maze.
    var Maze_size_Z = 25;               // The col size of maze.
    var BLOCK_SIZE = 8;

    function init() {
        engine = new BABYLON.Engine(canvas, true, { preserveDrawingBuffer: true, stencil: true });
        scene = createScene();
            engine.runRenderLoop(function() {
        scene.render();
        });
    }

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

// Camera
        camera = new BABYLON.ArcRotateCamera("Camera", 0/180*Math.PI, 30/180*Math.PI, 10, new BABYLON.Vector3(0, 8, 0), scene);
        camera.setPosition(new BABYLON.Vector3((BLOCK_SIZE * Maze_size_X / 2 * -1) - 40, 30, (BLOCK_SIZE * Maze_size_Z / 2 * -1) + 12));

// Ground
        var groundMaterial = new BABYLON.StandardMaterial("ground", scene);
        groundMaterial.diffuseTexture = new BABYLON.Texture("./textures/floor4.jpg", scene);
        groundMaterial.diffuseTexture.uScale = Maze_size_X;
        groundMaterial.diffuseTexture.vScale = Maze_size_Z;
        groundMaterial.specularColor = new BABYLON.Color3(0, 0, 0);
        var ground = BABYLON.Mesh.CreateGround("ground", (Maze_size_X + 2) * BLOCK_SIZE, (Maze_size_Z + 2) * BLOCK_SIZE, 1, scene, false);
        ground.material = groundMaterial;

//Skybox
        var skybox = BABYLON.Mesh.CreateBox("skyBox", 800.0, scene);
        var skyboxMaterial = new BABYLON.StandardMaterial("skyBox", scene);
        skyboxMaterial.backFaceCulling = false;
        skyboxMaterial.reflectionTexture = new BABYLON.CubeTexture(temp_Environment, 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;

// Lights
            var light0 = new BABYLON.DirectionalLight('light00', new BABYLON.Vector3(1000, -1000, 1000), scene);
//      light0.position = new BABYLON.Vector3(0, 100, 100);
        light0.intensity = 1.0;
        var light1 = new BABYLON.HemisphericLight("light01", new BABYLON.Vector3(0, 1000, 0), scene);
        light1.position = new BABYLON.Vector3(1000, 1000, -1000);
        light1.intensity = 0.5;

        camera.attachControl(canvas, true);
            return scene;
    };
      </script>
    </body>
</html>

Step-2: Virtual Joystickを追加する

BabylonJS_maze_02.html:次のようなVirtual Joystickを表示
この迷路ゲームでは、プレイヤーとカメラのそれぞれをコントロールしますので左側にプレイヤー用右側にカメラ用のVirtual Joystickを追加しました。(次の画像で右下に水色の円がVirtual Joystickです。 また、プレイヤーのコントロールはStep-6以降で可能です。)
Maze_02.png

BabylonJS_maze_02.htmlの一部
// Create VirtualJoystick and set z index to be below playgrounds top bar
            var leftJoystick = new BABYLON.VirtualJoystick(true);
        var rightJoystick = new BABYLON.VirtualJoystick(false);
            BABYLON.VirtualJoystick.Canvas.style.zIndex = "4";

// Render loop for VirtualJoystick
    scene.onBeforeRenderObservable.add(()=>{
        moveX=0;
        moveZ=0;
                if(leftJoystick.pressed){
            if(leftJoystick.deltaPosition.x <= -0.5) {
                walk_dir = 0 / 180 * Math.PI;
                moveX = 0;
                moveZ = 1;
                walk_step = walk_org * Math.pow(leftJoystick.deltaPosition.x, 2);
            } else if(leftJoystick.deltaPosition.x >= 0.5) {
                walk_dir = 180 / 180 * Math.PI;
                moveX = 0;
                moveZ = -1;
                walk_step = walk_org * Math.pow(leftJoystick.deltaPosition.x, 2);
            } else {
                moveZ = 0;
            }
            if(leftJoystick.deltaPosition.y <= -0.5) {
                walk_dir = -90 / 180 * Math.PI;
                moveX = -1;
                moveZ = 0;
                walk_step = walk_org * Math.pow(leftJoystick.deltaPosition.y, 2);
            } else if(leftJoystick.deltaPosition.y >= 0.5) {
                walk_dir = 90 / 180 * Math.PI;
                moveX = 1;
                moveZ = 0;
                walk_step = walk_org * Math.pow(leftJoystick.deltaPosition.y, 2);
            } else {
                moveX = 0;
            }
            if((camera.alpha >= -135 / 180 * Math.PI) && (camera.alpha < -45 / 180 * Math.PI)) {
            if(leftJoystick.deltaPosition.x <= -0.5) {
                walk_dir = -90 / 180 * Math.PI;
                moveX = -1;
                moveZ = 0;
                walk_step = walk_org * Math.pow(leftJoystick.deltaPosition.x, 2);
            } else if(leftJoystick.deltaPosition.x >= 0.5) {
                walk_dir = 90 / 180 * Math.PI;
                moveX = 1;
                moveZ = 0;
                walk_step = walk_org * Math.pow(leftJoystick.deltaPosition.x, 2);
            } else {
                moveX = 0;
            }
            if(leftJoystick.deltaPosition.y <= -0.5) {
                walk_dir = 180 / 180 * Math.PI;
                moveX = 0;
                moveZ = -1;
                walk_step = walk_org * Math.pow(leftJoystick.deltaPosition.y, 2);
            } else if(leftJoystick.deltaPosition.y >= 0.5) {
                walk_dir = 0 / 180 * Math.PI;
                moveX = 0;
                moveZ = 1;
                walk_step = walk_org * Math.pow(leftJoystick.deltaPosition.y, 2);
            } else {
                moveZ = 0;
            }
            }
            if((camera.alpha >= -45 / 180 * Math.PI) && (camera.alpha < 45 / 180 * Math.PI)) {
            if(leftJoystick.deltaPosition.x <= -0.5) {
                walk_dir = 180 / 180 * Math.PI;
                moveX = 0;
                moveZ = -1;
                walk_step = walk_org * Math.pow(leftJoystick.deltaPosition.x, 2);
            } else if(leftJoystick.deltaPosition.x >= 0.5) {
                walk_dir = 0 / 180 * Math.PI;
                moveX = 0;
                moveZ = 1;
                walk_step = walk_org * Math.pow(leftJoystick.deltaPosition.x, 2);
            } else {
                moveZ = 0;
            }
            if(leftJoystick.deltaPosition.y <= -0.5) {
                walk_dir = 90 / 180 * Math.PI;
                moveX = 1;
                moveZ = 0;
                walk_step = walk_org * Math.pow(leftJoystick.deltaPosition.y, 2);
            } else if(leftJoystick.deltaPosition.y >= 0.5) {
                walk_dir = -90 / 180 * Math.PI;
                moveX = -1;
                moveZ = 0;
                walk_step = walk_org * Math.pow(leftJoystick.deltaPosition.y, 2);
            } else {
                moveX = 0;
            }
            }
            if((camera.alpha >= 45 / 180 * Math.PI) && (camera.alpha < 135 / 180 * Math.PI)) {
            if(leftJoystick.deltaPosition.x <= -0.5) {
                walk_dir = 90 / 180 * Math.PI;
                moveX = 1;
                moveZ = 0;
                walk_step = walk_org * Math.pow(leftJoystick.deltaPosition.x, 2);
            } else if(leftJoystick.deltaPosition.x >= 0.5) {
                walk_dir = -90 / 180 * Math.PI;
                moveX = -1;
                moveZ = 0;
                walk_step = walk_org * Math.pow(leftJoystick.deltaPosition.x, 2);
            } else {
                moveX = 0;
            }
            if(leftJoystick.deltaPosition.y <= -0.5) {
                walk_dir = 0 / 180 * Math.PI;
                moveX = 0;
                moveZ = 1;
                walk_step = walk_org * Math.pow(leftJoystick.deltaPosition.y, 2);
            } else if(leftJoystick.deltaPosition.y >= 0.5) {
                walk_dir = 180 / 180 * Math.PI;
                moveX = 0;
                moveZ = -1;
                walk_step = walk_org * Math.pow(leftJoystick.deltaPosition.y, 2);
            } else {
                moveZ = 0;
            }
            }
                }
        if(rightJoystick.pressed){
            if(rightJoystick.deltaPosition.x <= -0.5) {
                    camera.alpha = camera.alpha + rightJoystick.deltaPosition.x/180*Math.PI;
                if(camera.alpha < -1 * Math.PI) {
                camera.alpha = Math.PI;
                }
            }
            else if(rightJoystick.deltaPosition.x >= 0.5) {
                    camera.alpha = camera.alpha + rightJoystick.deltaPosition.x/180*Math.PI;
                if(camera.alpha > Math.PI) {
                camera.alpha = -1 * Math.PI;
                }
            }
            else {
                    camera.alpha = camera.alpha;
            }
            if(rightJoystick.deltaPosition.y <= -0.5) {
                    camera.radius = camera.radius + 0.5;
            }
            else if(rightJoystick.deltaPosition.y >= 0.5) {
                    camera.radius = camera.radius - 0.5;
            }
            else {
                    camera.radius = camera.radius;
            }
            if((rightJoystick.deltaPosition.x <= -0.6) && (rightJoystick.deltaPosition.y <= -0.6)) {
                    camera.alpha = camera.alpha;
                    camera.beta = camera.beta + 0.2/180*Math.PI;
                    camera.radius = camera.radius;
            }
            else if((rightJoystick.deltaPosition.x <= -0.6) && (rightJoystick.deltaPosition.y >= 0.6)) {
                    camera.alpha = camera.alpha;
                    camera.beta = camera.beta - 0.2/180*Math.PI;
                    camera.radius = camera.radius;
            }
            else if((rightJoystick.deltaPosition.x >= 0.6) && (rightJoystick.deltaPosition.y <= -0.6)) {
                    camera.alpha = camera.alpha;
                    camera.beta = camera.beta + 0.2/180*Math.PI;
                    camera.radius = camera.radius;
            }
            else if((rightJoystick.deltaPosition.x >= 0.6) && (rightJoystick.deltaPosition.y >= 0.6)) {
                    camera.alpha = camera.alpha;
                    camera.beta = camera.beta - 0.2/180*Math.PI;
                    camera.radius = camera.radius;
            }
        }
    });

// Create button to toggle VirtualJoystick overlay canvas
            var btn = document.createElement("button");
            btn.innerText = "Enable/Disable Joystick";
            btn.style.zIndex = 10;
            btn.style.position = "absolute";
            btn.style.bottom = "50px";
            btn.style.right = "0px";
            document.body.appendChild(btn);

// Button toggle logic for VirtualJoystick
            btn.onclick = ()=>{
                if(BABYLON.VirtualJoystick.Canvas.style.zIndex == "-1"){
                    BABYLON.VirtualJoystick.Canvas.style.zIndex = "4";
                }else{
                    BABYLON.VirtualJoystick.Canvas.style.zIndex = "-1";
                }
            }

// Dispose button on rerun for VirtualJoystick
            scene.onDisposeObservable.add(()=>{
                document.body.removeChild(btn);
            });

// Moving Limit for Camera
        var angle = 0.03;
        var plane_Axis = new BABYLON.Vector3(0, 90, 0);
        var beforeRenderFunction = function () {
        camera.lowerBetaLimit = 0.1;
        camera.upperBetaLimit = (Math.PI / 2) * 0.9;
        camera.lowerRadiusLimit = 10;
        camera.upperRadiusLimit = 1500;
        camera.attachControl(canvas, true);
        };

        scene.registerBeforeRender(beforeRenderFunction);
            return scene;
    };

Step-3: ブロックを1個表示する

BabylonJS_maze_03.html:次のようにテクスチャー付きのブロック1個を表示
グラウンドの真中にブロックを1個設置します。 ここでは、ライトの位置を太陽とブロックの影が違和感なく見えるように調整しています。
Maze_03.png
 ブロックのテクスチャーは次の画像を使用しています。 正方形の画像6枚を横に並べており、上下2面と他の4面で画像を変えています。
block_texture.png

BabylonJS_maze_03.htmlの一部
// Create Materials
        var cubeMaterial = new BABYLON.StandardMaterial("cube", scene);
        var cubetexture = new BABYLON.Texture("./textures/block_texture.png", scene);
        cubeMaterial.diffuseTexture = cubetexture;

// Create Cube
        var columns = 6;
        var rows = 1;
        var faceUV = new Array(6);
        for (var i = 0; i < 6; i++) {
            faceUV[i] = new BABYLON.Vector4(i / columns, 0, (i + 1) / columns, 1 / rows);
        }
        var options = {
        width: BLOCK_SIZE,
        height: BLOCK_SIZE,
        depth: BLOCK_SIZE,
            faceUV: faceUV
        };
        var cube = BABYLON.MeshBuilder.CreateBox('Cube', options, scene);
        cube.material = cubeMaterial;
        cube.position = new BABYLON.Vector3(0, BLOCK_SIZE / 2, 0);
        shadowGenerator.addShadowCaster(cube);
        shadowGenerator.getShadowMap().renderList.push(cube);
        cube.receiveShadows = true;

Step-4: 迷路を表示する

BabylonJS_maze_04.html:次のように迷路本体を表示
迷路データは配列「ROOM」に壁(W)/ゴール(G)/プレーヤー(P)として収容されており、それぞれの行列位置に対応した画像を設置します。
Maze_04.png

BabylonJS_maze_04.htmlの一部
// Create a Maze
        for (var row = 0; row < Maze_size_X; row++) {
            for (var col = 0; col < Maze_size_Z; col++) {
            if(ROOM[row].substr(col, 1) == "W") {
                var cube = BABYLON.MeshBuilder.CreateBox('Cube', options, scene);
                cube.material = cubeMaterial;
                    cube.position = new BABYLON.Vector3(BLOCK_SIZE / 2 + (row - (Maze_size_X / 2)) * BLOCK_SIZE, BLOCK_SIZE / 2, BLOCK_SIZE / 2 + (col - (Maze_size_Z / 2)) * BLOCK_SIZE);
            shadowGenerator.addShadowCaster(cube);
            shadowGenerator.getShadowMap().renderList.push(cube);
            cube.receiveShadows = true;
            }
            if(ROOM[row].substr(col, 1) == "G") {
            Goal_x = BLOCK_SIZE / 2 + (row - (Maze_size_X / 2)) * BLOCK_SIZE;
            Goal_z = BLOCK_SIZE / 2 + (col - (Maze_size_Z / 2)) * BLOCK_SIZE;
            var goalCube = BABYLON.MeshBuilder.CreateBox('goalCube', options_G, scene);
                goalCube.material = goalMaterial;
                goalCube.position = new BABYLON.Vector3(Goal_x, 0.1, Goal_z);
                goalCube.receiveShadows = true;
            }
            if(ROOM[row].substr(col, 1) == "P") {
            x = BLOCK_SIZE / 2 + (row - (Maze_size_X / 2)) * BLOCK_SIZE;
            z = BLOCK_SIZE / 2 + (col - (Maze_size_Z / 2)) * BLOCK_SIZE;
            }
        }
        }

迷路データ作成用のJavaScriptを次に示します。 このJavaScriptは、「
5分でできる迷路・自動生成アルゴリズム
」に基づき作成しました。 なお、迷路は毎回異なる形状となります。

maze_01.js
// This program is based on "https://matome.naver.jp/odai/2141170552198835001" by Mr. tan23ae.
// by T. Fujita

var Maze = function(X, Y) {
    var w = X;                              // 幅(奇数)
    var h = Y;                              // 高さ(奇数)
    if(w < 11) {w = 11;}
    if(h < 11) {h = 11;}
    if(w % 2 == 0) {w = w - 1;}
    if(h % 2 == 0) {h = h - 1;}

    var x;
    var y;
    var maze = new Array();
    var results = new Array();

// 準備
    for (y = 0; y < h; y++) {
    maze[y] = new Array();
    for (x = 0; x < w; x++) {
        if (y == 0 || y == h -1 || x == 0 || x == w - 1) {
        maze[y][x] = "W";                   // 外周
        } else if (y % 2 == 0 && x % 2 == 0) {
        maze[y][x] = "W";                   // [偶数][偶数]
        } else {
            maze[y][x] = "F";                   // その他
        }
    }
    }

// 壁位置
    for (y = 2; y < h - 2; y += 2) {
    for (x = 2; x < w - 2; x += 2) {
        var n;
        if (y == 2) {                       // 一番上の段
        if (maze[y][x-1] == "W") {
            n = rand(0, 2);
        } else {
            n = rand(0, 3);
        }
        } else {
        if (maze[y][x-1] == "W") {
            n = rand(1, 2);
        } else {
            n = rand(1, 3);
        }
        }

        switch (n) {
        case 0:                         // 上
            maze[y - 1][x] = "W";
            break;
        case 1:                         // 右
            maze[y][x + 1] = "W";
            break;
        case 2:                         // 下
            maze[y + 1][x] = "W";
            break;
        default:                        // 左
            maze[y][x - 1] = "W";
            break;
        }
    }
    }
    var i = 0;
    while (maze[0][i] == "W") {
    if(maze[1][i + 1] == "F") {maze[0][i + 1] = "P";}
    i = i + 1;
    }
    i = maze[0].length - 2;
    var j = maze.length - 1;
    while(maze[j][i] == "W") {
    if(maze[j - 1][i - 1] == "F") {maze[j][i - 1] = "G";}
    i = i - 1;
    }
// alert(maze[0] + "\n" + maze[1]);
    for (y = 0; y < h; y++) {
    results[y] = [];
    for (x = 0; x < w; x++) {
        results[y] = results[y] + maze[y][x];
    }
    }
// alert(results[0] + "\n" + results[1]);
    return results;
}

// 乱数取得
var rand = function(min, max) {
    return Math.floor(Math.random() * (max + 1 - min) + min);
};

Step-5: ゴールとアニメーション付キャラクタ(プレイヤー)を追加する

BabylonJS_maze_05.html:次のようにゴールとアニメーション付キャラクタを表示
ゴールの位置が分かりにくいため、ゴールに街灯を設置しました。 また、プレイヤーを表示させカメラがプレーヤーを追跡するように設定しました。
Maze_05a.png

BabylonJS_maze_05.htmlの一部
            if(ROOM[row].substr(col, 1) == "G") {
            Goal_x = BLOCK_SIZE / 2 + (row - (Maze_size_X / 2)) * BLOCK_SIZE;
            Goal_z = BLOCK_SIZE / 2 + (col - (Maze_size_Z / 2)) * BLOCK_SIZE;
            var goalCube = BABYLON.MeshBuilder.CreateBox('goalCube', options_G, scene);
                goalCube.material = goalMaterial;
                goalCube.position = new BABYLON.Vector3(Goal_x, 0.1, Goal_z);
                goalCube.receiveShadows = true;

            BABYLON.SceneLoader.ImportMesh("", temp_dir, gltf_data_02, scene, function (newMeshes2) {
                var lamp = newMeshes2[0];
                lamp.position = new BABYLON.Vector3(Goal_x + 3, 0, Goal_z + 3);
                lamp.scaling = new BABYLON.Vector3(1, 1.5, 1);
                var materialSphere = new BABYLON.StandardMaterial("sphere0", scene);
                materialSphere.emissiveColor = new BABYLON.Color3(1.0, 0.84, 0.0);
                bulb.position = new BABYLON.Vector3(Goal_x + 3, 27, Goal_z + 3);
                bulb.material = materialSphere;
                shadowGenerator.addShadowCaster(lamp);
                shadowGenerator.getShadowMap().renderList.push(lamp);
            });
            }
            if(ROOM[row].substr(col, 1) == "P") {
            x = BLOCK_SIZE / 2 + (row - (Maze_size_X / 2)) * BLOCK_SIZE;
            y = 0;
            z = BLOCK_SIZE / 2 + (col - (Maze_size_Z / 2)) * BLOCK_SIZE;
            BABYLON.SceneLoader.ImportMesh("", temp_dir, gltf_data_01, scene, function (newMeshes1) {
                var player =  newMeshes1[0];
                player.rotationQuaternion = undefined;
                player.scaling = new BABYLON.Vector3(0.08, 0.08, 0.08);
                player.position = new BABYLON.Vector3(x, y, z);
                player.rotation.y = 180/180 * Math.PI + walk_dir;
                camera.target = player;
                shadowGenerator.addShadowCaster(player);
                shadowGenerator.getShadowMap().renderList.push(player);
                player.receiveShadows = true;
            });
            }

Step-6: キャラクタの動きを制御する

BabylonJS_maze_06.html:次のようにキャラクタの動きを制御する
プレーヤーが歩行するように設定する他、グラウンド内に留まるように制限したり、壁をすり抜けないよう制限しました。
Maze_06.png

BabylonJS_maze_06.htmlの一部
        scene.registerBeforeRender(function() {
                if((moveX == -1) && (player.position.x <= (BLOCK_SIZE * (Maze_size_X + 2) / -2) + 2)) {
                    moveX = 0;
                }
                if((moveX == 1) && (player.position.x >= (BLOCK_SIZE * (Maze_size_X + 2) / 2) - 2)) {
                    moveX = 0;
                }
                if((moveZ == -1) && (player.position.z <= (BLOCK_SIZE * (Maze_size_Z + 2) / -2) + 2)) {
                    moveZ = 0;
                }
                if((moveZ == 1) && (player.position.z >= (BLOCK_SIZE * (Maze_size_Z + 2) / 2) - 2)) {
                    moveZ = 0;
                }
                x = player.position.x;
                z = player.position.z;
                pos_row_00 = Math.round(((x - BLOCK_SIZE / 2) / BLOCK_SIZE) + (Maze_size_X / 2));
                pos_row_01 = Math.round((((x + limit) - BLOCK_SIZE / 2) / BLOCK_SIZE) + (Maze_size_X / 2));
                pos_row_02 = Math.round((((x - limit) - BLOCK_SIZE / 2) / BLOCK_SIZE) + (Maze_size_X / 2));
                pos_col_00 = Math.round(((z - BLOCK_SIZE / 2) / BLOCK_SIZE) + (Maze_size_Z / 2));
                pos_col_01 = Math.round((((z + limit) - BLOCK_SIZE / 2) / BLOCK_SIZE) + (Maze_size_Z / 2));
                pos_col_02 = Math.round((((z - limit) - BLOCK_SIZE / 2) / BLOCK_SIZE) + (Maze_size_Z / 2));

                if((moveX == 1) && (Temp_Room[pos_row_01 + 2].substr(pos_col_00 + 2, 1) == "W")) { moveX = 0; }
                if((moveX == -1) && (Temp_Room[pos_row_02 + 2].substr(pos_col_00 + 2, 1) == "W")) { moveX = 0; }
                if((moveZ == 1) && (Temp_Room[pos_row_00 + 2].substr(pos_col_01 + 2, 1) == "W")) { moveZ = 0; }
                if((moveZ == -1) && (Temp_Room[pos_row_00 + 2].substr(pos_col_02 + 2, 1) == "W")) { moveZ = 0; }
                player.position.x = x + walk_step * moveX;
                player.position.y = 0;
                player.position.z = z + walk_step * moveZ;
                player.rotation.y = walk_dir;
                });

Step-7: メニュー等を追加しゲームを完成させる

BabylonJS_maze_07.html:メニュー付の迷路ゲームを実行
最後に迷路サイズの変更やプレイヤー歩行速度の変更が可能なようにメニューを追加した他、ゴール時の処理を追加しました。
Maze_07.png
メニューを追加したHTML部分

BabylonJS_maze_07.htmlの一部
    <nav id="menu-wrap">  
        <ul id="menu">
        <li><a href="#">Menu</a>
        <ul>
            <li><a><span class = "fsize_12">Click the left side. Then, you can control the character.</span></a></li>
            <li><a><span class = "fsize_12">Click the right side. Then, you can control the camera.</span></a></li>
        </ul>
        </li>
        <li><a href="#">Maze's Size</a>
        <ul id="scroll">
            <li><a><input type = "radio" name = "maze_Layer" value = "0" checked onclick = "javascript: Sel_maze_Layer();">33 x 25</a></li>
            <li><a><input type = "radio" name = "maze_Layer" value = "1" onclick = "javascript: Sel_maze_Layer();">15 x 11</a></li>
            <li><a><input type = "radio" name = "maze_Layer" value = "2" onclick = "javascript: Sel_maze_Layer();">25 x 21</a></li>
            <li><a><input type = "radio" name = "maze_Layer" value = "3" onclick = "javascript: Sel_maze_Layer();">35 x 31</a></li>
            <li><a><input type = "radio" name = "maze_Layer" value = "4" onclick = "javascript: Sel_maze_Layer();">45 x 41</a></li>
            <li><a><input type = "radio" name = "maze_Layer" value = "5" onclick = "javascript: Sel_maze_Layer();">55 x 51</a></li>
            <li><a><input type = "radio" name = "maze_Layer" value = "6" onclick = "javascript: Sel_maze_Layer();">65 x 61</a></li>
            <li><a><input type = "radio" name = "maze_Layer" value = "7" onclick = "javascript: Sel_maze_Layer();">75 x 71</a></li>
            <li><a><input type = "radio" name = "maze_Layer" value = "8" onclick = "javascript: Sel_maze_Layer();">85 x 81</a></li>
            <li><a><input type = "radio" name = "maze_Layer" value = "9" onclick = "javascript: Sel_maze_Layer();">95 x 91</a></li> 
        </ul>
        </li>
        <li><a href="#">Walk Speed</a>
        <ul>
            <li><a href="#" onclick = "Slow()">Slow</a></li>
            <li><a href="#" onclick = "Medium()">Medium</a></li>
            <li><a href="#" onclick = "Fast()">Fast</a></li>
        </ul>
        </li>
        <li><a href="#">Game Start</a>
        <ul>
            <li><a href="#" onclick = "init()">Start Game</a></li>
            <li><a href="#" onclick = "window.location.reload()">All Reset</a></li>
        </ul>
        </li>
        </ul>
    </nav>

ゴール時の処理とメニューに対応したJavaScript部分を以下に示します。

BabylonJS_maze_07.htmlの一部
// Moving Limit for Camera
        var angle = 0.03;
        var plane_Axis = new BABYLON.Vector3(0, 90, 0);
        var beforeRenderFunction = function () {
        camera.lowerBetaLimit = 0.1;
        camera.upperBetaLimit = (Math.PI / 2) * 0.9;
        camera.lowerRadiusLimit = 10;
        camera.upperRadiusLimit = 1500;
        camera.attachControl(canvas, true);

// Player Reached to the Goal
        if((Math.round(x / 8) == Math.round(Goal_x / 8)) && (Math.round(z / 8) == Math.round(Goal_z / 8)) && (Goal_flag == 0)) {
            var advancedTexture_01 = BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI("UI");
                var text1 = new BABYLON.GUI.TextBlock();
            text1.text = "GOAL !";
                text1.color = "red";
                text1.fontSize = 100;
                advancedTexture_01.addControl(text1); 
            var music = new BABYLON.Sound('Goal', './sound/info-girl1-goal1.mp3', scene, function() {
                music.play();
                Goal_flag = 1;
            });
        }
        };

       scene.registerBeforeRender(beforeRenderFunction);
            return scene;
    };

    function Sel_maze_Layer() {
    var temp = document.getElementsByName("maze_Layer");
    Maze_size_X = 33;
    Maze_size_Z = 25;
    if(temp[1].checked) { Maze_size_X = 15; Maze_size_Z = 11;}
    if(temp[2].checked) { Maze_size_X = 25; Maze_size_Z = 21;}
    if(temp[3].checked) { Maze_size_X = 35; Maze_size_Z = 31;}
    if(temp[4].checked) { Maze_size_X = 45; Maze_size_Z = 41;}
    if(temp[5].checked) { Maze_size_X = 55; Maze_size_Z = 51;}
    if(temp[6].checked) { Maze_size_X = 65; Maze_size_Z = 61;}
    if(temp[7].checked) { Maze_size_X = 75; Maze_size_Z = 71;}
    if(temp[8].checked) { Maze_size_X = 85; Maze_size_Z = 81;}
    if(temp[9].checked) { Maze_size_X = 95; Maze_size_Z = 91;}
    }
    function Slow() {
    walk_org = 0.4;
    }
    function Medium() {
    walk_org = 0.8;
    }
    function Fast() {
    walk_org = 1.2;
    }

以上で迷路ゲームを作成することができました。

4、Reference

以上

9
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
5