JavaScript スクランブルのようなゲームをつくる(1)の続きです。前回は必要なクラスを定義したので、今回はページが読み込まれたときの処理を実装します。
Contents
ページが読み込まれたときの処理
ページが読み込まれたときの処理を示します。canvasの大きさを調整して全体を黒で塗りつぶします。そのあとイメージを初期化してイベントリスナーを追加します。そのあとPlayerオブジェクトの生成、地形の障害物の生成と描画、ボリュームの調整をおこないます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
window.onload = () => { $canvas.width = CANVAS_WIDTH; $canvas.height = CANVAS_HEIGHT; ctx.fillStyle = '#000'; ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT); initImages(); // 後述 addEventListeners(); // 後述 player = new Player(); // 地形を生成と描画 getObjects(); // 後述 draw(); // 後述 // ボリュームの初期化 initVolumes(0.5); // 後述 } |
イメージを初期化
initImages関数は画像ファイルを読み込んでイメージを初期化します(必要な画像はimagesフォルダ内に用意しておく)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
function initImages(){ playerImage.src = './images/player.png'; bulletImage.src = './images/bullet.png'; missileImage.src = './images/missile.png'; fuelTankImage.src = './images/fuel-tank.png'; ufoImage.src = './images/ufo.png'; fireballImage.src = './images/fireball.png'; finalbaseImage.src = './images/finalbase.png'; sparkImage1.src = './images/spark1.png'; sparkImage2.src = './images/spark2.png'; rectImage.src = './images/rectangle.png'; nonLeftTopImage.src = './images/non-left-top.png'; nonRightTopImage.src = './images/non-right-top.png'; nonLeftBottomImage.src = './images/non-left-bottom.png'; nonRightBottomImage.src = './images/non-right-bottom.png'; buildingBlockImage.src = './images/building-block.png'; buildingBlockHalfImage.src = './images/building-block-half.png'; hatoNameImage.src = './images/hato-name.png'; } |
イベントリスナーの追加
addEventListeners関数はイベントリスナーを追加します。
ボタンが押下、離されたときの処理、PCのキー操作への対応、チェックボックスのONOFFが切り替わったときの処理を定義しています。
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 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
function addEventListeners(){ // ボタンが押下、離されたときの処理 const types1 = ['mousedown', 'touchstart']; const types2 = ['mouseup', 'touchend']; // ボタンが押下、離されたときのデフォルトの動作を抑止する const buttons = [$up, $down, $accelerate, $shot, $bomb]; for(let i = 0; i < buttons.length; i++){ buttons[i].addEventListener('mousedown', (ev) => ev.preventDefault()); buttons[i].addEventListener('mouseup', (ev) => ev.preventDefault()); buttons[i].addEventListener('touchstart', (ev) => ev.preventDefault()); buttons[i].addEventListener('touchend', (ev) => ev.preventDefault()); } // ボタンが押下されたら移動を開始したり、弾丸発射、爆弾投下の処理をする for(let i=0; i<types1.length; i++){ $up.addEventListener(types1[i], (ev) => pressUp = true); $down.addEventListener(types1[i], (ev) => pressDown = true); $accelerate.addEventListener(types1[i], (ev) => pressRight = true); $shot.addEventListener(types1[i], (ev) => shot()); $bomb.addEventListener(types1[i], (ev) => bomb()); } // ボタンが離されたら移動を終了する for(let i=0; i<types2.length; i++){ $up.addEventListener(types2[i], (ev) => pressUp = false); $down.addEventListener(types2[i], (ev) => pressDown = false); $accelerate.addEventListener(types2[i], (ev) => pressRight = false); } // PCでマウスボタンが離されたら移動を終了する document.addEventListener('mouseup', () => { pressUp = false; pressDown = false; pressRight = false; }); // PCのキー操作にも対応させる document.addEventListener('keydown', (ev) => { // プレイ中はデフォルトの動作を抑止する if(isPlaying) ev.preventDefault(); if(ev.code == 'ArrowRight') pressRight = true; if(ev.code == 'ArrowUp') pressUp = true; if(ev.code == 'ArrowDown') pressDown = true; if(ev.code == 'KeyZ') shot(); if(ev.code == 'KeyX') bomb(); }); document.addEventListener('keyup', (ev) => { if(isPlaying) ev.preventDefault(); if(ev.code == 'ArrowRight') pressRight = false; if(ev.code == 'ArrowUp') pressUp = false; if(ev.code == 'ArrowDown') pressDown = false; }); // スマホ用の操作ボタンの表示非表示の切り替え(プレイ中でチェックされているときだけ表示する) $showButtons.onchange = () => { if(isPlaying && $showButtons.checked) buttons.forEach(button => button.style.display = 'block'); else buttons.forEach(button => button.style.display = 'none'); }; // スマホ用の操作ボタンはプレイ開始前はすべて非表示 buttons.forEach(button => button.style.display = 'none'); } |
地形の生成
getObjects関数は各オブジェクトを生成して配列に格納します。
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 |
function getObjects(){ obstaclePositions = []; missiles = []; fuelTanks = []; finalbases = []; startPositions = []; bullets = []; bombs = []; ufos = []; fireballs = []; sparks = []; // terrain.jsから読み取った文字列のうち空白行は削除 const arr = terrain.split('\n'); const terrainArray = []; for(let i = 0; i < arr.length; i++){ if(arr[i] != '') terrainArray.push(arr[i]); } for(let row = 0; row < terrainArray.length; row++){ for(let col = 0; col < terrainArray[row].length; col++){ const type = terrainArray[row][col]; // 地形に関するオブジェクトを生成 if(type == '■' || type == '●' || type == '▲' || type == '△' || type == '▼' || type == '▽' || type == '○' || type == '鳩') obstaclePositions.push(new Obstacle(col * BLOCK_WIDTH, row * BLOCK_HEIGHT, type)); // 敵に関するオブジェクトを生成 if(terrainArray[row][col] == 'M') missiles.push(new Missile(col, row)); if(terrainArray[row][col] == 'F') fuelTanks.push(new FuelTank(col, row)); if(terrainArray[row][col] == 'B') finalbases.push(new Finalbase(col, row)); // スタート地点に関する座標を取得 if(type == '1') startPositions[0] = new Position(col * BLOCK_WIDTH, row * BLOCK_HEIGHT); if(type == '2') startPositions[1] = new Position(col * BLOCK_WIDTH, row * BLOCK_HEIGHT); if(type == '3') startPositions[2] = new Position(col * BLOCK_WIDTH, row * BLOCK_HEIGHT); if(type == '4') startPositions[3] = new Position(col * BLOCK_WIDTH, row * BLOCK_HEIGHT); if(type == '5') startPositions[4] = new Position(col * BLOCK_WIDTH, row * BLOCK_HEIGHT); if(type == '6') startPositions[5] = new Position(col * BLOCK_WIDTH, row * BLOCK_HEIGHT); if(type == '7') startPositions[6] = new Position(col * BLOCK_WIDTH, row * BLOCK_HEIGHT); } } } |
ボリュームの初期化
レンジスライダーでボリュームの調整ができるようにする処理を示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
function initVolumes(initValue){ const $elemVolume = document.getElementById("volume"); const $elemRange = document.getElementById("vol-range"); $elemVolume.addEventListener('change', () => setVolume($elemVolume.value)); setVolume(initValue); function setVolume(value){ $elemVolume.value = value; $elemRange.textContent = value; shotSound.volume = value; bombSound.volume = value; deadSound.volume = value; hitSound.volume = value; gameoverSound.volume = value; } } function playSound(){ deadSound.currentTime = 0; deadSound.play(); } |
ゲーム開始時の処理
isPlayingフラグをセットしてplayerの死亡フラグをクリアします。スコアを0にリセットしてステージ数を初期値である1にします。燃料を満タン状態にして各オブジェクトの配列をgetObjects関数で初期化して、スタートボタンを非表示にします。そしてチェックボックスの状態によってはスマホ用の操作ボタンを表示させます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
function gameStart(){ isPlaying = true; player.IsDead = false; score = 0; stage = 1; rest = INIT_REST; // 最初はstartPositions[0]が開始地点 curPositionX = startPositions[0].X; // 自機も開始地点にセット(そのあとすぐに後述するupdatePlayer関数によって適切なX座標に修正される) player.X = startPositions[0].X; player.Y = startPositions[0].Y; fuel = INIT_FUEL; getObjects(); $start.style.display = 'none'; const buttons = [$up, $down, $accelerate, $shot, $bomb]; if($showButtons.checked) buttons.forEach(button => button.style.display = 'block'); } |
弾丸発射、爆弾投下時の処理
弾丸発射、爆弾投下の処理を示します。
連射が無制限にできるとゲームが簡単になりすぎるので連射制限をしています。一度処理がおこなわれるとその後0.2秒間は処理を拒否します。またプレイ中ではない場合、自機死亡時はなにもしません。弾丸と爆弾は自機先端の中央部から発射されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
function shot(){ if(!isAllowShot || player.IsDead || !isPlaying) return; isAllowShot = false; setTimeout(() => isAllowShot = true, 200); // player.X + PLAYER_WIDTH, player.Y + (PLAYER_HEIGHT - BULLET_SIZE) / 2 が自機先端の中央部 bullets.push(new Bullet(player.X + PLAYER_WIDTH, player.Y + (PLAYER_HEIGHT - BULLET_SIZE) / 2) ); // 効果音 shotSound.currentTime = 0; shotSound.play(); } function bomb(){ if(!isAllowBomb || bombs.length >= 2 || player.IsDead || !isPlaying) return; isAllowBomb = false; setTimeout(() => isAllowBomb = true, 200); bombs.push(new Bomb(player.X + PLAYER_WIDTH, player.Y + (PLAYER_HEIGHT - BULLET_SIZE) / 2) ); bombSound.currentTime = 0; bombSound.play(); } |