three.js を使用して 3D JavaScript ゲームを作成するCreating a 3D JavaScript game using three.js

概要Introduction

Web 開発者や JavaScript 作者にとって、JavaScript で UWP アプリを開発することは、作成したアプリを世界中に向けて公開するための簡単な方法です。For web developers or JavaScript tinkerers, developing UWP apps with JavaScript is an easy way in to getting your apps out to the world. C# や C++ のような言語を学習する必要はありません。No need to worry about learning a language like C# or C++!

このサンプルでは、three.js ライブラリを活用します。For this sample, we’re going to be taking advantage of the three.js library. このライブラリは、WebGL から構築されています。WebGL は、Web ブラウザー用に 2D/3D のグラフィックスをレンダリングするために使用する API です。This library builds off of WebGL, an API that's used for rendering 2D and 3D graphics for web browsers. three.js は、この複雑な API を単純化したもので、これを使用すると 3D 開発がずっと容易になります。three.js takes this complicated API and simplifies it, making 3D development much easier.

先に進む前に、これから作成するアプリを見ておきましょう。Want to get a glimpse of the app we'll be making before reading further? CodePen で確認してください。Check it out on CodePen!

注意

これはない完全版のゲームです。Microsoft Store に発行する準備がアプリを作成する JavaScript とサード パーティ製ライブラリを使用する方法を示すには設計されています。This is a not a complete game; it is designed to demonstrate using JavaScript and a third-party library to make an app ready to publish to the Microsoft Store.

要件Requirements

このプロジェクトを操作するには、以下が必要になります。To play with this project, you'll need the following:

  • 現在のバージョンの Windows 10 を実行する Windows コンピューター (または仮想マシン)。A Windows computer (or a virtual machine) running the current version of Windows 10.
  • Visual Studio。A copy of Visual Studio. 無料の Visual Studio Community Edition は、Visual Studio ホームページからダウンロードできます。The free Visual Studio Community Edition can be downloaded from the Visual Studio homepage. このプロジェクトでは、three.js という JavaScript ライブラリを使用します。This project makes use of the three.js JavaScript library. three.js は、MIT ライセンスの下でリリースされています。three.js is released under the MIT license. このライブラリは、プロジェクト内に既に存在します (ソリューション エクスプローラー ビューで js/libs を探してください)。This library is already present in the project (look for js/libs in the Solution Explorer view). このライブラリについて詳しくは、three.js のホーム ページをご覧ください。More information about this library can be found at the three.js home page.

概要Getting started

アプリの完全なソース コードは、GitHub にあります。The complete source code for the app is stored on GitHub.

最も簡単に始める方法は、GitHub のページで、緑色の [Clone or download] (複製またはダウンロード) ボタンをクリックし、[Open in Visual Studio] (Visual Studio で開く) を選択することです。The simplest way to get started is to visit GitHub, click on the green Clone or download button, and select Open in Visual Studio.

[Clone or download] (複製またはダウンロード) ボタン

プロジェクトを複製しない場合は、zip ファイルとしてダウンロードすることもできます。If you don't want to clone the project, you can download it as a zip file. ソリューションを Visual Studio に読み込むと、次のようなファイルが表示されます。Once the solution has been loaded into Visual Studio, you'll see several files, including:

  • Images/ - UWP アプリに必要なさまざまなアイコンが含まれるフォルダー。Images/ - a folder containing the various icons required by UWP apps.
  • css/ - 使用する CSS が含まれるフォルダー。css/ - a folder containing the CSS to be used.
  • js/ - JavaScript ファイルが含まれるフォルダー。js/ - a folder containing the JavaScript files. main.js ファイルはゲームで、他のファイルはサード パーティ製ライブラリです。The main.js file is our game while the other files are the third-party libraries.
  • models/ - 3D モデルが含まれるフォルダー。models/ - a folder containing the 3D models. このゲームでは、恐竜を 1 匹のみ使用します。For this game, we only have one for the dinosaur.
  • Index.html - ゲームのレンダラーをホストする Web ページです。index.html - the webpage that hosts the game's renderer.

これでゲームを実行できます。Now you can run the game!

F5 キーを押してアプリを起動します。Press F5 to start the app. ウィンドウが開き、画面上をクリックするよう求められます。You should see a window open, prompting you to click on the screen. また、背景で動き回る恐竜も見えます。You’ll also see a dinosaur moving around in the background. ゲームを閉じて、アプリと主要なコンポーネントを調べましょう。Go ahead and close out of the game and we’ll begin examining the app and its key components.

注意

うまくいかない場合は、Something go wrong? Web サポートを含めて Visual Studio がインストールされていることを確認してください。Be sure you have installed Visual Studio with web support. これは、新しいプロジェクトを作成することで確認できます。JavaScript のサポートが含まれていない場合は、[Microsoft Web Developer Tools] ボックスをオンにして Visual Studio を再インストールする必要があります。You can check by creating a new project - if there is no support for JavaScript, you will need to re-install Visual Studio and check the Microsoft Web Developer Tools box.

チュートリアルWalkthrough

このゲームを開始すると、画面上をクリックするよう求めるメッセージが表示されます。When you start up this game, you’ll see a prompt to click on the screen. マウスで位置を探すことができるように、Pointer Lock API が使用されています。The Pointer Lock API is used to allow you to look around with your mouse. 移動は、W キー、A キー、S キー、D キー、方向キーを押すことで操作できます。Moving is done by pressing the W, A, S, D/arrow keys. このゲームの目的は、恐竜から常に離れていることです。The goal of this game is to stay away from the dinosaur. 恐竜は、十分近くなると、圏外に出るか近付きすぎてゲームに負けるまで、プレイヤーを追いかけ始めます。Once the dinosaur is close enough to you, it’ll start chasing you until you either get out of range or get too close and lose the game.

1. 初期の HTML ファイルの設定1. Setting up your initial HTML file

最初に、小さな HTML を index.html 内に追加します。Within index.html, you'll need to add a little HTML to get started. このファイルは、アプリが含まれる既定の Web ページです。This file is the default web page that contains our app.

ここでは、使用するライブラリと、グラフィックスの表示先として使用する div (名前: container) でセットアップしますRight now, we'll set it up with the libraries we'll be using and the div (named container) that we'll use to render our graphics to. また、main.js (ゲーム コード) がポイントされるように指定しておきます。We'll also set it to point to our main.js (our game code).

<!DOCTYPE html>
<html lang='en'>

<head>
    <link rel="stylesheet" type="text/css" href="css/stylesheet.css" />
</head>

    <body>
        <div id='container'></div>
        <script src='js/libs/three.js'></script>
        <script src="js/controls/PointerLockControls.js"></script>
        <script src="js/main.js"></script>
    </body>

</html>

スターター HTML を準備できたので、main.js に移動し、いくつかのグラフィックスを作成しましょう。Now that we have our starter HTML ready to go, let's head over to main.js and make some graphics!

2. シーンを作成します。2. Creating your scene

このセクションでは、ゲームの基盤を追加します。In the section of the walkthrough we're going to adding the foundation of the game.

それでは、scene の肉付けから始めましょう。We'll start off by fleshing out a scene. three.jsscene は、カメラ、オブジェクト、光源を追加する場所です。A scene in three.js is where your camera, objects, and lights will be added. カメラが認識したものをシーンに反映して表示するためのレンダラーも必要です。You'll also need a renderer which will take what your camera sees in the scene and display it.

これらはすべて、main.js 内の init() という関数で行います。ここでは他の関数も呼び出されます。In main.js we'll make a function that does all of this called init() which calls on some additional functions:

var UNITWIDTH = 90; // Width of a cubes in the maze
var UNITHEIGHT = 45; // Height of the cubes in the maze

var camera, scene, renderer;

init();
animate();

function init() {
    // Create the scene where everything will go
    scene = new THREE.Scene();

    // Add some fog for effects
    scene.fog = new THREE.FogExp2(0xcccccc, 0.0015);

    // Set render settings
    renderer = new THREE.WebGLRenderer();
    renderer.setClearColor(scene.fog.color);
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);

    // Get the HTML container and connect renderer to it
    var container = document.getElementById('container');
    container.appendChild(renderer.domElement);

    // Set camera position and view details
    camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 2000);
    camera.position.y = 20; // Height the camera will be looking from
    camera.position.x = 0;
    camera.position.z = 0;

    // Add the camera
    scene.add(camera);

    // Add the walls(cubes) of the maze
    createMazeCubes();

    // Add lights to the scene
    addLights();

    // Listen for if the window changes sizes and adjust
    window.addEventListener('resize', onWindowResize, false);
}

他の関数として、以下を作成する必要があります。The other functions we'll need to create include:

  • createMazeCubes()
  • addLights()
  • onWindowResize()
  • animate() / render()
  • 単位変換関数Unit conversion functions

createMazeCubes()createMazeCubes()

createMazeCubes() 関数は、単純な立方体をシーンに追加します。The createMazeCubes() function will add a simple cube to our scene. この関数では、後で多数の立方体を追加して迷路を作成します。Later on we'll make the funcion add many cubes to make our maze.

function createMazeCubes() {

  // Make the shape of the cube that is UNITWIDTH wide/deep, and UNITHEIGHT tall
  var cubeGeo = new THREE.BoxGeometry(UNITWIDTH, UNITHEIGHT, UNITWIDTH);
  // Make the material of the cube and set it to blue
  var cubeMat = new THREE.MeshPhongMaterial({
    color: 0x81cfe0,
  });
  
  // Combine the geometry and material to make the cube
  var cube = new THREE.Mesh(cubeGeo, cubeMat);

  // Add the cube to the scene
  scene.add(cube);

  // Update the cube's position
  cube.position.y = UNITHEIGHT / 2;
  cube.position.x = 0;
  cube.position.z = -100;
  // rotate the cube by 30 degrees
  cube.rotation.y = degreesToRadians(30);
}

addLights()addLights()

addLights() 関数は、光源の作成をグループ化してシーンに追加する単純な関数です。The addLights() function is a simple function that groups the creation of our lights and adds them to the scene.

function addLights() {
  var lightOne = new THREE.DirectionalLight(0xffffff);
  lightOne.position.set(1, 1, 1);
  scene.add(lightOne);

  // Add a second light with half the intensity
  var lightTwo = new THREE.DirectionalLight(0xffffff, .5);
  lightTwo.position.set(1, -1, -1);
  scene.add(lightTwo);
}

onWindowResize()onWindowResize()

onWindowResize 関数は、resize イベントの発生をイベント リスナーが認識するたびに呼び出されます。The onWindowResize function is called whenever our event listener hears that a resize event was fired. このイベントは、ユーザーがウィンドウのサイズを調整するたびに発生します。This happens whenever the user adjusts the size of the window. このとき、画像の縦横比が維持され、ウィンドウ全体に表示できることを確認する必要があります。If this happens, we want to make sure tha the image stays proportional and can be seen in the entire window.

function onWindowResize() {

  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();

  renderer.setSize(window.innerWidth, window.innerHeight);
}

animate()animate()

最後に必要になるのが animate() 関数です。この関数からは、render() 関数も呼び出されます。The last thing we'll need is our animate() function, which will also call the render() function. レンダラーを定期的に更新するためには、requestAnimationFrame() 関数を使用します。The requestAnimationFrame() function is used to constantly update our renderer. 後で、これらの関数を使ってレンダラーを更新し、迷路内の移動などのアニメーションを追加します。Later on, we'll use these functions to update our renderer with cool animations like moving around the maze.

function animate() {
    render();
    // Keep updating the renderer
    requestAnimationFrame(animate);
}

function render() {
    renderer.render(scene, camera);
}

単位変換関数Unit conversion functions

three.js では、回転の単位としてラジアンが使用されています。In three.js rotations are measured in radians. このため、度とラジアンの変換を簡単に処理できるように、必要な関数を追加します。To make things easy for us, we'll go ahead and add some functions so that we can easily convert between degrees and radians.

function degreesToRadians(degrees) {
  return degrees * Math.PI / 180;
}

function radiansToDegrees(radians) {
  return radians * 180 / Math.PI;
}

30 度が 0.523 ラジアンであることは覚えにくいので、Who would remember that 30 degrees is .523 radians? 代わりに degreesToRadians(30) を実行して回転角度を取得する方が簡単です。これを createMazeCubes() 関数内で使用します。It's much simpler to instead do degreesToRadians(30) to get our rotation amount which is used in our createMazeCubes() function.


さまざまなコードを使いましたが、これできちんと立方体を container に表示できました。That was quite a bit of code to take in, but we now have a beautiful cube the is rendered to our container! 結果を CodePen で確認してください。Check out the results in the CodePen.

問題が発生した場合や、光源の調整または色の変更を行う場合は、この CodePen で提供されている JavaScript をすべてコピーして貼り付けることで、対処できます。You can copy and paste all the JavaScript in this CodePen to get caught up if you encountered issues, or edit it to adjust some lights and change some colors.

3.迷路の作成3. Making the maze

立方体を表示できたので、次は立方体でできた迷路全体を表示してみましょう。While staring at a cube is breathtaking, what’s even better is a whole maze made out of cubes! ゲーム コミュニティではよく知られた手法ですが、レベルを最短時間で作成する手法の 1 つは、2D 配列によって立方体を全体に配置することです。It’s a pretty well-known secret in the games community that one of the quickest ways to creating a level is by placing cubes all over with a 2D array.

2D 配列で作成された迷路

立方体の場所に 1 を配置して空白の場所に 0 を配置すると、迷路を手動で簡単に作成または調整できます。Placing 1’s where cubes are and 0’s where empty space is allows for a manual and simple way for you to create/tweak the maze.

これには、元の createMazeCubes() 関数に、複数の立方体を作成および配置するための入れ子になったループを追加します。We achieve this by replacing our old createMazeCubes() function with one that uses a nested loop to create and place multiple cubes. また、collidableObjects という名前の配列を作成して、立方体を追加します。これは、後で衝突を検出するために使用します。We'll also create an array name collidableObjects and add the cubes to it for collision detection later in this tutorial:

var totalCubesWide; // How many cubes wide the maze will be
var collidableObjects = []; // An array of collidable objects used later

function createMazeCubes() {
  // Maze wall mapping, assuming even square
  // 1's are cubes, 0's are empty space
  var map = [
    [0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, ],
    [0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, ],
    [0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, ],
    [0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, ],
    [0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, ],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, ],
    [1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, ],
    [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, ],
    [0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, ],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, ],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, ],
    [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, ],
    [0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, ],
    [1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, ],
    [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ],
    [1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, ],
    [0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, ],
    [0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, ]
  ];

  // wall details
  var cubeGeo = new THREE.BoxGeometry(UNITWIDTH, UNITHEIGHT, UNITWIDTH);
  var cubeMat = new THREE.MeshPhongMaterial({
    color: 0x81cfe0,
  });

  // Keep cubes within boundry walls
  var widthOffset = UNITWIDTH / 2;
  // Put the bottom of the cube at y = 0
  var heightOffset = UNITHEIGHT / 2;
  
  // See how wide the map is by seeing how long the first array is
  totalCubesWide = map[0].length;

  // Place walls where 1`s are
  for (var i = 0; i < totalCubesWide; i++) {
    for (var j = 0; j < map[i].length; j++) {
      // If a 1 is found, add a cube at the corresponding position
      if (map[i][j]) {
        // Make the cube
        var cube = new THREE.Mesh(cubeGeo, cubeMat);
        // Set the cube position
        cube.position.z = (i - totalCubesWide / 2) * UNITWIDTH + widthOffset;
        cube.position.y = heightOffset;
        cube.position.x = (j - totalCubesWide / 2) * UNITWIDTH + widthOffset;
        // Add the cube
        scene.add(cube);
        // Used later for collision detection
        collidableObjects.push(cube);
      }
    }
  }
    // The size of the maze will be how many cubes wide the array is * the width of a cube
    mapSize = totalCubesWide * UNITWIDTH;
}

使用する立方体の数 (およびその大きさ) が決まったため、計算された mapSize 変数を使用して、グループ化された平面の寸法を設定できます。Now that we know how many cubes are being used (and how big they are), we can now use the calculated mapSize variable to set the dimensions of the ground plane:

var mapSize;    // The width/depth of the maze

function createGround() {
    // Create ground geometry and material
    var groundGeo = new THREE.PlaneGeometry(mapSize, mapSize);
    var groundMat = new THREE.MeshPhongMaterial({ color: 0xA0522D, side: THREE.DoubleSide});

    var ground = new THREE.Mesh(groundGeo, groundMat);
    ground.position.set(0, 1, 0);
    // Rotate the place to ground level
    ground.rotation.x = degreesToRadians(90);
    scene.add(ground);
}

迷路に追加する最後の部分は、すべてを囲む外周の壁です。The last piece of the maze we'll add is perimeter walls to box everything in. ループを使用して、一度に 2 つの平面 (壁) を作成します。幅を決定するには、createGround() で計算した mapSize 変数を使用します。We'll use a loop to make two planes (our walls) at a time, using the mapSize variable we calculated in createGround() to determine how wide they should be. 新しい壁は、衝突の検出用に collidableObjects 配列にも追加されます。The new walls will also be added to our collidableObjects array for future collision detection:

function createPerimWalls() {
    var halfMap = mapSize / 2;  // Half the size of the map
    var sign = 1;               // Used to make an amount positive or negative

    // Loop through twice, making two perimeter walls at a time
    for (var i = 0; i < 2; i++) {
        var perimGeo = new THREE.PlaneGeometry(mapSize, UNITHEIGHT);
        // Make the material double sided
        var perimMat = new THREE.MeshPhongMaterial({ color: 0x464646, side: THREE.DoubleSide });
        // Make two walls
        var perimWallLR = new THREE.Mesh(perimGeo, perimMat);
        var perimWallFB = new THREE.Mesh(perimGeo, perimMat);

        // Create left/right wall
        perimWallLR.position.set(halfMap * sign, UNITHEIGHT / 2, 0);
        perimWallLR.rotation.y = degreesToRadians(90);
        scene.add(perimWallLR);
        // Used later for collision detection
        collidableObjects.push(perimWallLR);
        // Create front/back wall
        perimWallFB.position.set(0, UNITHEIGHT / 2, halfMap * sign);
        scene.add(perimWallFB);

        // Used later for collision detection
        collidableObjects.push(perimWallFB);

        sign = -1; // Swap to negative value
    }
}

createGround()createPerimWalls を正しくコンパイルするには、init() 関数内で、createMazeCubes() の後にこれらを呼び出すことを忘れないでください。Don't forget to add a call to createGround() and createPerimWalls after createMazeCubes() in your init() function so that they get compiled!


これで立派な迷路を表示できましたが、カメラが 1 点に固定されているため、特におもしろくありません。We now have a beautiful maze to look at but can't really get a feel for just how cool it is because our camera is stuck in one spot. カメラ コントロールを加えて、ゲームをパワーアップしてみましょう。It's time to kick this game up a notch and add in some camera controls.

CodePen を使うと、立方体の色を変更したり、init() 関数の createGround() をコメントアウトして地面を削除するなど、さまざまなテストを行うことができます。Feel free to test things out in the CodePen like changing the colors of the cubes or removing the ground by commenting out createGround() in the init() function.

4。プレーヤーが、少し調べてみることができます。4. Allowing the player to look around

では、迷路に入り、探検を開始しましょう。Now it’s time to get in that maze and start looking around. これを行うには、PointerLockControls.js ライブラリとカメラを使います。To do this we’ll be using the PointerLockControls.js library and our camera.

PoinerLockControls.js ライブラリでは、マウスを使用して、マウスの移動方向にカメラを回転することで、プレイヤーによる探検を可能にします。The PoinerLockControls.js library uses the mouse to rotate the camera in the direction that the mouse is moved, allowing the player to look around.

まず、index.html ファイルにいくつか新しい要素を追加しましょう。First let's add some new elements to our index.html file:

<div id="blocker">
    <div id="instructions">
    <strong>Click to look!</strong>
    </div>
</div>

<script src="main.js"></script>

また、このセクションの最後には、CodePen 内のすべての CSS も必要になります。You'll also need all the CSS in the CodePen at the bottom of this section. CSS は、stylesheet.css ファイルに貼り付けます。It should be pasted into your stylesheet.css file.

main.js に戻り、新しい変数をいくつか追加します。controls にはコントローラーを格納し、controlsEnabled ではコントローラーの状態を追跡します。blocker には、index.htmlblocker 要素を格納します。Switching back to main.js, add a few new global variables; controls to store our controller, controlsEnabled to keep track of the controller state, and blocker to grab the blocker element in index.html:

var controls;
var controlsEnabled = false;

// HTML elements to be changed
var blocker = document.getElementById('blocker');

これで、init() 関数内で新しい PoinerLockControls オブジェクトを作成し、これに camera, を渡して、camera を追加します (controls.getObject() でアクセスします)。Now in our init() function we can make a new PoinerLockControls object, pass it our camera, and add the camera (accessed with controls.getObject()).

controls = new THREE.PointerLockControls(camera);
scene.add(controls.getObject());

これでカメラが接続されましたが、探検のためには、マウスとコントローラーの間でのやり取りが必要です。The camera is now connected, but we need to somehow let the mouse and controller interact so that we can look around.

このような場合は、マウスの動きとカメラを連動させる Pointer Lock API が役立ちます。For this situation, the Pointer Lock API comes to the rescue by letting us connect mouse movements to our camera. Pointer Lock API では、よりイマーシブなエクスペリエンスを提供するために、マウス カーソルを非表示にすることもできます。The Pointer Lock API also makes the mouse disappear for a more immersive experience. Esc キーを押すと、マウスからカメラへの接続が終了し、マウス カーソルが再び現れます。By pressing ESC we end the mouse to camera connection and make the mouse reappear. これには、getPointerLock() 関数と lockChange() 関数を追加します。Additions of the getPointerLock() and lockChange() functions will help us do just that.

getPointerLock() 関数は、マウス クリックの発生をリッスンします。The getPointerLock() function listens for when a mouse click happens. クリックの後、レンダリングされたゲームでは、(container 要素内で) マウス コントロールの取得が試行されます。After the click, our rendered game (in the container element) tries to get control of the mouse. コードに、イベント リスナーも追加します。これにより、プレイヤーによるロックの有効化および無効化を検出して lockChange() を呼び出します。We also add an event listener to detect when the player activates or deactivates the lock which then calls lockChange().

function getPointerLock() {
  document.onclick = function () {
    container.requestPointerLock();
  }
  document.addEventListener('pointerlockchange', lockChange, false); 
}

lockChange() 関数では、コントロールと blocker 要素を無効化または有効化する必要があります。Our lockChange() function needs to either disable or enable the controls and blocker element. ポインター ロックの状態を判断するには、pointerLockElement プロパティでマウス イベントのターゲットが container に設定されているかどうかを確認します。We can determine the state of the pointer lock by checking if the pointerLockElement property's target for mouse events is set to our container.

function lockChange() {
    // Turn on controls
    if (document.pointerLockElement === container) {
        // Hide blocker and instructions
        blocker.style.display = "none";
        controls.enabled = true;
    // Turn off the controls
    } else {
      // Display the blocker and instruction
        blocker.style.display = "";
        controls.enabled = false;
    }
}

次に、init() 関数のすぐ前に、getPointerLock() の呼び出しを追加します。Now we can add a call to getPointerLock() just before our init() function.

// Get the pointer lock state
getPointerLock();
init();
animate();

これで、周囲を見回すことができますが、実際には動き回る機能が欲しいところです。At this point we now have the ability to look around, but the real 'wow' factor is being able to move around. ベクトルなど、少し数学的な説明になりますが、3D グラフィックスの動作には数学が不可欠です。Things are about to get a little mathematical with vectors, but what's 3D graphics without a bit of math?

5。プレイヤーの動きを追加します。5. Adding player movement

プレイヤーに動きを提供する方法を理解するには、数学の勉強に戻る必要があります。To dig into how to get our player moving, we've got to think back to our calculus days. 特定のベクトル (direction) に沿って、速度 (movement) を camera に適用します。We want to apply velocity (movement) to the camera along a certain vector (direction).

それでは、プレイヤーの移動方向を追跡するためのグローバル変数をいくつか追加し、初期速度ベクトルを設定しましょう。Let's add a few more global variables to keep track of which direction the player is moving, and set an initial velocity vector:

// Flags to determine which direction the player is moving
var moveForward = false;
var moveBackward = false;
var moveLeft = false;
var moveRight = false;

// Velocity vector for the player
var playerVelocity = new THREE.Vector3();

// How fast the player will move
var PLAYERSPEED = 800.0;

var clock;

init() 関数の先頭で、clock を新しい Clock オブジェクトに設定します。In the beginning of the init() function, set clock to a new Clock object. これは、新しいフレームのレンダリングにかかる時間の経過 (delta) を追跡するために使用します。We'll be using this to keep track of the change in time (delta) it takes to render new frames. また、ユーザー入力を収集する listenForPlayerMovement() の呼び出しも追加します。You'll also need to add a call to listenForPlayerMovement(), which gathers user input.

clock = new THREE.Clock();
listenForPlayerMovement();

listenForPlayerMovement() 関数では、方向の状態の切り替えが行われます。Our listenForPlayerMovement() function is what will be flipping our direction states. この関数の終わり近くには、キーが押されるか離されるのを待機する 2 つのイベント リスナーがあります。At the bottom of the function we have two event listeners that are waiting for keys to be pressed and released. どちらかのイベントが発生した場合は、動きをトリガーするキーか、動きを停止させるキーかを確認します。Once one of these events are fired, we'll then check to see if it's a key that we want to trigger or stop movement.

このゲームは、プレイヤーが W キー、A キー、S キー、D キー、または方向キーで動き回ることができるとセットアップしてあります。For this game, we've set it up so that the player can move around with the W, A, S, D keys or the arrow keys.

function listenForPlayerMovement() {
    
    // A key has been pressed
    var onKeyDown = function(event) {

    switch (event.keyCode) {

      case 38: // up
      case 87: // w
        moveForward = true;
        break;

      case 37: // left
      case 65: // a
        moveLeft = true;
        break;

      case 40: // down
      case 83: // s
        moveBackward = true;
        break;

      case 39: // right
      case 68: // d
        moveRight = true;
        break;
    }
  };

  // A key has been released
    var onKeyUp = function(event) {

    switch (event.keyCode) {

      case 38: // up
      case 87: // w
        moveForward = false;
        break;

      case 37: // left
      case 65: // a
        moveLeft = false;
        break;

      case 40: // down
      case 83: // s
        moveBackward = false;
        break;

      case 39: // right
      case 68: // d
        moveRight = false;
        break;
    }
  };

  // Add event listeners for when movement keys are pressed and released
  document.addEventListener('keydown', onKeyDown, false);
  document.addEventListener('keyup', onKeyUp, false);
}

これで、ユーザーが進もうとしている方向 (いずれかのグローバル方向フラグに true として格納されている) を判断できるため、少しアクションを加えてみましょう。Now that we're able to determine which direction the user wants to go (which is now stored as true in one of the global direction flags), it's time for some action. アクションは、animatePlayer() 関数の形式で発生します。This action happens to come in the form of the animatePlayer() function.

この関数は、animate() 内で呼び出します。このとき、フレームレートが変化しても動作がずれないように、delta によってフレーム間の時間の経過を取得します。This function will be called from within animate(), passing in delta to get the change in time between frames so that our movement doesn't seem out of sync during changes in framerate:

function animate() {
  render();
  requestAnimationFrame(animate);
  // Get the change in time between frames
  var delta = clock.getDelta();
  animatePlayer(delta);
}

では、楽しい部分に移りましょう。Now it's time for the fun part! 推進力を決定するベクトル (playerVeloctiy) には、3 つのパラメーター (x, y, z) があり、y が垂直方向の推進力になります。Our momentum vector (playerVeloctiy) has three parameters, (x, y, z), with y being the vertical momentum. このゲームにはジャンプ動作が含まれないため、処理するのは x パラメーターと z パラメーターのみです。Since we aren't doing any jumping in this game, we'll only be working with the x and z parameters. このベクトルは、最初は (0, 0, 0) に設定されています。Initially this vector is set to (0, 0, 0).

以下のコードに示されているように、どの方向フラグが true になっているかを確認するために、一連のチェックが行われます。As seen in the code below, a series of checks are done to see which direction flag is flipped to true. 方向を取得したら、x および y に値を加算または減算して、その方向に推進力を適用します。Once we have the direction, we add or subtract from x and y to apply momentum in that direction. 移動キーが押されていない場合は、ベクトルの設定が (0, 0, 0) に戻ります。If no movement keys are being pressed, the vector will be set back to (0, 0, 0).


function animatePlayer(delta) {
  // Gradual slowdown
  playerVelocity.x -= playerVelocity.x * 10.0 * delta;
  playerVelocity.z -= playerVelocity.z * 10.0 * delta;

  if (moveForward) {
    playerVelocity.z -= PLAYERSPEED * delta;
  } 
  if (moveBackward) {
    playerVelocity.z += PLAYERSPEED * delta;
  } 
  if (moveLeft) {
    playerVelocity.x -= PLAYERSPEED * delta;
  } 
  if (moveRight) {
    playerVelocity.x += PLAYERSPEED * delta;
  }
  if( !( moveForward || moveBackward || moveLeft ||moveRight)) {
    // No movement key being pressed. Stop movememnt
    playerVelocity.x = 0;
    playerVelocity.z = 0;
  }
  controls.getObject().translateX(playerVelocity.x * delta);
  controls.getObject().translateZ(playerVelocity.z * delta);
}

最後に、更新された xy の値を、実際にプレイヤーを移動させる値に変換して、カメラに適用します。In the end, we apply the whatever the updated x and y values are to the camera as translations to make the player actually move.


これで終了です。Congratulations! プレイヤーによって制御されるカメラで、自由に見回し、動き回ることができます。You now have a player controlled camera that can move and look around. まだ、壁に入り込むことができますが、これについては後で考えましょう。We still slip right through walls, but that's something to worry about later. 次は、恐竜を追加します。Next we'll add our dinosaur.

注意

UWP アプリでこれらのコントロールを使用する場合、動きの遅延や keyUp イベントの未登録エラーが発生することがあります。If you use these controls in your UWP app you may experience movement lag and unregistered keyUp events. この問題については調査中であり、サンプルの該当部分も間もなく修正される予定です。We're looking into this and hope to fix this portion of the sample soon!

6。その dino を読み込みます。6. Load that dino!

このプロジェクト リポジトリを複製またはダウンロードした場合は、models フォルダー内に dino.json があります。If you cloned or downloaded this projects repo, you'll see a models folder with dino.json inside. この JSON ファイルは、Blender で作成され、エクスポートされた 3D の恐竜です。This JSON file is a 3D dinosaur model that was made and exported from Blender.

この恐竜を読み込むには、さらにグローバル変数を追加する必要があります。We'll have to add more global variables to get this dino loaded up:

var DINOSCALE = 20;  // How big our dino is scaled to

var clock;
var dino;
var loader = new THREE.JSONLoader();

var instructions = document.getElementById('instructions');

作成した JSONLoader には、dino.json のパスと、ファイルから収集した geometry および materials が指定されたコールバックを渡します。Now that we have our JSONLoader created, we'll pass in the path to our dino.json and a callback with the geometry and materials gathered from the file. 恐竜の読み込みは非同期タスクです。つまり、恐竜が完全に読み込まれるまで、レンダリングは一切行われません。Loading the dino is an asynchronous task, meaning nothing will render until the dino is completely loaded. index.htmlinstructions 要素内の文字列は、処理中であることをプレイヤーに伝えるために、"Loading..." に変更してあります。In our index.html we changed the string in the instructions element to "Loading..." to let the player know things are in progress.

以下のように、恐竜が読み込まれた後、instructions 要素の内容をゲームの実際の手順に更新し、animate() 関数を init() の末尾から関数コールバックの末尾に移動します。After the dino is loaded, update the instructions element with the actual instructions of the game, and move the animate() function from the end of init() to the end of the function callback seen below:

   // load the dino JSON model and start animating once complete
    loader.load('./models/dino.json', function (geometry, materials) {


        // Get the geometry and materials from the JSON
        var dinoObject = new THREE.Mesh(geometry, new THREE.MultiMaterial(materials));

        // Scale the size of the dino
        dinoObject.scale.set(DINOSCALE, DINOSCALE, DINOSCALE);
        dinoObject.rotation.y = degreesToRadians(-90);
        dinoObject.position.set(30, 0, -400);
        dinoObject.name = "dino";
        scene.add(dinoObject);
        
        // Store the dino
        dino = scene.getObjectByName("dino"); 

        // Model is loaded, switch from "Loading..." to instruction text
        instructions.innerHTML = "<strong>Click to Play!</strong> </br></br> W,A,S,D or arrow keys = move </br>Mouse = look around";

        // Call the animate function so that animation begins after the model is loaded
        animate();
    });

これで、恐竜モデルが読み込まれました。We now have our dino model loaded in. ご確認ください。Check it out!

7.その dino を移動できます。7. Move that dino!

ゲーム用に AI を作成するときわめて複雑になる可能性があるため、この例の恐竜には単純な動作を適用します。Creating AI for a game can get extremely complex, so for this example we'll make this dino have a simple movement behavior. 今のところ、恐竜はまっすぐ移動し、そのまま壁を突き抜けていきます。Our dino will move straight, slipping its way through walls and off into the distant fog.

では、まずグローバル変数 dinoVelocity を追加します。To do this, first add the global variable dinoVelocity.

var DINOSPEED = 400.0;

var dinoVelocity = new THREE.Vector3();

次に、animation() 関数から animateDino() 関数を呼び出し、以下のコードを追加します。Next, call the animateDino() function from the animation() function and add in the below code:

function animateDino(delta) {
    // Gradual slowdown
    dinoVelocity.x -= dinoVelocity.x * 10.0 * delta;
    dinoVelocity.z -= dinoVelocity.z * 10.0 * delta;

    dinoVelocity.z += DINOSPEED * delta;
    // Move the dino
    dino.translateZ(dinoVelocity.z * delta);
}

このままでは恐竜が消えるのを見守るだけですが、衝突検出機能を追加すると、もう少しおもしろくなります。Watching the dino sail away isn't very fun, but once we add collision detection things will get more interesting.

8.プレーヤーの衝突の検出8. Collision detection for the player

これで、プレイヤーと恐竜が動き回るようになりましたが、全員が壁をつきぬけてしまうという問題が残っています。So we now have the player and the dino moving around, but there's still that annoying issue with everyone going through walls. このチュートリアルで初めて立方体や壁の追加を始めたときは、collidableObjects 配列に配置しました。When we first started adding our cubes and walls earlier in this tutorial, we pushed them into the collidableObjects array. プレイヤーが何かに近すぎて通ることができない状態を判断するには、この配列を使用します。This array is what we'll be using to tell if a player is too close to something they can't walk through.

交差が発生するタイミングを判断するには、Raycaster を使用します。We'll be using raycasters to determine when an intersection is about to occur. Raycaster は、カメラから指定の角度で出ているレーザー ビームのようなものと考えることができます。Raycaster では、物に当たったかどうかという情報と正確な距離が返されます。You could imagine a raycaster as a laser beam coming out of the camera at some specified direction, reporting back if it hit an object and exactly how far away it is.

var PLAYERCOLLISIONDISTANCE = 20;

新しい関数として detectPlayerCollision() を作成しますが、この関数では、プレイヤーがオブジェクトに当たるほど近い場合は true が返されます。We'll be making a new function called detectPlayerCollision() that will return true if the player is too close to a collidable object. プレイヤー用には 1 つの Raycaster を適用し、プレイヤーの進行方向に応じて方向を変更します。For the player, we're going to apply one raycaster to it, changing which direction it's pointing depending on which direction they're going.

これを行うには、未定義のマトリックス rotationMatrix を作成します。To do this, we create rotationMatrix, an undefined matrix. 進行方向を確認し、定義された rotationMatrix を使用するか、前進する場合は未定義のマトリックスを使用します。As we check which direction we're going, we'll end up with either a defined rotationMatrix, or undefined if you're moving forward. 定義されている場合、コントロールの方向に rotationMatrix が適用されます。If defined, the rotationMatrix will be applied to the direction of the controls.

次に、カメラから cameraDirection 方向へ向かう Raycaster が作成されます。A raycaster will then be created, starting from and camera and reaching out in the cameraDirection direction.

function detectPlayerCollision() {
    // The rotation matrix to apply to our direction vector
    // Undefined by default to indicate ray should coming from front
    var rotationMatrix;
    // Get direction of camera
    var cameraDirection = controls.getDirection(new THREE.Vector3(0, 0, 0)).clone();

    // Check which direction we're moving (not looking)
    // Flip matrix to that direction so that we can reposition the ray
    if (moveBackward) {
        rotationMatrix = new THREE.Matrix4();
        rotationMatrix.makeRotationY(degreesToRadians(180));
    }
    else if (moveLeft) {
        rotationMatrix = new THREE.Matrix4();
        rotationMatrix.makeRotationY(degreesToRadians(90));
    }
    else if (moveRight) {
        rotationMatrix = new THREE.Matrix4();
        rotationMatrix.makeRotationY(degreesToRadians(270));
    }

    // Player is not moving forward, apply rotation matrix needed
    if (rotationMatrix !== undefined) {
        cameraDirection.applyMatrix4(rotationMatrix);
    }

    // Apply ray to player camera
    var rayCaster = new THREE.Raycaster(controls.getObject().position, cameraDirection);

    // If our ray hit a collidable object, return true
    if (rayIntersect(rayCaster, PLAYERCOLLISIONDISTANCE)) {
        return true;
    } else {
        return false;
    }
}

detectPlayerCollision() 関数は、rayIntersect() ヘルパー関数に依存しています。Our detectPlayerCollision() function relies on the rayIntersect() helper function. 衝突が発生したと判断するには、Raycaster のほか、collidableObjects 配列に格納されているオブジェクトにどの程度近付くことができるかを表した値が必要です。This takes a raycaster and value representing how close we can get to an object in the collidableObjects array before determining a collision has occured.

function rayIntersect(ray, distance) {
    var intersects = ray.intersectObjects(collidableObjects);
    for (var i = 0; i < intersects.length; i++) {
        // Check if there's a collision
        if (intersects[i].distance < distance) {
            return true;
        }
    }
    return false;
}

これで、衝突がいつ発生するかを判断できるようになったため、animatePlayer() 関数を仕上げることができます。Now that we can determine when a collision is about to occur, we can spruce up our animatePlayer() function:

function animatePlayer(delta) {
    // Gradual slowdown
    playerVelocity.x -= playerVelocity.x * 10.0 * delta;
    playerVelocity.z -= playerVelocity.z * 10.0 * delta;

    // If no collision and a movement key is being pressed, apply movement velocity
    if (detectPlayerCollision() == false) {
        if (moveForward) {
            playerVelocity.z -= PLAYERSPEED * delta;
        }
        if (moveBackward) {
            playerVelocity.z += PLAYERSPEED * delta;
        } 
        if (moveLeft) {
            playerVelocity.x -= PLAYERSPEED * delta;
        }
        if (moveRight) {
            playerVelocity.x += PLAYERSPEED * delta;
        }

        controls.getObject().translateX(playerVelocity.x * delta);
        controls.getObject().translateZ(playerVelocity.z * delta);
    } else {
        // Collision or no movement key being pressed. Stop movememnt
        playerVelocity.x = 0;
        playerVelocity.z = 0;
    }
}

プレイヤーの衝突も検出できるので、何か所かの壁に突き当たってみてください。We now have player collision detection, so go ahead and try to run into some walls!

9.衝突検出と dino のアニメーション9. Collision detection and animation for dino

ここで、恐竜が壁を突き抜ける動作を修正し、衝突可能なオブジェクトに近付き過ぎた場合はランダムな方向に進むようにしましょう。It's time to stop our dino from moving through walls, and instead have it go a random direction once it's too close to a collidable object.

まず、恐竜がいつ衝突するかを把握しましょう。First let's figure out when our dino has a collision.

衝突の距離用に、グローバル変数をもう 1 つ設定する必要があります。We'll need to set anothe global variable for the collision distance:

var DINOCOLLISIONDISTANCE = 55;     

恐竜にどの程度近付くと衝突が発生するかを指定したので、detectPlayerCollision() に似ていてもう少し単純な関数を追加しましょう。Now that we've specifed at what distance we want our dino to collide at, let's add a function similar to detectPlayerCollision(), but a bit simpler. detectDinoCollision 関数は、常に恐竜の中央から 1 つの Raycasterがまっすぐ出ているという点で単純です。The detectDinoCollision function is simple in that we always have one raycaster coming straight out the front of the dino. プレイヤーの衝突のように、回転させる必要はありません。No need to rotate it around like for the player collision.

function detectDinoCollision() {
    // Get the rotation matrix from dino
    var matrix = new THREE.Matrix4();
    matrix.extractRotation(dino.matrix);
    // Create direction vector
    var directionFront = new THREE.Vector3(0, 0, 1);

    // Get the vectors coming from the front of the dino
    directionFront.applyMatrix4(matrix);

    // Create raycaster
    var rayCasterF = new THREE.Raycaster(dino.position, directionFront);
    // If we have a front collision, we have to adjust our direction so return true
    if (rayIntersect(rayCasterF, DINOCOLLISIONDISTANCE))
        return true;
    else
        return false;
}

では、衝突検出機能を組み込んだ最終的な animateDino() 関数がどのようなものか、見てみましょう。Let's take a peek at what our final animateDino() function will look like when hooked up with collision detection:

function animateDino(delta) {
    // Gradual slowdown
    dinoVelocity.x -= dinoVelocity.x * 10.0 * delta;
    dinoVelocity.z -= dinoVelocity.z * 10.0 * delta;


    // If no collision, apply movement velocity
    if (detectDinoCollision() == false) {
        dinoVelocity.z += DINOSPEED * delta;
        // Move the dino
        dino.translateZ(dinoVelocity.z * delta);

    } else {
        // Collision. Adjust direction
        var directionMultiples = [-1, 1, 2];
        var randomIndex = getRandomInt(0, 2);
        var randomDirection = degreesToRadians(90 * directionMultiples[randomIndex]);

        dinoVelocity.z += DINOSPEED * delta;
        dino.rotation.y += randomDirection;
    }
}

恐竜の方向転換は、常に -90 度、90 度、180 度のいずれかです。We always want our dino to turn either -90, 90, or 180 degrees. これを単純化するために、上のコードでは directionMultiples 配列を使用し、90 の倍数としてこれらの数値を生成しています。To make this straight forward, above we've made the directionMultiples array which will produce these numbers when multiplied by 90. 回転角度がランダムに選択されるようにするために、ヘルパー関数として getRandomInt() を追加しました。この関数では、配列のランダム インデックスを表す値 0、1、または 2 を取得します。To make selecting the rotation degrees random, we've added the getRandomInt() helper function to grab a value of 0, 1, or 2, which will represent a random index of the array.

function getRandomInt(min, max) {
    min = Math.ceil(min);
    max = Math.floor(max);
    return Math.floor(Math.random() * (max - min)) + min;
}

これらがすべて完了したら、配列のランダム インデックスに 90 を掛けて、回転角度 (ラジアンに変換した値) を取得します。Once this is all done, we multiply the random index of the array by 90 to get the degree (converted to radians) of rotation. この値を恐竜の y 回転に加算することによって (dino.rotation.y += randomDirection;)、恐竜は衝突時にランダムに回転するようになります。By adding this value to the dino's y rotation with dino.rotation.y += randomDirection;, the dino now makes random turns upon collision.


これで、We did it! AI を持った恐竜が、迷路を動き回るようになりました。We now have a dino with AI that can move around our maze!

10.追跡を開始10. Starting the chase

ここで、恐竜がプレイヤーから一定の範囲内に入った場合はプレイヤーに追随するようにセットアップします。Once the dino is within a certain distance to the player, we want it to start chasing them. ここに示すのは単なる例であるため、プレイヤーを追い詰めるための高度なアルゴリズムは適用されていません。Since this is just an example, there aren't any advanced algorithms applied for the dino to track the player down. 代わりに、恐竜はプレイヤーを見て、歩いて追随します。Instead, the dino will look at the player and walk towards them. 迷路内で通ることのできる部分では、これが正しく機能しますが、進行方向に壁があると恐竜はぶつかってしまいます。In an open part of the maze this works great, but the dino does get stuck when a wall is in the way.

これに対処するため、animate() 関数に、triggerChase() から返された値によって決定されるブール変数を追加します。In our animate() function we'll add a boolean variable that is determined by what is returned by triggerChase():

function animate() {
    render();
    requestAnimationFrame(animate);

    // Get the change in time between frames
    var delta = clock.getDelta();

    // If the player is in dino's range, trigger the chase
    var isBeingChased = triggerChase();

    animateDino(delta);
    animatePlayer(delta);
}

triggerChase 関数では、恐竜の追随範囲にプレイヤーがいるかどうかを確認し、恐竜が常にプレイヤーに向くように設定します。これにより、恐竜がプレイヤー方向に移動できます。Our triggerChase function will check to see if the player is in chasing range of the dino, and then make the dino always face the player, which allows it to move in the player's direction.

function triggerChase() {
    // Check if in dino detection range of the player
    if (dino.position.distanceTo(controls.getObject().position) < 300) {
        // Set the dino target's y value to the current y value. We only care about x and z for movement.
        var lookTarget = new THREE.Vector3();
        lookTarget.copy(controls.getObject().position);
        lookTarget.y = dino.position.y;

        // Make dino face camera
        dino.lookAt(lookTarget);

        // Get distance between dino and camera with a unit offset
        // Game over when dino is the value of CATCHOFFSET units away from camera
        var distanceFrom = Math.round(dino.position.distanceTo(controls.getObject().position)) - CATCHOFFSET;
        // Alert and display distance between camera and dino
        dinoAlert.innerHTML = "Dino has spotted you! Distance from you: " + distanceFrom;
        dinoAlert.style.display = '';
        return true;
        // Not in agro range, don't start distance countdown
    } else {
        dinoAlert.style.display = 'none';
        return false;
    }
}

triggerChase の後半では、恐竜からどの程度離れているかをプレイヤーに知らせるテキストの表示処理を行います。The second half of triggerChase deals with displaying some text that lets the player know how far away the dino is. また、CATCHOFFSET を使用して、0 の距離を指定します。We also introduce CATCHOFFSET to specify how far away 0 should be. オフセットがなければ、0 はプレイヤーに重なり、視覚的に印象的な終わり方になりません。If we didn't have the offset, 0 would be right on top of the player which doesn't make for a very cinematic end to all this.

var dinoAlert = document.getElementById('dino-alert');
dinoAlert.style.display = 'none';

今の時点では、気の荒い恐竜はプレイヤーが近くなると追随を始め、プレイヤーの位置になるまで止まりません。At this point we have a wild dinosaur that starts following the player once you get too close, and doesn't stop until it's position is on top of the player. 最後の手順では、恐竜までの距離が CATCHOFFSET 単位になったらゲーム オーバー状態を追加します。The final step is to add some game over conditions once the dino is CATCHOFFSET units away.

11.ゲームの終了11. Ending the game

単純な立方体から長い道のりでしたが、終了の操作に取り掛かりましょう。We've come a long away from a simple cube, and now it's time to end things.

まず、ゲーム オーバーかどうかを追跡するための変数を設定しましょう。Let's first set a variable to keep track of whether the game is over or not:

var gameOver = false;

最後にもう一度だけ animate() 関数を更新して、恐竜がプレイヤーに近付きすぎていないか確認できるようにします。Now we need to update our animate() function one last time to check if the dino is too close to the player. 恐竜が近すぎる場合は、caught() という新しい関数を呼び出して、プレイヤーと恐竜の動作を停止します。そうでない場合は、プレイヤーと恐竜が動き回れる状態で通常の動作を継続します。If the dino is too close, we'll start a new function called caught() and will stop the player and dino from moving, if not, we'll carry on with business as usual and let the player and dino move around.

function animate() {
    render();
    requestAnimationFrame(animate);

    // Get the change in time between frames
    var delta = clock.getDelta();
    // Update our frames per second monitor

    // If the player is in dino's range, trigger the chase
    var isBeingChased = triggerChase();
    // If the player is too close, trigger the end of the game
    if (dino.position.distanceTo(controls.getObject().position) < CATCHOFFSET) {
        caught();
    // Player is at an undetected distance
    // Keep the dino moving and let the player keep moving too
    } else {
        animateDino(delta);
        animatePlayer(delta);
    }
}

プレイヤーが恐竜につかまった場合は、caught() によって blocker 要素が表示され、ゲームに負けたことを示すテキストに更新されます。If the dino catches the player, caught() will display our blocker element and update the text to indicate that the game has been lost. gameOver 変数も、ゲーム オーバーを示す true に設定されます。The gameOver variable is also set to true, which now lets us know the game is over.

function caught() {
    blocker.style.display = '';
    instructions.innerHTML = "GAME OVER </br></br></br> Press ESC to restart";
    gameOver = true;
    instructions.style.display = '';
    dinoAlert.style.display = 'none';
}

ゲーム オーバーかどうかがわかるようになったため、ゲーム オーバーのチェックを lockChange() 関数に追加できます。Now that we know whether the game is over or not, we can add a check for game over to our lockChange() function. ゲームが終了してユーザーによって Esc キーを押されたら、location.reload を追加してゲームを再起動できます。Now when the user presses ESC once the game is over, we can add location.reload to restart the game.

function lockChange() {
    if (document.pointerLockElement === container) {
        blocker.style.display = "none";
        controls.enabled = true;
    } else {
        if (gameOver) {
            location.reload();
        }
        blocker.style.display = "";
        controls.enabled = false;
    }
}

以上で作業は終了です。That's it! 長い道のりでしたが、three.js でゲームを作成できました。It was quite the journey, but we now have a game made with three.js.

最終的な CodePen は、ページの上部から確認できます。Head back up to the top of the page to see the final CodePen!

Microsoft Store への発行Publishing to the Microsoft Store

(これが最初に向上を想定)。 Microsoft Store に発行することは UWP アプリを作成したら、このプロセスにはいくつかの手順が必要になります。Now you have a UWP app, it is possible to publish it to the Microsoft Store (assuming you have improved it first!) There are a few steps to the process.

  1. Windows 開発者として登録する必要があります。You must be registered as a Windows Developer.
  2. アプリの申請チェックリストを使用する必要があります。You must use the app submission checklist.
  3. 認定を受けるために、アプリを提出する必要があります。The app must be submitted for certification. 詳細については、次を参照してください。 UWP アプリの発行します。For more details, see Publishing your UWP app.