アクセルとブレーキは別のキーにしてほしいというリクエストがあったので別バージョンも作成しました。Zキー:アクセル、Xキー:ブレーキです。⇒ 動作確認はこちらから
Contents
問題点の改善
前回まででTypeScript/JavaScriptでもつくる3Dカーレースゲームは一応完成しました。ただ問題点もあるので、それを改善します。
問題点として以下があります。
いきなり音がでる
フレームでどのようなものを作ったのかがわかるようにしているのですが、ロードとすぐにゲームが開始されるため、ライバル車に追突されれば衝突音が発生します。記事を読みたい方にとって頻繁に衝突音がするのはあまりよいとはいえません。基本的にページを読み込んだときに音が自動再生される仕様はよくありません。この点は改善されなければなりません。
読み込みに時間がかかる
コースを描画するためのデータを読み込むときに結構な時間がかかっています。アクセスしてなにも表示されないとせっかくアクセスしてくれたユーザーは離脱してしまいます。この点についても完全の余地があります。
時間が正しく計測されない
Update関数を実行するときに時間がかかる判定処理をしているため時間がかかっているようです。1秒間60フレームのはずなのですが、明らかに1フレームの処理に1/60秒以上の時間がかかっています。タイムとして表示されている時間がじっさいの時間経過と大きくズレているので、この点を改善します。
ゲームとしての完成度を高める
現状では車が走っているだけのブラウザアプリでしかありません。もうちょっとゲームらしいものに改良します。
このようにパッと思いつくだけでもこれだけの問題点があります。では改善策を考えていきましょう。
読み込みに時間がかかる問題を解決する
読み込みに時間がかかっている原因はコースを描画するために必要なデータを読み込んでいる部分と、ここからgeometryを生成してmergeMeshしている部分です。そこで最初は車だけを表示させてゲーム開始と操作方法を表示させます。ゲーム開始ボタンを押してから時間のかかる初期化をおこないます。また「初期化に数秒かかります」と表示させておけばそんなに問題にはならないと思います。
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 |
window.addEventListener('load', Init); // ゲームは開始されているか? let isGameInited = false; // 自車の位置(デモ表示用の車) let car0; function Init() { document.onkeydown = OnKeyDown; document.onkeyup = OnKeyUp; // シーンを作成 scene = new THREE.Scene(); // カメラを作成 camera = new THREE.PerspectiveCamera(45, width / height, 1, 10000); camera.position.set(0, 2, 12); camera.rotateX(-10 / 180 * Math.PI); // 平行光源 const light = new THREE.DirectionalLight(0xffffff); light.intensity = 2; // 光の強さを倍に light.position.set(1, 1, 1); scene.add(light); const light2 = new THREE.AmbientLight(0xFFFFFF, 0.4); scene.add(light2); // レンダラーを作成 renderer = new THREE.WebGLRenderer({ canvas: <HTMLCanvasElement>document.getElementById('can') }); renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(width, height); ShowHowToGameStart(); //@ts-ignore car0 = CreateOrangeCar1(); car0.position.x = 0; car0.position.y = 0; car0.position.z = 3; scene.add(car0); renderer.render(scene, camera); setInterval(UpdateBeforeGameStart, 1000/60); } |
1 2 3 4 5 6 7 8 9 10 11 |
function ShowHowToGameStart() { const tf1 = document.getElementById("info1"); // テキストフィールドに表示 tf1.innerHTML = "↑キー加速 ↓キー減速 ハンドル操作 ← →キー<br>Sキー:ゲーム開始(開始まで数秒かかります)"; tf1.style.transform = "translate(30px, 30px)"; tf1.style.backgroundColor = "#000000"; tf1.style.color = "white"; tf1.style.fontSize = "22px"; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
function UpdateBeforeGameStart() { // ゲームが開始されたらこの関数は呼び出されないようにする // 理由はこの関数がカメラの位置を変更したりcar0を移動させるから if (isGameInited) return; camera.position.set(0, 2, 12); camera.rotation.x = (-10 / 180 * Math.PI); car0.position.x = 0; car0.position.y = 0; car0.position.z = 3; car0.rotation.y += 0.03; // レンダリング renderer.render(scene, camera); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
function OnKeyDown(e: KeyboardEvent) { if (e.keyCode == 37) { isLeftKeyDown = true; } if (e.keyCode == 38) { isUpKeyDown = true; } if (e.keyCode == 39) { isRightKeyDown = true; } if (e.keyCode == 40) { isDownKeyDown = true; } // Sキーが押されたらゲーム開始 // InitGame関数が呼び出されるのは1回だけでよいのでisGameInitedフラグで制御 // またisGameInitedフラグが立っているときはUpdateBeforeGameStart関数は呼び出されない if (e.keyCode == 83 && !isGameInited) { isGameInited = true; InitGame(); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
function InitGame() { // コースを作成 AddSceneCourse(); // 自車の初期化 InitMyCar(); // 自車の初期位置にスタートラインを引く AddStartLine(); // ライバル車の初期化 InitCars(); renderer.render(scene, camera); Update(); // BGMをならす(無条件ではならないように改良の要あり) setInterval(PlayBGM, 10000); soundBgm.currentTime = 0; soundBgm.play(); } |
これで読み込みに時間がかかる問題は解決です。それからアクセスしてもゲーム開始の操作をしなければゲームがはじまらないので、いきなり音がでる問題も解決です。
音あり音なしを選択できる
音あり音なしを選択できるようにします。
チェックボックスで選択できるようにする
HTMLに以下の3行を追加します。別にどこでもいいのですが、canvasの前だとレイアウトが崩れてしまいます。管理人は以下の3行をHTMLの一番最後に追加しました。
1 2 3 |
<form name="form1"> <input type="checkbox" value="音を出す" id="SoundCheckbox">音を出す </form> |
Init関数の最初のほうにイベントリスナーを追加する処理を書いておきます。
1 2 3 4 5 6 7 8 9 |
let soundCheckbox: HTMLElement; function Init() { // 最初のほうに書く soundCheckbox = document.getElementById('SoundCheckbox'); soundCheckbox.addEventListener('change', soundCheckboxChanged); // 後略 } |
BGMを止める
ゲームが開始されたとき、チェックボックス[音を鳴らす]がチェックされているか調べて、チェックされているときだけBGMを鳴らします。
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 |
let intervalId = undefined; function InitGame() { AddSceneCourse(); InitMyCar(); AddStartLine(); InitCars(); renderer.render(scene, camera); Update(); PlayBgms(); } function PlayBgms() { // チェックボックス[音を鳴らす]がチェックされている場合だけ、一定間隔でBGMを鳴らす //@ts-ignore if (soundCheckbox.checked) { intervalId = setInterval(PlayBGM, 10000); PlayBGM(); } } function PlayBGM() { //@ts-ignore if (soundCheckbox.checked) { soundBgm.currentTime = 0; soundBgm.play(); } } |
ゲームが開始されている状態でチェックがつけられた場合、BGMを再生します。チェックが外された場合はBGMを停止します。それからTypeScriptを使う場合、//@ts-ignore をいれておかないと「プロパティ ‘checked’ は型 ‘HTMLElement’ に存在しません」というエラーが出てビルドできません。
1 2 3 4 5 6 7 |
function soundCheckboxChanged() { //@ts-ignore if (soundCheckbox.checked && isGameInited) PlayBgms(); else StopBgm(); } |
ハンドル操作時の効果音を止める
他にも効果音がなる部分があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
一定速度以上でハンドル操作をしたときにタイヤが滑る音をならす処理がありますが、チェックボックス[音を鳴らす]が外されていた場合、音は鳴らしません。 function PlaySoundIfTurning() { if (isLeftKeyDown || isRightKeyDown) { //@ts-ignore if (soundCheckbox.checked && realSpeedH > 100 && !isSoundturn && remainingNumberUntilResurrection == 0) { isSoundturn = true; soundturn.currentTime = 0; soundturn.play(); } } if (!isLeftKeyDown && !isRightKeyDown) { soundturn.pause(); isSoundturn = false; } } |
クラッシュ時の衝突音を止める
それからクラッシュしたときも衝突音が出るので、チェックボックス[音を鳴らす]が外されていた場合、衝突音が鳴らないようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
function Crash() { //@ts-ignore if (soundCheckbox.checked) { soundMiss.currentTime = 0; soundMiss.play(); } remainingNumberUntilResurrection = 60 * 3; xDeadPoint = car1.position.x; zDeadPoint = car1.position.z; let dx = camera.position.x - car1.position.x; let dz = camera.position.z - car1.position.z; let rad = Math.atan2(dz, dx); zDead = Math.sin(rad) * 0.8; xDead = Math.cos(rad) * 0.8; speed = 0; } |