これまで線路プレートと目覚まし時計の移動と描画の処理をしてきましたが、今回はゲームとして完成させます。
動作確認は こちらから
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"; } |
動作確認は こちらから