Babylon.jsで3Dゲームを作成してみた（その１：迷路編）

#１．概要
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](http://6b.u5ch.com/others/downloads/index.html)」様から、音声データは、「[Let's Play with Free Sound Effects !](https://taira-komori.jpn.org/)」からダウンロードさせて戴きました。　無料グラフィック等を提供されている各位に感謝いたします。

「[完成した迷路ゲームの実行](https://to-fujita.github.io/Babylon.js_3D_Graphics/BabylonJS_maze_07.html)」：　迷路ゲームの画像（例）は次の通りです。　
![Maze_00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408707/83c66ebe-ffb8-81d1-9ddb-bf06df1557cf.png)

#２．ファイル構成
ここで説明する内容に関するファイルは全て「[GitHub Babylon.js_3D_Graphics](https://github.com/To-Fujita/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ファイルです。

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

## Step-1: 周囲の環境とグラウンドを表示する
[BabylonJS_maze_01.html](https://to-fujita.github.io/Babylon.js_3D_Graphics/BabylonJS_maze_01.html)：次のような周囲の環境とグラウンドを表示
![Maze_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408707/50fb8c4f-e99e-2aa5-1edb-92962c18a698.png)
周囲の環境(skybox)として晴れた海上をグラウンドとしてタイル張りの床を表示させました。　また、ライトとカメラも設定してありますので、マウスとカーソル・キーでカメラの視点を変更できます。　全体のソースコードを以下に示します。

~~~HTML: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](https://to-fujita.github.io/Babylon.js_3D_Graphics/BabylonJS_maze_02.html)：次のようなVirtual Joystickを表示
この迷路ゲームでは、プレイヤーとカメラのそれぞれをコントロールしますので左側にプレイヤー用右側にカメラ用のVirtual Joystickを追加しました。（次の画像で右下に水色の円がVirtual Joystickです。　また、プレイヤーのコントロールはStep-6以降で可能です。）
![Maze_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408707/eed9c0e6-4d52-7d26-98c8-d513c9025615.png)


~~~Javascript: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: ブロックを１個表示する
[BabylonJS_maze_03.html](https://to-fujita.github.io/Babylon.js_3D_Graphics/BabylonJS_maze_03.html)：次のようにテクスチャー付きのブロック１個を表示
グラウンドの真中にブロックを１個設置します。　ここでは、ライトの位置を太陽とブロックの影が違和感なく見えるように調整しています。
![Maze_03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408707/4e3f407c-26f0-f8cf-a11f-ee8adf928150.png)
　ブロックのテクスチャーは次の画像を使用しています。　正方形の画像６枚を横に並べており、上下２面と他の４面で画像を変えています。
![block_texture.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408707/251dca33-1ef8-a39d-babd-14f76691e582.png)

~~~Javascript: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](https://to-fujita.github.io/Babylon.js_3D_Graphics/BabylonJS_maze_04.html)：次のように迷路本体を表示
迷路データは配列「ROOM」に壁(W)／ゴール(G)／プレーヤー(P)として収容されており、それぞれの行列位置に対応した画像を設置します。
![Maze_04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408707/ce2b358a-b5d7-400d-a935-aaa4d6224f98.png)

~~~Javascript: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は、「[ 
５分でできる迷路・自動生成アルゴリズム](https://matome.naver.jp/odai/2141170552198835001)」に基づき作成しました。　なお、迷路は毎回異なる形状となります。

~~~Javascript: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](https://to-fujita.github.io/Babylon.js_3D_Graphics/BabylonJS_maze_05.html)：次のようにゴールとアニメーション付キャラクタを表示
ゴールの位置が分かりにくいため、ゴールに街灯を設置しました。　また、プレイヤーを表示させカメラがプレーヤーを追跡するように設定しました。
![Maze_05a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408707/a71be674-808b-c7ac-a837-ba068342a40e.png)

~~~Javascript: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](https://to-fujita.github.io/Babylon.js_3D_Graphics/BabylonJS_maze_06.html)：次のようにキャラクタの動きを制御する
プレーヤーが歩行するように設定する他、グラウンド内に留まるように制限したり、壁をすり抜けないよう制限しました。
![Maze_06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408707/44a65a42-442e-6951-f2b9-a7abbd874797.png)

~~~Javascript: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](https://to-fujita.github.io/Babylon.js_3D_Graphics/BabylonJS_maze_07.html)：メニュー付の迷路ゲームを実行
最後に迷路サイズの変更やプレイヤー歩行速度の変更が可能なようにメニューを追加した他、ゴール時の処理を追加しました。
![Maze_07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/408707/725cf6d1-30ce-539b-bd8d-326aa339d12e.png)
メニューを追加したHTML部分

~~~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部分を以下に示します。

~~~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;
    }
~~~

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

#４、Reference
- [Babylon.js](https://www.babylonjs.com/): Home page of Babylon.js
- [かんたんBlender講座](http://krlab.info.kochi-tech.ac.jp/kurihara/lecture/cg/BlenderWeb_Hayashi/html/index.html): 高知工科大学のかんたんBlender講座
- [PROJECT-6B](http://6b.u5ch.com/index.html): デフォルメ人物キャラクター(女の子)のダウンロード・サイト
- [Coding4Fun tutorial: creating a 3D WebGL procedural QRCode maze with Babylon.js](https://www.davrous.com/2014/02/19/coding4fun-tutorial-creating-a-3d-webgl-procedural-qrcode-maze-with-babylon-js/): Babylon.jsによる迷路作成の解説
- [５分でできる迷路・自動生成アルゴリズム](https://matome.naver.jp/odai/2141170552198835001)　  
- [Let's Play with Free Sound Effects !](https://taira-komori.jpn.org/) 
- [Cocos2d.JSでゲームを作ってみた](https://qiita.com/halboujp/items/3d99c122bdc471caa5b8) 
- [Phaser3.JSでゲームを作ってみた](https://qiita.com/halboujp/items/7f6cfcd73a1c95023511) 
- [Phina.JSでゲームを作ってみた](https://qiita.com/halboujp/items/1cae099645d7a4e1bf2f) 
- [Babylon.jsで3Dアニメーションを含むグラフィックを描画してみる](https://qiita.com/halboujp/items/c18daf72019b09a94d44)   
- [Babylon.jsで3Dゲームを作成してみた（その２：脱出パズル編）](https://qiita.com/halboujp/items/751c42f3bda10adb7674)　　
- [Babylon.jsで空中散歩はいかが？](https://qiita.com/halboujp/items/4222f33057c3c83f97fa)　

以上