これまで線路プレートと目覚まし時計の移動と描画の処理をしてきましたが、今回はゲームとして完成させます。
動作確認は こちらから
Contents
Window: load イベント時の処理
ページがロードされたらMain関数を実行します。ここでやっているのはシーンの作成、光源の設定、レンダラーを作成、線路プレートと目覚まし時計を生成してシーンに追加する処理です。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | let scene: THREE.Scene; let camera: THREE.PerspectiveCamera; let renderer: THREE.WebGLRenderer; const width = 600; const height = 480; let alarmClock1: AlarmClock; // 移動時とゲームオーバー時に音を鳴らす let soundMove: HTMLAudioElement = new Audio('./sounds/move.mp3'); let soundGameover: HTMLAudioElement = new Audio('./sounds/gameover.mp3'); let isGameOver = false; // ゲーム開始時刻と経過時間 let startTime; let passedTime; window.addEventListener('load', Main); function Main() {     document.onkeydown = OnKeyDown;     // シーンを作成     scene = new THREE.Scene();     // カメラを作成     camera = new THREE.PerspectiveCamera(45, width / height, 1, 10000);     camera.position.set(-80, 64, 0);     camera.lookAt(0, 2, 0);     // 平行光源     const light = new THREE.DirectionalLight(0xffffff);     light.intensity = 2; // 光の強さを2倍に     light.position.set(-1, 1, 0.5);     scene.add(light);     // 環境光     const ambientLight = new THREE.AmbientLight(0xffffff, 0.3);     scene.add(ambientLight);     // レンダラーを作成     renderer = new THREE.WebGLRenderer({         canvas: <HTMLCanvasElement>document.getElementById('can')     });     renderer.setPixelRatio(window.devicePixelRatio);     renderer.setSize(width, height);     renderer.setClearColor(0x006000);     // 線路プレートを生成してシーンに追加     let startTrackPlate = InitTrackPlates();     // 目覚まし時計を生成してシーンに追加     AddClock(startTrackPlate);     renderer.render(scene, camera);     setInterval(Update, 1000 / 60);     // ゲーム開始!     GameStart(); } | 
線路プレートを生成してシーンに追加する
線路プレートを生成してシーンに追加する処理を示します。
乱数を生成してシーンに追加する線路プレートを決めます。縦移動だけ、横移動だけというプレートが多くなるとゲームがやりにくいのでNSWE型やNESW型、NWSE型が出現しやすくしています。また余裕をもってプレイを開始できるように、目覚まし時計の初期位置は固定し、最初はある程度線路が繋がっている状態にしておきます。InitTrackPlates関数の戻り値は目覚まし時計の初期位置にある線路プレートです。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | // 0 ~ max-1 の整数を生成する function getRandomInt(max) {     return Math.floor(Math.random() * Math.floor(max)); } function InitTrackPlates(): TrackPlate {     let start = null;     for (let colum = 0; colum < columMax; colum++) {         for (let row = 0; row < rowMax; row++) {             // 最初はある程度線路が繋がっている状態にするため、3箇所はPlateTypeを固定にする             if (colum == 0 && row == 2)                 new TrackPlate(0, 2, PlateType.SE);             else if (colum == 1 && row == 2)                 new TrackPlate(1, 2, PlateType.NWSE);             else if (colum == 1 && row == 3)                 start = new TrackPlate(1, 3, PlateType.NSWE); // 目覚まし時計の初期位置             else if (colum == 3 && row == 1) {                 // 線路プレートが存在しない初期位置                 TrackPlate.BlankColum = colum;                 TrackPlate.BlankRow = row;             }             else {                 // それ以外は乱数で線路プレートを生成する                 let plateType: PlateType = PlateType.NS;                 let r = getRandomInt(12);                 if (r == 0 || r == 1)                     plateType = PlateType.NSWE;                 if (r == 2)                     plateType = PlateType.NS;                 if (r == 3)                     plateType = PlateType.WE;                 if (r == 4 || r == 5)                     plateType = PlateType.NESW;                 if (r == 6)                     plateType = PlateType.NE;                 if (r == 7)                     plateType = PlateType.SW;                 if (r == 8 || r == 9)                     plateType = PlateType.NWSE;                 if (r == 10)                     plateType = PlateType.NW;                 if (r == 11)                     plateType = PlateType.SE;                 // 生成されたオブジェクトはTrackPlateクラスのコンストラクタ内でシーンに追加される                 new TrackPlate(colum, row, plateType);             }         }     }     return start; } | 
目覚まし時計を生成してシーンに追加する
次に目覚まし時計を生成してシーンに追加する処理をする関数を示します。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | function AddClock(startTrackPlate: TrackPlate) {     // モデルデータを読み込む     //@ts-ignore     const loader = new THREE.ColladaLoader();     // 3DデータのCOLLADA(.dae)ファイルのパスを指定     loader.load('./clock.dae', collada => {         let model;         model = collada.scene;         // 線路プレートにあうように大きさとY座標を調整する         model.position.y = 8;         model.scale.x = 5.5;         model.scale.y = 5.5;         model.scale.z = 5.5;         // 最初は南向き         model.rotation.z = Math.PI / 2 * 3;         // AlarmClockクラスのコンストラクタにモデルデータと開始位置を渡す         alarmClock1 = new AlarmClock(model, startTrackPlate);         for (let i = 0; i < model.children.length; i++) {             // 目覚まし時計の文字盤はpngファイルから生成したテクスチャを貼り付ける             if (model.children[i].material != null) {                 if (model.children[i].material.name == "material_clock_face") {                     const loader = new THREE.TextureLoader();                     let texture = loader.load('face.png');                     const material = new THREE.MeshStandardMaterial({ map: texture, });                     model.children[i].material = material;                 }             }         }     }); } | 
更新処理
setInterval関数によってUpdate関数が1秒間に60回呼び出されます。このときにおこなわれる処理を示します。
現在時刻を取得してゲーム開始時に取得しておいた値(後述)と比較して経過時間を求めます。これをスコア代わりに表示させます(後述)。ゲームオーバー時は時間の計測は停止され、目覚まし時計の移動もされません。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | function Update() {     if (!isGameOver) {         alarmClock1.Move();         let now = new Date();         let nowTime = now.getTime();         passedTime = nowTime - startTime; // ゲーム開始からの経過時間を求める(単位はミリ秒)     }     ShowTextIfGameOver(); // ゲームオーバーであれば文字列を表示     ShowTime(); // 経過時間をスコアとして表示     // レンダリング     renderer.render(scene, camera); } | 
ゲーム開始とゲームオーバー時の処理
ゲームスタートのときはスタート時刻を取得し、ゲームオーバーのときはゲームオーバー時の音を鳴らします。またisGameOverフラグをセットして線路プレートの移動操作ができないようにします。
| 1 2 3 4 5 6 7 8 9 10 11 | function GameStart() {     // スタート時刻をリセットする     let now = new Date();     startTime = now.getTime(); } function GameOver() {     isGameOver = true;     soundGameover.currentTime = 0;     soundGameover.play(); } | 
| 1 2 3 4 5 6 7 | // ゲームオーバー時にfunction GameOver()を呼び出せるようにする class AlarmClock {     GameOver() {         this.DirectOfMove = DirectOfMove.None;         GameOver();     } } | 
キーが押されたときの処理
キーが押されたときの処理を示します。ゲームオーバーでなければ線路プレートの移動処理がおこなわれます。実際に移動することができた場合は音を鳴らします。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | function OnKeyDown(e: KeyboardEvent) {     if (e.keyCode == 37 && !isGameOver) { // 左         if (TrackPlate.MoveWest()) {             soundMove.currentTime = 0;             soundMove.play();         }     }     if (e.keyCode == 39 && !isGameOver) { // 上         if (TrackPlate.MoveEast()) {             soundMove.currentTime = 0;             soundMove.play();         }     }     if (e.keyCode == 38 && !isGameOver) { // 右         if (TrackPlate.MoveNorth()) {             soundMove.currentTime = 0;             soundMove.play();         }     }     if (e.keyCode == 40 && !isGameOver) { // 下         if (TrackPlate.MoveSouth()) {             soundMove.currentTime = 0;             soundMove.play();         }     }     if (e.keyCode == 83 && isGameOver) { // Sキー         Retry(); // ゲームオーバー後の再挑戦     } } | 
再挑戦するときの処理
再挑戦するときの処理を示します。線路プレートと目覚まし時計をシーンから取り除き、新しく生成しなおします。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | function Retry() {     // 線路プレートをシーンから取り除く。配列 TrackPlate.Platesもクリアする     TrackPlate.Plates.forEach(plate => scene.remove(plate.Group));     TrackPlate.Plates = [];     // 目覚まし時計をシーンから取り除く     scene.remove(alarmClock1.Model);     // 新しく生成する     let startTrackPlate = InitTrackPlates();     AddClock(startTrackPlate);     // isGameOverフラグをクリアしてゲーム開始     isGameOver = false;     GameStart(); } | 
文字列を表示させる処理
文字列を表示させる処理を示します。
HTMLは以下のようになっています。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <!DOCTYPE html> <html> <head> <title>Three.jsでチクタクバンバンのようなゲームをつくってみた</title> <meta charset="UTF-8" /> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js"></script> <script src="./ColladaLoader.js"></script> </head> <body> <canvas id="can"></canvas> <div id="time" style="position: absolute; top: 0; left: 0;"></div> <div id="gameover" style="position: absolute; top: 0; left: 0;"></div> <div id="retry" style="position: absolute; top: 0; left: 0;"></div> <script src="TrackPlate.js"></script> <script src="AlarmClock.js"></script> <script src="functions.js"></script> <script src="app.js"></script> </body> </html> | 
document.getElementById関数でドキュメント要素を取得して、そこに文字列を描画します。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | function ShowTextIfGameOver() {     // テキストフィールドに表示     const tf1 = document.getElementById("gameover");     if (isGameOver)         tf1.innerHTML = "GAME OVER";     else         tf1.innerHTML = "";     tf1.style.transform = "translate(160px, 180px)";     tf1.style.color = "white";     tf1.style.fontSize = "48px";     const tf2 = document.getElementById("retry");     if (isGameOver)         tf2.innerHTML = "Press S key to Retry";     else         tf2.innerHTML = "";     tf2.style.transform = "translate(150px, 250px)";     tf2.style.color = "white";     tf2.style.fontSize = "32px"; } function ShowTime() {     const tf1 = document.getElementById("time");     let minutes = Math.floor(passedTime / 1000 / 60); // passedTimeはミリ秒     let str = minutes + " 分 " + ((passedTime - 60000 * minutes) / 1000).toFixed(1) + " 秒";     tf1.innerHTML = str;     tf1.style.transform = "translate(40px, 20px)";     tf1.style.color = "#ffffff";     tf1.style.fontSize = "24px"; } | 
動作確認は こちらから
