본문 바로가기
IT

테트리스 자바스크립트 シューティングゲーム

by 엘리후 2021. 6. 30.

https://www.sist.ac.jp/~suganuma/game/JavaScript/shoot/index.htm

 

シューティングゲーム

  基本的に, 「ゲーム枠の作成」 で説明した方法とほぼ同じ方法で作成します.ただし,画面のサイズは変更しています.以下,各プログラムに対して,「ゲーム枠の作成」の場合との違いについて説明していきます. HTML ファイル   「ゲーム枠の作成」における HTML ファイル とほとんど同じです.ただし,CANVAS 要素に対して,キーイベント処理のため,TABINDEX 属性が追加してあります.本来,「ゲームクリア」ボタンや「ゲームオーバー」ボタンは必要ありませんが,作成の途中でゲームレベルを変更して確認したい場合が多いので,完成時点まで残しておきます. <!DOCTYPE HTML> <HT...

www.sist.ac.jp

ステップ1: ゲームの枠組み

ステップ2: 移動(自機)

ステップ3: 移動(ボス,敵機)

ステップ4: 弾の発射(自機)

ステップ5: 弾の発射(ボス,敵機)

ステップ6: 完成

ステップ6: 完成( BGM 付き)

ステップ1: ゲームの枠組み

  基本的に,「ゲーム枠の作成」で説明した方法とほぼ同じ方法で作成します.ただし,画面のサイズは変更しています.以下,各プログラムに対して,「ゲーム枠の作成」の場合との違いについて説明していきます.

HTML ファイル

  「ゲーム枠の作成」における HTML ファイルとほとんど同じです.ただし,CANVAS 要素に対して,キーイベント処理のため,TABINDEX 属性が追加してあります.本来,「ゲームクリア」ボタンや「ゲームオーバー」ボタンは必要ありませんが,作成の途中でゲームレベルを変更して確認したい場合が多いので,完成時点まで残しておきます.

<!DOCTYPE HTML> <HTML> <HEAD> <TITLE>シューティングゲーム:ステップ1</TITLE> <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8"> <LINK REL="stylesheet" TYPE="text/css" HREF="../../../master.css"> <SCRIPT TYPE="text/javascript" SRC="main/MainPanel.js"></SCRIPT> <SCRIPT TYPE="text/javascript" SRC="start/StartPanel.js"></SCRIPT> <SCRIPT TYPE="text/javascript" SRC="game/GamePanel.js"></SCRIPT> <SCRIPT TYPE="text/javascript" SRC="clear/GameClearPanel.js"></SCRIPT> <SCRIPT TYPE="text/javascript" SRC="over/GameOverPanel.js"></SCRIPT> </HEAD> <BODY CLASS="eeffee" onLoad="mp_start()"> <H1>シューティングゲーム:ステップ1</H1> <CANVAS ID="canvas_e" STYLE="background-color: #ffffff;" WIDTH="500" HEIGHT="500" TABINDEX="1"></CANVAS><BR> <A HREF="method.htm" TARGET="method"><BUTTON ID="method" CLASS="std">遊び方</BUTTON></A> <BUTTON ID="start" CLASS="std" onClick="gp_start()">ゲーム開始</BUTTON> <BUTTON ID="first" CLASS="std" onClick="st_start()">最初から再開</BUTTON> <BUTTON ID="finish" CLASS="std" onClick="mp.finish()">ゲーム終了</BUTTON> <BUTTON ID="clear" CLASS="std" onClick="gp.next(0)">ゲームクリア</BUTTON> <BUTTON ID="over" CLASS="std" onClick="gp.next(1)">ゲームオーバー</BUTTON> </BODY> </HTML>

MainPanel

  このプログラムに関しても,「ゲーム枠の作成」における MainPanel とほとんど同じです.ボタンの制御部分が異なっているだけです.ゲームが完成し,「ゲームクリア」ボタンや「ゲームオーバー」ボタンを削除した後は,38,39 行目も削除する必要があります.この点は,以下のプログラムについても同様です.

01 mp = null; // MainPanel オブジェクト 02 03 // 04 // MainPanel の開始 05 // 06 function mp_start() 07 { 08 // キャンバス情報 09 let canvas = document.getElementById('canvas_e'); // キャンバス要素の取得 10 let ctx = canvas.getContext('2d'); // キャンバスからコンテキストを取得 11 // MainPanel オブジェクト 12 mp = new MainPanel(canvas, ctx); 13 // StartPanel の表示 14 st_start(); 15 } 16 // 17 // MainPanel オブジェクト(プロパティ) 18 // 19 function MainPanel(canvas, ctx) 20 { 21 this.canvas = canvas; // キャンバス要素 22 this.ctx = ctx; // キャンバスのコンテキスト 23 this.level = 1; // ゲームレベル 24 return this; 25 } 26 // 27 // MainPanel オブジェクト(メソッド) 28 // 29 MainPanel.prototype.finish = function() 30 { 31 // キャンバスのクリア 32 mp.ctx.clearRect(0, 0, mp.canvas.width, mp.canvas.height); 33 // ボタンを非表示 34 document.getElementById('method').style.display = "none"; 35 document.getElementById('start').style.display = "none"; 36 document.getElementById('first').style.display = "none"; 37 document.getElementById('finish').style.display = "none"; 38 document.getElementById('clear').style.display = "none"; 39 document.getElementById('over').style.display = "none"; 40 }

StartPanel

  このプログラムに関しても,「ゲーム枠の作成」における StartPanel とほとんど同じです.ボタンの制御部分が異なっているだけです.当然のことながら,ゲームタイトル及び「遊び方」の内容を変更しています.

// // StartPanel の開始 // function st_start() { mp.level = 1; // ゲームレベルの設定 // キャンバスのクリア mp.ctx.clearRect(0, 0, mp.canvas.width, mp.canvas.height); // ゲームタイトルの表示 mp.ctx.font = "40px 'MS ゴシック'"; mp.ctx.textBaseline = "middle"; mp.ctx.textAlign = "center"; mp.ctx.fillStyle = "rgb(0, 0, 0)"; mp.ctx.fillText("シューティングゲーム", mp.canvas.width/2, mp.canvas.height/2); // ボタンの表示制御 document.getElementById('method').style.display = ""; document.getElementById('start').style.display = ""; document.getElementById('first').style.display = "none"; document.getElementById('finish').style.display = "none"; document.getElementById('start').innerHTML = "ゲーム開始"; document.getElementById('clear').style.display = "none"; document.getElementById('over').style.display = "none"; }

GamePanel

  GamePanel は,実際のゲームを実現する部分です.従って,「ゲーム枠の作成」における GamePanel とは,ゲームの種類によってその内容は大きく異なります.今後,このプログラムを完成させていくことになりますが,ここでは,敵機と自機を表示しています.なお,敵機には 2 種類あり,今後,大きな方をボス,小さな方を敵機と呼びます.

001 gp = null; // GamePanel オブジェクト 002 003 // 004 // GamePanel の開始 005 // 006 function gp_start() 007 { 008 // GamePanel オブジェクト 009 gp = new GamePanel(); 010 // タイマーのスタート 011 gp.timerID = setInterval('gp.timer()', 10); 012 // ボタンの表示制御 013 document.getElementById('method').style.display = "none"; 014 document.getElementById('start').style.display = "none"; 015 document.getElementById('first').style.display = "none"; 016 document.getElementById('finish').style.display = "none"; 017 document.getElementById('clear').style.display = ""; 018 document.getElementById('over').style.display = ""; 019 } 020 // 021 // GamePanel オブジェクト(プロパティ) 022 // 023 function GamePanel() 024 { 025 this.timerID = -1; 026 this.ct = 0; // カウンター 027 this.my = new My(); // 自機 028 this.bs = new Boss(); // ボス 029 this.no = 2; // 敵機の数 030 this.em = new Array(); // 敵機 031 this.em[0] = new Enemy(0, this.bs); 032 this.em[1] = new Enemy(1, this.bs); 033 // 敵機の存在 034 this.ex = new Array(); 035 if (mp.level == 1) { 036 this.ex[0] = false; 037 this.ex[1] = false; 038 } 039 else { 040 this.ex[0] = true; 041 this.ex[1] = true; 042 } 043 return this; 044 } 045 // 046 // GamePanel オブジェクト(メソッド timer) 047 // 048 GamePanel.prototype.timer = function() 049 { 050 // 描画 051 gp.draw(); 052 // カウントアップ 053 gp.ct = (gp.ct + 1) % 300; 054 } 055 // 056 // GamePanel オブジェクト(メソッド draw) 057 // 058 GamePanel.prototype.draw = function() 059 { 060 // キャンバスのクリア 061 mp.ctx.clearRect(0, 0, mp.canvas.width, mp.canvas.height); 062 // 描画 063 mp.ctx.drawImage(gp.my.image, gp.my.x, gp.my.y); 064 mp.ctx.drawImage(gp.bs.image, gp.bs.x, gp.bs.y); 065 for (let i1 = 0; i1 < gp.no; i1++) { 066 if (gp.ex[i1]) 067 mp.ctx.drawImage(gp.em[i1].image, gp.em[i1].x, gp.em[i1].y); 068 } 069 } 070 // 071 // My オブジェクト(プロパティ) 072 // 073 function My() 074 { 075 this.image = new Image(); // 自機の画像 076 this.image.src = "image/my.gif"; 077 this.width = 50; // 自機の幅 078 this.height = 51; // 自機の高さ 079 this.x = mp.canvas.width / 2 - this.width / 2; // 自機の位置(横) 080 this.y = mp.canvas.height - this.height - 10; // 自機の位置(縦) 081 return this; 082 } 083 // 084 // Boss オブジェクト(プロパティ) 085 // 086 function Boss() 087 { 088 this.image = new Image(); // ボス画像 089 this.image.src = "image/boss.gif"; 090 this.width = 66; // ボスの幅 091 this.height = 95; // ボスの高さ 092 let a = 100 + Math.floor((mp.canvas.width - 200 - this.width) * Math.random()); 093 this.x = a; // ボスの位置(横) 094 let b = 10 + Math.floor(20 * Math.random()); 095 this.y = b; // ボスの位置(縦) 096 return this; 097 } 098 // 099 // Enemy オブジェクト(プロパティ) 100 // 101 function Enemy(sw, bs) 102 { 103 this.image = new Image(); // 敵機画像 104 this.image.src = "image/enemy.gif"; 105 this.width = 27; // 敵機の幅 106 this.height = 41; // 敵機の高さ 107 this.x; // 敵機の位置(横) 108 this.y; // 敵機の位置(縦) 109 // 敵機の初期位置 110 if (sw == 0) { 111 this.x = bs.x - 150 + Math.floor(100 * Math.random()); 112 this.y = bs.y + bs.height - 80 + Math.floor(100 * Math.random()); 113 } 114 else { 115 this.x = bs.x + bs.width + 50 + Math.floor(100 * Math.random()); 116 this.y = bs.y + bs.height - 80 + Math.floor(100 * Math.random()); 117 } 118 return this; 119 } 120 // 121 // GamePanel オブジェクト(メソッド next) 122 // 123 GamePanel.prototype.next = function(sw) 124 { 125 clearInterval(gp.timerID); // タイマーの停止 126 if (sw == 0) 127 gcp_start(); // ゲームクリア 128 else 129 gop_start(); // ゲームオーバー 130 }
009 行目( gp_start 関数)
  GamePanel オブジェクトを生成し,その結果をグローバル変数 gp( 01 行目)に代入しています.具体的には,23 行目~ 44 行目に記述された GamePanel 関数が実行されます.
011 行目( gp_start 関数)
  タイマーを設定しています.この設定により,10 ms 毎に,GamePanel オブジェクトのメソッド timer( 048 行目~ 054 行目)が実行されます.
027 行目~ 032 行目( GamePanel 関数)
  今後,その機能を拡張していく可能性がありますので,自機,ボス,及び,敵機は,別のオブジェクトとして定義してあります( 073 行目~ 082 行目,086 行目~ 097 行目,及び,101 行目~ 119 行目).なお,敵機は 2 機存在するため,配列を利用しています.
034 行目~ 042 行目( GamePanel 関数)
  プロパティ ex は,敵機が存在するか否かを表す変数です( 034 行目).レベル1では敵機は存在せず(ボスだけ),レベル2の初期状態では 2 機存在しますので,その状態を設定しています.
048 行目~ 054 行目( timer メソッド)
  タイマーにより 10 ms 毎に実行される GamePanel オブジェクトのメソッド timer の定義です.051 行目で描画を実行する GamePanel オブジェクトのメソッド draw( 058 行目~ 069 行目)を呼び,053 行目では,カウンタ gp.ct ( GamePanel オブジェクトのプロパティ)の値を増加させています( 値が 300 になると 0 に戻る).このプログラムでは,異なる周期で実行される処理を一つのタイマーで制御しています.これは,それに対応するための処理です(後述).
061 行目( draw メソッド)
  画面をクリアしています..
063,064 行目( draw メソッド)
  自機とボスを描画しています.
065 行目~ 068 行目( draw メソッド)
  敵機が存在する場合は,敵機を描画しています.
073 行目~ 082 行目( My 関数)
  My オブジェクトのプロパティを設定しています.
086 行目~ 097 行目( Boss 関数)
  Boss オブジェクトのプロパティを設定しています.初期位置は,Math オブジェクトの random 関数を使用して,ランダムに設定しています(垂直位置に関しては [ 10, 30 ]区間のランダム,水平位置に関しては [ 100, 画面サイズ-200-ボスの幅] 区間のランダム値).
101 行目~ 119 行目( Enemy 関数)
  Enemy オブジェクトのプロパティを設定しています.敵機1に対しては,x 座標は [ ボスの左の座標-150, ボスの左の座標-50 ],y 座標は [ ボスの下の座標-80, ボスの下の座標+20 ] 区間からランダムに決定されます.また,敵機2に対しては,x 座標は [ ボスの右の座標+50, ボスの右の座標+150 ],y 座標は [ ボスの下の座標-80, ボスの下の座標+20 ] 区間からランダムに決定されます.
123 行目~ 130 行目( next メソッド)
  「ゲームクリア」ボタン,及び「ゲームオーバー」ボタンがクリックされた時実行される GamePanel オブジェクトのメソッドです.ゲーム完成時には削除されます.

GameClearPanel

  このプログラムに関しても,「ゲーム枠の作成」における GameClearPanel とほとんど同じです.違いは,ボタンの制御部分と,レベルが 2 までしか無い点だけです.

// // GameClearPanel の開始 // function gcp_start() { // キャンバスのクリア mp.ctx.clearRect(0, 0, mp.canvas.width, mp.canvas.height); // タイトルの表示 mp.ctx.font = "40px 'MS ゴシック'"; mp.ctx.textBaseline = "middle"; mp.ctx.textAlign = "center"; mp.ctx.fillStyle = "rgb(0, 0, 0)"; mp.ctx.fillText("Game Clear!", mp.canvas.width/2, mp.canvas.height/2); // ボタンの表示制御 document.getElementById('method').style.display = "none"; if (mp.level > 1) { // 最初からゲーム再開 document.getElementById('start').style.display = "none"; document.getElementById('first').style.display = ""; } else { // レベルアップ mp.level++; document.getElementById('start').style.display = ""; document.getElementById('first').style.display = "none"; document.getElementById('start').innerHTML = "次のレベル"; } document.getElementById('finish').style.display = ""; document.getElementById('clear').style.display = "none"; document.getElementById('over').style.display = "none"; }

GameOverPanel

  このプログラムに関しても,「ゲーム枠の作成」における GameOverPanel とほとんど同じです.ボタンの制御部分が異なっているだけです.

// // GameOverPanel の開始 // function gop_start() { // キャンバスのクリア mp.ctx.clearRect(0, 0, mp.canvas.width, mp.canvas.height); // タイトルの表示 mp.ctx.font = "40px 'MS ゴシック'"; mp.ctx.textBaseline = "middle"; mp.ctx.textAlign = "center"; mp.ctx.fillStyle = "rgb(0, 0, 0)"; mp.ctx.fillText("Game Over!", mp.canvas.width/2, mp.canvas.height/2); // ボタンの表示制御 document.getElementById('method').style.display = "none"; document.getElementById('start').style.display = ""; document.getElementById('first').style.display = ""; document.getElementById('finish').style.display = ""; document.getElementById('start').innerHTML = "現レベルで再開"; document.getElementById('clear').style.display = "none"; document.getElementById('over').style.display = "none"; }

ステップ2: 移動(自機)

  ここでは,「↑」,「↓」,「←」,「→」キーによって,自機を上下左右に移動させる機能を付加します.修正するのは,GamePanel の My 関数,gp_start 関数,及び,キーが押された時に実行される onKeyDown メソッド( GamePanel オブジェクトのメソッド)です.まず,My 関数の修正部分から順に説明していきます.

001 gp = null; // GamePanel オブジェクト 002 003 // 004 // GamePanel の開始 005 // 006 function gp_start() 007 { 008 // GamePanel オブジェクト 009 gp = new GamePanel(); 010 // タイマーのスタート 011 gp.timerID = setInterval('gp.timer()', 10); 012 // キーリスナの追加(キーが押された時) 013 mp.canvas.addEventListener("keydown", gp.onKeyDown, false); 014 mp.canvas.focus(); 015 // ボタンの表示制御 016 document.getElementById('method').style.display = "none"; 017 document.getElementById('start').style.display = "none"; 018 document.getElementById('first').style.display = "none"; 019 document.getElementById('finish').style.display = "none"; 020 document.getElementById('clear').style.display = ""; 021 document.getElementById('over').style.display = ""; 022 } 023 // 024 // GamePanel オブジェクト(プロパティ) 025 // 026 function GamePanel() 027 { 028 this.timerID = -1; 029 this.ct = 0; // カウンター 030 this.my = new My(); // 自機 031 this.bs = new Boss(); // ボス 032 this.no = 2; // 敵機の数 033 this.em = new Array(); // 敵機 034 this.em[0] = new Enemy(0, this.bs); 035 this.em[1] = new Enemy(1, this.bs); 036 // 敵機の存在 037 this.ex = new Array(); 038 if (mp.level == 1) { 039 this.ex[0] = false; 040 this.ex[1] = false; 041 } 042 else { 043 this.ex[0] = true; 044 this.ex[1] = true; 045 } 046 return this; 047 } 048 // 049 // GamePanel オブジェクト(メソッド timer) 050 // 051 GamePanel.prototype.timer = function() 052 { 053 // 描画 054 gp.draw(); 055 // カウントアップ 056 gp.ct = (gp.ct + 1) % 300; 057 } 058 // 059 // GamePanel オブジェクト(メソッド draw) 060 // 061 GamePanel.prototype.draw = function() 062 { 063 // キャンバスのクリア 064 mp.ctx.clearRect(0, 0, mp.canvas.width, mp.canvas.height); 065 // 描画 066 mp.ctx.drawImage(gp.my.image, gp.my.x, gp.my.y); 067 mp.ctx.drawImage(gp.bs.image, gp.bs.x, gp.bs.y); 068 for (let i1 = 0; i1 < gp.no; i1++) { 069 if (gp.ex[i1]) 070 mp.ctx.drawImage(gp.em[i1].image, gp.em[i1].x, gp.em[i1].y); 071 } 072 } 073 // 074 // GamePanel オブジェクト(メソッド onKeyDown) 075 // 076 GamePanel.prototype.onKeyDown = function(event) 077 { 078 if (event.keyCode == 38) // 「↑」キー 079 gp.my.y -= gp.my.v; 080 else if (event.keyCode == 40) // 「↓」キー 081 gp.my.y += gp.my.v; 082 else if (event.keyCode == 37) // 「←」キー 083 gp.my.x -= gp.my.v; 084 else if (event.keyCode == 39) // 「→」キー 085 gp.my.x += gp.my.v; 086 } 087 // 088 // My オブジェクト(プロパティ) 089 // 090 function My() 091 { 092 this.image = new Image(); // 自機の画像 093 this.image.src = "image/my.gif"; 094 this.width = 50; // 自機の幅 095 this.height = 51; // 自機の高さ 096 this.x = mp.canvas.width / 2 - this.width / 2; // 自機の位置(横) 097 this.y = mp.canvas.height - this.height - 10; // 自機の位置(縦) 098 this.v = 20; // 移動キーが押されたときの移動量 099 return this; 100 } 101 // 102 // Boss オブジェクト(プロパティ) 103 // 104 function Boss() 105 { 106 this.image = new Image(); // ボス画像 107 this.image.src = "image/boss.gif"; 108 this.width = 66; // ボスの幅 109 this.height = 95; // ボスの高さ 110 let a = 100 + Math.floor((mp.canvas.width - 200 - this.width) * Math.random()); 111 this.x = a; // ボスの位置(横) 112 let b = 10 + Math.floor(20 * Math.random()); 113 this.y = b; // ボスの位置(縦) 114 return this; 115 } 116 // 117 // Enemy オブジェクト(プロパティ) 118 // 119 function Enemy(sw, bs) 120 { 121 this.image = new Image(); // 敵機画像 122 this.image.src = "image/enemy.gif"; 123 this.width = 27; // 敵機の幅 124 this.height = 41; // 敵機の高さ 125 this.x; // 敵機の位置(横) 126 this.y; // 敵機の位置(縦) 127 // 敵機の初期位置 128 if (sw == 0) { 129 this.x = bs.x - 150 + Math.floor(100 * Math.random()); 130 this.y = bs.y + bs.height - 80 + Math.floor(100 * Math.random()); 131 } 132 else { 133 this.x = bs.x + bs.width + 50 + Math.floor(100 * Math.random()); 134 this.y = bs.y + bs.height - 80 + Math.floor(100 * Math.random()); 135 } 136 return this; 137 } 138 // 139 // GamePanel オブジェクト(メソッド next) 140 // 141 GamePanel.prototype.next = function(sw) 142 { 143 clearInterval(gp.timerID); // タイマーの停止 144 if (sw == 0) 145 gcp_start(); 146 else 147 gop_start(); 148 }
098 行目( My 関数)
  矢印キーが押された時の移動量を設定しています.
013,014 行目( gp_start 関数)
  キーイベントに対するリスナーを付加しています(キーが押された時の具体的な処理は 076 行目~ 086 行目).014 行目は,キャンバスにフォーカスを持ってくるための処理です.
076 行目~ 086 行目( onKeyDown メソッド)
  キーが押された時の処理であり,押されたキーによって,自機の位置を変更しています.

ステップ3: 移動(ボス,敵機)

  ここでは,ボス及び敵機をあらかじめ決めたパターンに沿って動かします.GamePanel の timer メソッド( GamePanel オブジェクトのメソッド),及び,Boss 関数を修正すると共に,一定時間毎にボス及び敵機を動かすメソッド move を Boss オブジェクトに追加しています.

001 gp = null; // GamePanel オブジェクト 002 003 // 004 // GamePanel の開始 005 // 006 function gp_start() 007 { 008 // GamePanel オブジェクト 009 gp = new GamePanel(); 010 // タイマーのスタート 011 gp.timerID = setInterval('gp.timer()', 10); 012 // キーリスナの追加(キーが押された時) 013 mp.canvas.addEventListener("keydown", gp.onKeyDown, false); 014 mp.canvas.focus(); 015 // ボタンの表示制御 016 document.getElementById('method').style.display = "none"; 017 document.getElementById('start').style.display = "none"; 018 document.getElementById('first').style.display = "none"; 019 document.getElementById('finish').style.display = "none"; 020 document.getElementById('clear').style.display = ""; 021 document.getElementById('over').style.display = ""; 022 } 023 // 024 // GamePanel オブジェクト(プロパティ) 025 // 026 function GamePanel() 027 { 028 this.timerID = -1; 029 this.ct = 0; // カウンタ 030 this.my = new My(); // 自機 031 this.bs = new Boss(); // ボス 032 this.no = 2; // 敵機の数 033 this.em = new Array(); // 敵機 034 this.em[0] = new Enemy(0, this.bs); 035 this.em[1] = new Enemy(1, this.bs); 036 // 敵機の存在 037 this.ex = new Array(); 038 if (mp.level == 1) { 039 this.ex[0] = false; 040 this.ex[1] = false; 041 } 042 else { 043 this.ex[0] = true; 044 this.ex[1] = true; 045 } 046 return this; 047 } 048 // 049 // GamePanel オブジェクト(メソッド timer) 050 // 051 GamePanel.prototype.timer = function() 052 { 053 // 描画 054 gp.draw(); 055 // 移動処理 056 if (gp.ct%5 == 0) 057 gp.bs.move(); 058 // カウントアップ 059 gp.ct = (gp.ct + 1) % 300; 060 } 061 // 062 // GamePanel オブジェクト(メソッド draw) 063 // 064 GamePanel.prototype.draw = function() 065 { 066 // キャンバスのクリア 067 mp.ctx.clearRect(0, 0, mp.canvas.width, mp.canvas.height); 068 // 描画 069 mp.ctx.drawImage(gp.my.image, gp.my.x, gp.my.y); 070 mp.ctx.drawImage(gp.bs.image, gp.bs.x, gp.bs.y); 071 for (let i1 = 0; i1 < gp.no; i1++) { 072 if (gp.ex[i1]) 073 mp.ctx.drawImage(gp.em[i1].image, gp.em[i1].x, gp.em[i1].y); 074 } 075 } 076 // 077 // GamePanel オブジェクト(メソッド onKeyDown) 078 // 079 GamePanel.prototype.onKeyDown = function(event) 080 { 081 if (event.keyCode == 38) // 「↑」キー 082 gp.my.y -= gp.my.v; 083 else if (event.keyCode == 40) // 「↓」キー 084 gp.my.y += gp.my.v; 085 else if (event.keyCode == 37) // 「←」キー 086 gp.my.x -= gp.my.v; 087 else if (event.keyCode == 39) // 「→」キー 088 gp.my.x += gp.my.v; 089 } 090 // 091 // My オブジェクト(プロパティ) 092 // 093 function My() 094 { 095 this.image = new Image(); // 自機の画像 096 this.image.src = "image/my.gif"; 097 this.width = 50; // 自機の幅 098 this.height = 51; // 自機の高さ 099 this.x = mp.canvas.width / 2 - this.width / 2; // 自機の位置(横) 100 this.y = mp.canvas.height - this.height - 10; // 自機の位置(縦) 101 this.v = 20; // 移動キーが押されたときの移動量 102 return this; 103 } 104 // 105 // Boss オブジェクト(プロパティ) 106 // 107 function Boss() 108 { 109 this.image = new Image(); // ボス画像 110 this.image.src = "image/boss.gif"; 111 this.width = 66; // ボスの幅 112 this.height = 95; // ボスの高さ 113 let a = 100 + Math.floor((mp.canvas.width - 200 - this.width) * Math.random()); 114 this.x = a; // ボスの位置(横) 115 let b = 10 + Math.floor(20 * Math.random()); 116 this.y = b; // ボスの位置(縦) 117 // 行動パターンの設定 118 this.ct = 1; 119 this.ptn1 = new Array(); 120 this.ptn1[0] = new Array(-5, 0, 50); 121 this.ptn1[1] = new Array(0, 20, 55); 122 this.ptn1[2] = new Array(5, 0, 105); 123 this.ptn1[3] = new Array(0, -20, 110); 124 this.ptn2 = new Array(); 125 this.ptn2[0] = new Array(5, 0, 50); 126 this.ptn2[1] = new Array(0, 20, 55); 127 this.ptn2[2] = new Array(-5, 0, 105); 128 this.ptn2[3] = new Array(0, -20, 110); 129 this.ptn = new Array(); 130 if (this.x > mp.canvas.width/2-this.width/2) 131 this.ptn = this.ptn1; 132 else 133 this.ptn = this.ptn2; 134 return this; 135 } 136 // 137 // Boss オブジェクト(メソッド move) 138 // 139 Boss.prototype.move = function() 140 { 141 // 移動 142 gp.bs.ct++; 143 if (gp.bs.ct > 110) 144 gp.bs.ct = 1; 145 // ボスの位置 146 let k = -1; 147 for (let i1 = 0; i1 < gp.bs.ptn.length-1 && k < 0; i1++) { 148 if (gp.bs.ct <= gp.bs.ptn[i1][2]) 149 k = i1; 150 } 151 if (k < 0) 152 k = gp.bs.ptn.length - 1; 153 gp.bs.x += gp.bs.ptn[k][0]; 154 gp.bs.y += gp.bs.ptn[k][1]; 155 // 敵機の位置 156 if (gp.ex[0]) { 157 gp.em[0].x += gp.bs.ptn[k][0]; 158 gp.em[0].y += gp.bs.ptn[k][1]; 159 } 160 if (gp.ex[1]) { 161 gp.em[1].x += gp.bs.ptn[k][0]; 162 gp.em[1].y += gp.bs.ptn[k][1]; 163 } 164 } 165 // 166 // Enemy オブジェクト(プロパティ) 167 // 168 function Enemy(sw, bs) 169 { 170 this.image = new Image(); // 敵機画像 171 this.image.src = "image/enemy.gif"; 172 this.width = 27; // 敵機の幅 173 this.height = 41; // 敵機の高さ 174 this.x; // 敵機の位置(横) 175 this.y; // 敵機の位置(縦) 176 // 敵機の初期位置 177 if (sw == 0) { 178 this.x = bs.x - 150 + Math.floor(100 * Math.random()); 179 this.y = bs.y + bs.height - 80 + Math.floor(100 * Math.random()); 180 } 181 else { 182 this.x = bs.x + bs.width + 50 + Math.floor(100 * Math.random()); 183 this.y = bs.y + bs.height - 80 + Math.floor(100 * Math.random()); 184 } 185 return this; 186 } 187 // 188 // GamePanel オブジェクト(メソッド next) 189 // 190 GamePanel.prototype.next = function(sw) 191 { 192 clearInterval(gp.timerID); // タイマーの停止 193 if (sw == 0) 194 gcp_start(); 195 else 196 gop_start(); 197 }
056,057 行目( timer メソッド)
  ボスを 50 ms 毎に動かすため,カウンタ ct ( GamePanel オブジェクトのプロパティ)の値が 5 の倍数の時だけ,ボスを移動させるメソッド move ( Boss オブジェクトのメソッド)を呼んでいます.
119 行目~ 128 行目( Boss 関数)
  ボスの行動パターンを 2 次元配列で表し,初期設定しています.ここに示すように,2 次元配列は,まず配列を定義し( 119,124 行目),その配列の各要素を再び配列として定義する( 120 行目~ 123 行目,125 行目~ 128 行目)ことによって可能です.この例では,いずれも 4 行 3 列の配列になります.プロパティ ptn1 は,初期状態でボスが画面の右側にいるときのパターン,また,プロパティ ptn2 は,そうでない場合のパターンであり,130 行目~ 133 行目においてプロパティ ptn に設定されます.実際には,そのアドレスが設定されるとみなせますので,例えば,131 行目のような代入を行えば,ptn と ptn1 が同じデータを指すことになります.従って,ptn を介して各要素の値を変更すれば,ptn1 の対応する要素の値も変化します(逆も同様).
  一般に,配列の各要素を配列として定義することによって,多次元配列を定義することが可能です.以下に示すのは 2 行 3 列の 2 次元配列の例です.
let a = new Array(2); // let a = new Array(); でも可 for (let i1 = 0; i1 < 2; i1++) a[i1] = new Array(3);
初期設定も同時に行いたい場合は,例えば,以下のようにして行います.
let a = new Array(2); // let a = new Array(); でも可 a[0] = new Array(1, 2, 3); a[1] = new Array(4, 5, 6);
  118 行目に定義されているプロパティ ct は,カウンタであり,50 ms 毎( 056,057 行目,142 行目)に 1 ずつ増加し,110 を超えると再び 1 に戻されます( 143,144 行目).この ct の値に従って,例えば,120 行目以降は,
(-5, 0, 50) : ct の値が 1 ~ 50 の間は x 座標は 5 ピクセルずつ減らし,y 座標は変化させない (0, 20, 55) : ct の値が 51 ~ 55 の間は x 座標は変化させず,y 座標は 20 ピクセルずつ増やす ・・・・・
のような意味を持つことになります.この結果,ボスは矩形に沿った動きをします.
146 行目~ 152 行目( move メソッド)
  Boss オブジェクトのプロパティ ct の値がパターンのどの位置に対応しているかを調べています.なお,length は,Array オブジェクトのプロパティであり,配列の要素数を表します.この場合は、配列 ptn の行の数に相当します.
153,154 行目( move メソッド)
  パターンに従って,Boss オブジェクトのプロパティ x 及び y 座標を変化させています.
156 行目~ 163 行目( move メソッド)
  敵機に対しても,ボスと同じ動きをさせています.

ステップ4: 弾の発射(自機)

  ここでは,「スペース」キーを押すことによって,自機の弾を発射させる処理を行っています.GamePanel の timer メソッド( GamePanel オブジェクトのメソッド),draw メソッド( GamePanel オブジェクトのメソッド),onKeyDown メソッド( GamePanel オブジェクトのメソッド),及び,My 関数を修正すると共に,自機の弾に相当する Bullet 関数( Bullet オブジェクト)とその弾を処理するメソッド move,及び,shoot を Bullet オブジェクトに追加しています.

001 gp = null; // GamePanel オブジェクト 002 003 // 004 // GamePanel の開始 005 // 006 function gp_start() 007 { 008 // GamePanel オブジェクト 009 gp = new GamePanel(); 010 // タイマーのスタート 011 gp.timerID = setInterval('gp.timer()', 10); 012 // キーリスナの追加(キーが押された時) 013 mp.canvas.addEventListener("keydown", gp.onKeyDown, false); 014 mp.canvas.focus(); 015 // ボタンの表示制御 016 document.getElementById('method').style.display = "none"; 017 document.getElementById('start').style.display = "none"; 018 document.getElementById('first').style.display = "none"; 019 document.getElementById('finish').style.display = "none"; 020 document.getElementById('clear').style.display = ""; 021 document.getElementById('over').style.display = ""; 022 } 023 // 024 // GamePanel オブジェクト(プロパティ) 025 // 026 function GamePanel() 027 { 028 this.timerID = -1; 029 this.ct = 0; // カウンタ 030 this.my = new My(); // 自機 031 this.bs = new Boss(); // ボス 032 this.no = 2; // 敵機の数 033 this.em = new Array(); // 敵機 034 this.em[0] = new Enemy(0, this.bs); 035 this.em[1] = new Enemy(1, this.bs); 036 // 敵機の存在 037 this.ex = new Array(); 038 if (mp.level == 1) { 039 this.ex[0] = false; 040 this.ex[1] = false; 041 } 042 else { 043 this.ex[0] = true; 044 this.ex[1] = true; 045 } 046 return this; 047 } 048 // 049 // GamePanel オブジェクト(メソッド timer) 050 // 051 GamePanel.prototype.timer = function() 052 { 053 // 描画 054 gp.draw(); 055 // 移動処理 056 if (gp.ct%3 == 0) 057 gp.my.bl.move(); // 自機の弾 058 if (gp.ct%5 == 0) 059 gp.bs.move(); // ボスの移動 060 // カウントアップ 061 gp.ct = (gp.ct + 1) % 300; 062 } 063 // 064 // GamePanel オブジェクト(メソッド draw) 065 // 066 GamePanel.prototype.draw = function() 067 { 068 // キャンバスのクリア 069 mp.ctx.clearRect(0, 0, mp.canvas.width, mp.canvas.height); 070 // 描画 071 // 自機と弾 072 mp.ctx.drawImage(gp.my.image, gp.my.x, gp.my.y); 073 for (let i1 = 0; i1 < gp.my.bl.no; i1++) { 074 if (gp.my.bl.ex[i1]) { 075 mp.ctx.beginPath(); 076 mp.ctx.fillStyle = "rgb(0, 255, 0)"; 077 mp.ctx.arc(gp.my.bl.x[i1], gp.my.bl.y[i1], gp.my.bl.r, 0, 2*Math.PI); 078 mp.ctx.fill(); 079 } 080 } 081 // ボス 082 mp.ctx.drawImage(gp.bs.image, gp.bs.x, gp.bs.y); 083 // 敵機 084 for (let i1 = 0; i1 < gp.no; i1++) { 085 if (gp.ex[i1]) 086 mp.ctx.drawImage(gp.em[i1].image, gp.em[i1].x, gp.em[i1].y); 087 } 088 } 089 // 090 // GamePanel オブジェクト(メソッド onKeyDown) 091 // 092 GamePanel.prototype.onKeyDown = function(event) 093 { 094 if (event.keyCode == 38) // 「↑」キー 095 gp.my.y -= gp.my.v; 096 else if (event.keyCode == 40) // 「↓」キー 097 gp.my.y += gp.my.v; 098 else if (event.keyCode == 37) // 「←」キー 099 gp.my.x -= gp.my.v; 100 else if (event.keyCode == 39) // 「→」キー 101 gp.my.x += gp.my.v; 102 else if (event.keyCode == 32) // 「スペース」キー 103 gp.my.bl.shoot(); 104 } 105 // 106 // My オブジェクト(プロパティ) 107 // 108 function My() 109 { 110 this.image = new Image(); // 自機の画像 111 this.image.src = "image/my.gif"; 112 this.width = 50; // 自機の幅 113 this.height = 51; // 自機の高さ 114 this.x = mp.canvas.width / 2 - this.width / 2; // 自機の位置(横) 115 this.y = mp.canvas.height - this.height - 10; // 自機の位置(縦) 116 this.v = 20; // 移動キーが押されたときの移動量 117 this.bl = new Bullet(); // 弾 118 return this; 119 } 120 // 121 // Bullet オブジェクト(プロパティ) 122 // 123 function Bullet() 124 { 125 this.r = 12; // 弾の半径 126 this.no = 15; // 弾の全数 127 this.no_1 = 5; // 一度に撃てる弾数 128 this.ct = 0; // 現在の弾の数 129 this.x = new Array(); // 弾の位置(横) 130 this.y = new Array(); // 弾の位置(縦) 131 this.v = 30; // 弾の速さ 132 this.fire = false; // 弾を発射中か否か 133 this.ex = new Array(); // 弾の存在 134 for (let i1 = 0; i1 < this.no; i1++) 135 this.ex[i1] = false; 136 } 137 // 138 // Bullet オブジェクト(メソッド move) 139 // 140 Bullet.prototype.move = function() 141 { 142 // 弾の移動 143 for (let i1 = 0; i1 < gp.my.bl.no; i1++) { 144 if (gp.my.bl.ex[i1]) { 145 gp.my.bl.y[i1] -= gp.my.bl.v; 146 if (gp.my.bl.y[i1] < -gp.my.bl.r) 147 gp.my.bl.ex[i1] = false; 148 } 149 } 150 // 次の弾の発射 151 if (gp.my.bl.fire) { 152 if (gp.my.bl.ct < gp.my.bl.no_1) { 153 let sw = true; 154 for (let i1 = 0; i1 < gp.my.bl.no && sw; i1++) { 155 if (!gp.my.bl.ex[i1]) { 156 gp.my.bl.x[i1] = gp.my.x + gp.my.width / 2; 157 gp.my.bl.y[i1] = gp.my.y - gp.my.bl.r; 158 gp.my.bl.ex[i1] = true; 159 sw = false; 160 } 161 } 162 gp.my.bl.ct++; 163 if (gp.my.bl.ct >= gp.my.bl.no_1) { 164 gp.my.bl.fire = false; 165 gp.my.bl.ct = 0; 166 } 167 } 168 } 169 } 170 // 171 // Bullet オブジェクト(メソッド shoot,最初の弾の発射) 172 // 173 Bullet.prototype.shoot = function() 174 { 175 if (!gp.my.bl.fire) { 176 gp.my.bl.fire = true; 177 gp.my.bl.ct = 1; 178 let sw = true; 179 for (let i1 = 0; i1 < gp.my.bl.no && sw; i1++) { 180 if (!gp.my.bl.ex[i1]) { 181 gp.my.bl.x[i1] = gp.my.x + gp.my.width / 2; 182 gp.my.bl.y[i1] = gp.my.y - gp.my.bl.r; 183 gp.my.bl.ex[i1] = true; 184 sw = false; 185 } 186 } 187 } 188 } 189 // 190 // Boss オブジェクト(プロパティ) 191 // 192 function Boss() 193 { 194 this.image = new Image(); // ボス画像 195 this.image.src = "image/boss.gif"; 196 this.width = 66; // ボスの幅 197 this.height = 95; // ボスの高さ 198 let a = 100 + Math.floor((mp.canvas.width - 200 - this.width) * Math.random()); 199 this.x = a; // ボスの位置(横) 200 let b = 10 + Math.floor(20 * Math.random()); 201 this.y = b; // ボスの位置(縦) 202 // 行動パターンの設定 203 this.ct = 1; 204 this.ptn1 = new Array(); 205 this.ptn1[0] = new Array(-5, 0, 50); 206 this.ptn1[1] = new Array(0, 20, 55); 207 this.ptn1[2] = new Array(5, 0, 105); 208 this.ptn1[3] = new Array(0, -20, 110); 209 this.ptn2 = new Array(); 210 this.ptn2[0] = new Array(5, 0, 50); 211 this.ptn2[1] = new Array(0, 20, 55); 212 this.ptn2[2] = new Array(-5, 0, 105); 213 this.ptn2[3] = new Array(0, -20, 110); 214 this.ptn = new Array(); 215 if (this.x > mp.canvas.width/2-this.width/2) 216 this.ptn = this.ptn1; 217 else 218 this.ptn = this.ptn2; 219 return this; 220 } 221 // 222 // Boss オブジェクト(メソッド move) 223 // 224 Boss.prototype.move = function() 225 { 226 // 移動 227 gp.bs.ct++; 228 if (gp.bs.ct > 110) 229 gp.bs.ct = 1; 230 // ボスの位置 231 let k = -1; 232 for (let i1 = 0; i1 < gp.bs.ptn.length-1 && k < 0; i1++) { 233 if (gp.bs.ct <= gp.bs.ptn[i1][2]) 234 k = i1; 235 } 236 if (k < 0) 237 k = gp.bs.ptn.length - 1; 238 gp.bs.x += gp.bs.ptn[k][0]; 239 gp.bs.y += gp.bs.ptn[k][1]; 240 // 敵機の位置 241 if (gp.ex[0]) { 242 gp.em[0].x += gp.bs.ptn[k][0]; 243 gp.em[0].y += gp.bs.ptn[k][1]; 244 } 245 if (gp.ex[1]) { 246 gp.em[1].x += gp.bs.ptn[k][0]; 247 gp.em[1].y += gp.bs.ptn[k][1]; 248 } 249 } 250 // 251 // Enemy オブジェクト(プロパティ) 252 // 253 function Enemy(sw, bs) 254 { 255 this.image = new Image(); // 敵機画像 256 this.image.src = "image/enemy.gif"; 257 this.width = 27; // 敵機の幅 258 this.height = 41; // 敵機の高さ 259 this.x; // 敵機の位置(横) 260 this.y; // 敵機の位置(縦) 261 // 敵機の初期位置 262 if (sw == 0) { 263 this.x = bs.x - 150 + Math.floor(100 * Math.random()); 264 this.y = bs.y + bs.height - 80 + Math.floor(100 * Math.random()); 265 } 266 else { 267 this.x = bs.x + bs.width + 50 + Math.floor(100 * Math.random()); 268 this.y = bs.y + bs.height - 80 + Math.floor(100 * Math.random()); 269 } 270 return this; 271 } 272 // 273 // GamePanel オブジェクト(メソッド next) 274 // 275 GamePanel.prototype.next = function(sw) 276 { 277 clearInterval(gp.timerID); // タイマーの停止 278 if (sw == 0) 279 gcp_start(); 280 else 281 gop_start(); 282 }
056,057 行目( timer メソッド)
  Bullet オブジェクトのメソッド move( 140 行目~ 169 行目)によって,30 ms 毎に,自機の弾の移動処理を行っています.
072 行目~ 080 行目( draw メソッド)
  自機の弾を描画しています.
102,103 行目( onKeyDown メソッド)
  スペースキーを押した時の処理を追加しています.具体的には,最初の弾を発射する処理を行う Bullet オブジェクトのメソッド shoot( 173 行目~ 188 行目)を呼んでいます.
117 行目( My 関数)
  自機の弾に相当する Bullet オブジェクトを生成しています.
123 行目~ 136 行目( Bullet 関数)
  Bullet オブジェクトのプロパティを設定しています.132 行目の fire は,弾を発射しているか否かを表すプロパティです.「スペース」キーを押すと true に設定され,弾の発射が始まります.すべての弾(一度に撃てる弾の総数は 5 個,127 行目のプロパティ no_1 で指定)を発射し終わると,false に再設定されます.
  133 行目の ex は,画面上に発射された弾が存在するか否かを表しています.当然のことながら,初期状態では,弾は全く存在しません( 134,135 行目).なお,no は,126 行目で定義されており,画面に表示可能な弾の総数です.一度に撃てる弾の数,弾の速度,画面の大きさ等から見て,これ以上の数が一度に表示される必要はないと思います.
143 行目~ 149 行目( Bullet オブジェクトの move メソッド)
  存在する自機の弾の位置を変更し,その弾が画面外に出た場合は消去しています.
151 行目~ 168 行目( Bullet オブジェクトの move メソッド)
  弾を発射中であり,かつ,すべての弾を発射し終わっていない場合は,新しい弾を生成します.154 行目の for 文によって,弾の存在を示す配列 gp.my.bl.ex が false の位置を探し,そこに新しい弾を追加します.なお,弾の初期位置は,自機の先端です.また,最後の弾を発射し終わった場合は,スペースキーによる次の弾の発射を可能にします( 163 行目~ 166 行目).
  154 行目の「 i1 < gp.my.bl.no && sw; 」の記述から,sw の値が false になると,for 文によるループを抜け出します.ここでは,新しい弾を追加した時点で,159 行目において false に設定されますので,そこでループから抜け出します.この例の場合は,sw を使用せず,159 行目を break 文に変更した場合と同じ結果になりますが,多重のループから一気に一番外へ抜け出したいような場合には便利な方法だと思います.
173 行目~ 188 行目( Bullet オブジェクトの shoot メソッド)
  弾の発射を開始する関数であり,「スペース」キーを押したとき呼ばれます.弾の存在を示す配列 gp.my.bl.ex が false の位置に新しい弾を追加します.

ステップ5: 弾の発射(ボス,敵機)

  ここでは,ボス及び敵機から弾を発射させる処理を行っています.GamePanel の timer メソッド( GamePanel オブジェクトのメソッド),draw メソッド( GamePanel オブジェクトのメソッド),Boss 関数,及び,Enemy 関数を修正すると共に,ボスの弾に相当する Bullet_b 関数( Bullet_b オブジェクト)とその弾を処理するメソッド move,及び,shoot を Bullet_b オブジェクトに追加しています.さらに,敵機の弾に相当する Bullet_e 関数( Bullet_e オブジェクト)とその弾を処理するメソッド move,及び,shoot を Bullet_e オブジェクトに追加しています.

001 gp = null; // GamePanel オブジェクト 002 003 // 004 // GamePanel の開始 005 // 006 function gp_start() 007 { 008 // GamePanel オブジェクト 009 gp = new GamePanel(); 010 // タイマーのスタート 011 gp.timerID = setInterval('gp.timer()', 10); 012 // キーリスナの追加(キーが押された時) 013 mp.canvas.addEventListener("keydown", gp.onKeyDown, false); 014 mp.canvas.focus(); 015 // ボタンの表示制御 016 document.getElementById('method').style.display = "none"; 017 document.getElementById('start').style.display = "none"; 018 document.getElementById('first').style.display = "none"; 019 document.getElementById('finish').style.display = "none"; 020 document.getElementById('clear').style.display = ""; 021 document.getElementById('over').style.display = ""; 022 } 023 // 024 // GamePanel オブジェクト(プロパティ) 025 // 026 function GamePanel() 027 { 028 this.timerID = -1; 029 this.ct = 0; // カウンタ 030 this.my = new My(); // 自機 031 this.bs = new Boss(); // ボス 032 this.no = 2; // 敵機の数 033 this.em = new Array(); // 敵機 034 this.em[0] = new Enemy(0, this.bs); 035 this.em[1] = new Enemy(1, this.bs); 036 // 敵機の存在 037 this.ex = new Array(); 038 if (mp.level == 1) { 039 this.ex[0] = false; 040 this.ex[1] = false; 041 } 042 else { 043 this.ex[0] = true; 044 this.ex[1] = true; 045 } 046 return this; 047 } 048 // 049 // GamePanel オブジェクト(メソッド timer) 050 // 051 GamePanel.prototype.timer = function() 052 { 053 // 描画 054 gp.draw(); 055 // 移動処理 056 if (gp.ct%3 == 0) 057 gp.my.bl.move(); // 自機の弾 058 if (gp.ct%5 == 0) { 059 gp.bs.move(); // ボスと敵機の移動 060 for (let i1 = 0; i1 < gp.no; i1++) { // 敵機の弾 061 if (gp.ex[i1]) 062 gp.em[i1].bl.move(gp.em[i1]); 063 } 064 } 065 if (gp.ct%10 == 0) 066 gp.bs.bl.move(); // ボスの弾 067 // カウントアップ 068 gp.ct = (gp.ct + 1) % 300; 069 } 070 // 071 // GamePanel オブジェクト(メソッド draw) 072 // 073 GamePanel.prototype.draw = function() 074 { 075 // キャンバスのクリア 076 mp.ctx.clearRect(0, 0, mp.canvas.width, mp.canvas.height); 077 // 描画 078 // 自機と弾 079 mp.ctx.drawImage(gp.my.image, gp.my.x, gp.my.y); 080 for (let i1 = 0; i1 < gp.my.bl.no; i1++) { 081 if (gp.my.bl.ex[i1]) { 082 mp.ctx.beginPath(); 083 mp.ctx.fillStyle = "rgb(0, 255, 0)"; 084 mp.ctx.arc(gp.my.bl.x[i1], gp.my.bl.y[i1], gp.my.bl.r, 0, 2*Math.PI); 085 mp.ctx.fill(); 086 } 087 } 088 // ボスと弾 089 mp.ctx.drawImage(gp.bs.image, gp.bs.x, gp.bs.y); 090 for (let i1 = 0; i1 < gp.bs.bl.no; i1++) { 091 if (gp.bs.bl.ex[i1]) { 092 mp.ctx.beginPath(); 093 mp.ctx.fillStyle = "rgb(255, 165, 0)"; 094 mp.ctx.arc(gp.bs.bl.x[i1], gp.bs.bl.y[i1], gp.bs.bl.r, 0, 2*Math.PI); 095 mp.ctx.fill(); 096 } 097 } 098 // 敵機と弾 099 for (let i1 = 0; i1 < gp.no; i1++) { 100 if (gp.ex[i1]) { 101 mp.ctx.drawImage(gp.em[i1].image, gp.em[i1].x, gp.em[i1].y); 102 for (let i2 = 0; i2 < gp.em[i1].bl.no; i2++) { 103 if (gp.em[i1].bl.ex[i2]) { 104 mp.ctx.beginPath(); 105 mp.ctx.fillStyle = "rgb(255, 0, 0)"; 106 mp.ctx.arc(gp.em[i1].bl.x[i2], gp.em[i1].bl.y[i2], gp.em[i1].bl.r, 0, 2*Math.PI); 107 mp.ctx.fill(); 108 } 109 } 110 } 111 } 112 } 113 // 114 // GamePanel オブジェクト(メソッド onKeyDown) 115 // 116 GamePanel.prototype.onKeyDown = function(event) 117 { 118 if (event.keyCode == 38) // 「↑」キー 119 gp.my.y -= gp.my.v; 120 else if (event.keyCode == 40) // 「↓」キー 121 gp.my.y += gp.my.v; 122 else if (event.keyCode == 37) // 「←」キー 123 gp.my.x -= gp.my.v; 124 else if (event.keyCode == 39) // 「→」キー 125 gp.my.x += gp.my.v; 126 else if (event.keyCode == 32) // 「スペース」キー 127 gp.my.bl.shoot(); 128 } 129 // 130 // My オブジェクト(プロパティ) 131 // 132 function My() 133 { 134 this.image = new Image(); // 自機の画像 135 this.image.src = "image/my.gif"; 136 this.width = 50; // 自機の幅 137 this.height = 51; // 自機の高さ 138 this.x = mp.canvas.width / 2 - this.width / 2; // 自機の位置(横) 139 this.y = mp.canvas.height - this.height - 10; // 自機の位置(縦) 140 this.v = 20; // 移動キーが押されたときの移動量 141 this.bl = new Bullet(); // 弾 142 return this; 143 } 144 // 145 // Bullet オブジェクト(プロパティ) 146 // 147 function Bullet() 148 { 149 this.r = 12; // 弾の半径 150 this.no = 15; // 弾の全数 151 this.no_1 = 5; // 一度に撃てる弾数 152 this.ct = 0; // 現在の弾の数 153 this.x = new Array(); // 弾の位置(横) 154 this.y = new Array(); // 弾の位置(縦) 155 this.v = 30; // 弾の速さ 156 this.fire = false; // 弾を発射中か否か 157 this.ex = new Array(); // 弾の存在 158 for (let i1 = 0; i1 < this.no; i1++) 159 this.ex[i1] = false; 160 } 161 // 162 // Bullet オブジェクト(メソッド move) 163 // 164 Bullet.prototype.move = function() 165 { 166 // 弾の移動 167 for (let i1 = 0; i1 < gp.my.bl.no; i1++) { 168 if (gp.my.bl.ex[i1]) { 169 gp.my.bl.y[i1] -= gp.my.bl.v; 170 if (gp.my.bl.y[i1] < -gp.my.bl.r) 171 gp.my.bl.ex[i1] = false; 172 } 173 } 174 // 次の弾の発射 175 if (gp.my.bl.fire) { 176 if (gp.my.bl.ct < gp.my.bl.no_1) { 177 let sw = true; 178 for (let i1 = 0; i1 < gp.my.bl.no && sw; i1++) { 179 if (!gp.my.bl.ex[i1]) { 180 gp.my.bl.x[i1] = gp.my.x + gp.my.width / 2; 181 gp.my.bl.y[i1] = gp.my.y - gp.my.bl.r; 182 gp.my.bl.ex[i1] = true; 183 sw = false; 184 } 185 } 186 gp.my.bl.ct++; 187 if (gp.my.bl.ct >= gp.my.bl.no_1) { 188 gp.my.bl.fire = false; 189 gp.my.bl.ct = 0; 190 } 191 } 192 } 193 } 194 // 195 // Bullet オブジェクト(メソッド shoot,最初の弾の発射) 196 // 197 Bullet.prototype.shoot = function() 198 { 199 if (!gp.my.bl.fire) { 200 gp.my.bl.fire = true; 201 gp.my.bl.ct = 1; 202 let sw = true; 203 for (let i1 = 0; i1 < gp.my.bl.no && sw; i1++) { 204 if (!gp.my.bl.ex[i1]) { 205 gp.my.bl.x[i1] = gp.my.x + gp.my.width / 2; 206 gp.my.bl.y[i1] = gp.my.y - gp.my.bl.r; 207 gp.my.bl.ex[i1] = true; 208 sw = false; 209 } 210 } 211 } 212 } 213 // 214 // Boss オブジェクト(プロパティ) 215 // 216 function Boss() 217 { 218 this.image = new Image(); // ボス画像 219 this.image.src = "image/boss.gif"; 220 this.width = 66; // ボスの幅 221 this.height = 95; // ボスの高さ 222 let a = 100 + Math.floor((mp.canvas.width - 200 - this.width) * Math.random()); 223 this.x = a; // ボスの位置(横) 224 let b = 10 + Math.floor(20 * Math.random()); 225 this.y = b; // ボスの位置(縦) 226 this.bl = new Bullet_b(); // 弾 227 // 行動パターンの設定 228 this.ct = 1; 229 this.ptn1 = new Array(); 230 this.ptn1[0] = new Array(-5, 0, 50); 231 this.ptn1[1] = new Array(0, 20, 55); 232 this.ptn1[2] = new Array(5, 0, 105); 233 this.ptn1[3] = new Array(0, -20, 110); 234 this.ptn2 = new Array(); 235 this.ptn2[0] = new Array(5, 0, 50); 236 this.ptn2[1] = new Array(0, 20, 55); 237 this.ptn2[2] = new Array(-5, 0, 105); 238 this.ptn2[3] = new Array(0, -20, 110); 239 this.ptn = new Array(); 240 if (this.x > mp.canvas.width/2-this.width/2) 241 this.ptn = this.ptn1; 242 else 243 this.ptn = this.ptn2; 244 return this; 245 } 246 // 247 // Boss オブジェクト(メソッド move) 248 // 249 Boss.prototype.move = function() 250 { 251 // 移動 252 gp.bs.ct++; 253 if (gp.bs.ct > 110) 254 gp.bs.ct = 1; 255 // ボスの位置 256 let k = -1; 257 for (let i1 = 0; i1 < gp.bs.ptn.length-1 && k < 0; i1++) { 258 if (gp.bs.ct <= gp.bs.ptn[i1][2]) 259 k = i1; 260 } 261 if (k < 0) 262 k = gp.bs.ptn.length - 1; 263 gp.bs.x += gp.bs.ptn[k][0]; 264 gp.bs.y += gp.bs.ptn[k][1]; 265 // 敵機の位置 266 if (gp.ex[0]) { 267 gp.em[0].x += gp.bs.ptn[k][0]; 268 gp.em[0].y += gp.bs.ptn[k][1]; 269 } 270 if (gp.ex[1]) { 271 gp.em[1].x += gp.bs.ptn[k][0]; 272 gp.em[1].y += gp.bs.ptn[k][1]; 273 } 274 } 275 // 276 // Bullet_b オブジェクト(プロパティ) 277 // 278 function Bullet_b() 279 { 280 this.r = 12; // 弾の幅 281 this.no = 15; // 弾の全数 282 this.x = new Array(); // 弾の位置(横) 283 this.y = new Array(); // 弾の位置(縦) 284 this.v = 30; // 弾の速さ 285 this.vx = new Array(); // 横方向の弾の速さ 286 this.vy = new Array(); // 縦方向の弾の速さ 287 this.pr = 5; // 弾の発射間隔 288 this.ct = 0; 289 this.ex = new Array(); // 弾の存在 290 for (let i1 = 0; i1 < this.no; i1++) 291 this.ex[i1] = false; 292 } 293 // 294 // Bullet_b オブジェクト(メソッド move) 295 // 296 Bullet_b.prototype.move = function() 297 { 298 // 弾の移動 299 for (let i1 = 0; i1 < gp.bs.bl.no; i1++) { 300 if (gp.bs.bl.ex[i1]) { 301 gp.bs.bl.x[i1] += gp.bs.bl.vx[i1]; 302 gp.bs.bl.y[i1] += gp.bs.bl.vy[i1]; 303 if (gp.bs.bl.x[i1] < -gp.bs.bl.r || 304 gp.bs.bl.x[i1] > mp.canvas.width+gp.bs.bl.r || 305 gp.bs.bl.y[i1] > mp.canvas.height+gp.bs.bl.r) 306 gp.bs.bl.ex[i1] = false; 307 } 308 } 309 // 次の弾の発射 310 gp.bs.bl.ct = (gp.bs.bl.ct + 1) % gp.bs.bl.pr; 311 if (gp.bs.bl.ct == 0) 312 gp.bs.bl.shoot(); 313 } 314 // 315 // Bullet_b オブジェクト(メソッド shoot,弾の発射) 316 // 317 Bullet_b.prototype.shoot = function() 318 { 319 let sw = true; 320 for (let i1 = 1; i1 < gp.bs.bl.no && sw; i1++) { 321 if (!gp.bs.bl.ex[i1]) { 322 sw = false; 323 gp.bs.bl.ex[i1] = true; 324 gp.bs.bl.x[i1] = gp.bs.x + gp.bs.width / 2; 325 gp.bs.bl.y[i1] = gp.bs.y + gp.bs.height + gp.bs.bl.r; 326 let yt = gp.my.y + gp.my.height / 2 - gp.bs.bl.y[i1]; 327 let xt = gp.my.x + gp.my.width / 2 - gp.bs.bl.x[i1]; 328 let ang = Math.atan2(yt, xt); 329 gp.bs.bl.vx[i1] = Math.floor(gp.bs.bl.v * Math.cos(ang) + 0.5); 330 gp.bs.bl.vy[i1] = Math.floor(gp.bs.bl.v * Math.sin(ang) + 0.5); 331 } 332 } 333 } 334 // 335 // Enemy オブジェクト(プロパティ) 336 // 337 function Enemy(sw, bs) 338 { 339 this.image = new Image(); // 敵機画像 340 this.image.src = "image/enemy.gif"; 341 this.width = 27; // 敵機の幅 342 this.height = 41; // 敵機の高さ 343 this.x; // 敵機の位置(横) 344 this.y; // 敵機の位置(縦) 345 this.bl = new Bullet_e(); // 弾 346 // 敵機の初期位置 347 if (sw == 0) { 348 this.x = bs.x - 150 + Math.floor(100 * Math.random()); 349 this.y = bs.y + bs.height - 80 + Math.floor(100 * Math.random()); 350 } 351 else { 352 this.x = bs.x + bs.width + 50 + Math.floor(100 * Math.random()); 353 this.y = bs.y + bs.height - 80 + Math.floor(100 * Math.random()); 354 } 355 return this; 356 } 357 // 358 // Bullet_e オブジェクト(プロパティ) 359 // 360 function Bullet_e() 361 { 362 this.r = 7; // 弾の幅 363 this.no = 5; // 弾の全数 364 this.ct = 0; // 現在の弾の数 365 this.x = new Array(); // 弾の位置(横) 366 this.y = new Array(); // 弾の位置(縦) 367 this.v = 30; // 弾の速さ 368 this.vx = new Array(); // 横方向の弾の速さ 369 this.vy = new Array(); // 縦方向の弾の速さ 370 this.ex = new Array(); // 弾の存在 371 for (let i1 = 0; i1 < this.no; i1++) 372 this.ex[i1] = false; 373 } 374 // 375 // Bullet_e オブジェクト(メソッド move) 376 // 377 Bullet_e.prototype.move = function(em) 378 { 379 if (em.bl.ct < em.bl.no) 380 em.bl.ct++; 381 // 弾の移動 382 let sw = false; 383 for (let i1 = 0; i1 < em.bl.no; i1++) { 384 if (em.bl.ex[i1]) { 385 em.bl.x[i1] += em.bl.vx; 386 em.bl.y[i1] += em.bl.vy; 387 if (em.bl.x[i1] < -em.bl.r || em.bl.x[i1] > mp.canvas.width+em.bl.r || 388 em.bl.y[i1] > mp.canvas.height+em.bl.r) 389 em.bl.ex[i1] = false; 390 else 391 sw = true; 392 } 393 } 394 // 最初の弾の発射 395 if (!sw) 396 em.bl.shoot(em); 397 // 次の弾の発射 398 else { 399 if (em.bl.ct < em.bl.no) { 400 em.bl.x[em.bl.ct] = em.x + em.width / 2; 401 em.bl.y[em.bl.ct] = em.y + em.height + em.bl.r; 402 em.bl.ex[em.bl.ct] = true; 403 } 404 } 405 } 406 // 407 // Bullet_e オブジェクト(メソッド shoot,弾の発射) 408 // 409 Bullet_e.prototype.shoot = function(em) 410 { 411 em.bl.ct = 0; 412 em.bl.ex[0] = true; 413 for (let i1 = 1; i1 < em.bl.no; i1++) 414 em.bl.ex[i1] = false; 415 let ang = 0.25 * Math.PI + 0.5 * Math.PI * Math.random(); 416 em.bl.vx = Math.floor(em.bl.v * Math.cos(ang) + 0.5); 417 em.bl.vy = Math.floor(em.bl.v * Math.sin(ang) + 0.5); 418 em.bl.x[0] = em.x + em.width / 2; 419 em.bl.y[0] = em.y + em.height + em.bl.r; 420 } 421 // 422 // GamePanel オブジェクト(メソッド next) 423 // 424 GamePanel.prototype.next = function(sw) 425 { 426 clearInterval(gp.timerID); // タイマーの停止 427 if (sw == 0) 428 gcp_start(); 429 else 430 gop_start(); 431 }
058 行目~ 064 行目( timer メソッド)
  ボスと敵機及び敵の弾の位置を変更しています.
065 行目~ 066 行目( timer メソッド)
  ボスの弾の位置を変更しています.
089 行目~ 097 行目( draw メソッド)
  ボス及びその弾を描画しています.
099 行目~ 111 行目( draw メソッド)
  敵機及びその弾を描画しています.
226 行目( Boss 関数)
  ボスの弾に相当する Bullet_b オブジェクトを生成しています.
278 行目~ 292 行目( Bullet_b 関数)
  Bullet_b オブジェクトのプロパティを設定しています.287,288 行目は,弾を撃つ間隔を制御するためのプロパティです(後述).
299 行目~ 308 行目( Bullet_b オブジェクトの move メソッド)
  ボスの弾の移動を行い,画面の外に出た場合はその弾を消去しています.これらの処理は 100 ms の間隔で実行されます( 065,066 行目).
310 行目~ 312 行目( Bullet_b オブジェクトの move メソッド)
  288 行目の変数 ct を 1 だけ増加させ,その値が 287 行目の変数 pr で割り切れると(つまり,5 の倍数になると),次の弾が発射されます.結局,500 ms 毎にボスの弾の発射が行われます.
324 行目~ 328 行目( Bullet_b オブジェクトの shoot メソッド)
  ボスから見た自機の方向( ang )を,Math オブジェクトのメソッド atan2 を利用して計算しています.atan2 は,逆正接の計算,つまり,atan2(y, x) は,tan(ang) = y/x となるような角度 ang をラジアン単位で返します.この角度を利用して,ボスの弾の発射方向が決定されます( 329,330 行目).従って,自機が停止していれば必ず命中します.
345 行目( Enemy 関数)
  敵機の弾に相当する Bullet_e オブジェクトを生成しています.
360 行目~ 373 行目( Bullet_e 関数)
  Bullet_e オブジェクトのプロパティを設定しています.
379,380 行目( Bullet_e オブジェクトの move メソッド)
  敵機から発射された弾の数をカウントしています.
382 行目~ 393 行目( Bullet_e オブジェクトの move メソッド)
  敵機の弾の移動を行い,画面の外に出た場合はその弾を消去しています.
395,396 行目( Bullet_e オブジェクトの move メソッド)
  敵機は,5 個( 363 行目)の弾を連続的に発射し,そのすべての弾が消滅しない限り次の一連の弾を撃たないように設定してあります.383 行目~ 393 行目の for 文の実行後,発射された弾が存在しない状態になると,sw の値は false になっています.そのような場合,Bullet_e オブジェクトのメソッド shoot( 409 行目~ 420 行目)によって,次の一連の弾の最初の弾を発射します.
399 行目~ 403 行目( Bullet_e オブジェクトの move メソッド)
  発射した弾が 5 個に満たない場合は,次の弾を発射します.
409 行目~ 420 行目( Bullet_e オブジェクトの shoot メソッド)
  一連の弾の最初の弾を発射するための関数です.すべての弾は同じ方向に発射され( 415 行目~ 417 行目),その方向は,敵機の前方 ±45°の範囲内でランダムに決定されます.

ステップ6: 完成

  ここでは,弾の命中判定を付加し,ゲームを完成させます.以下の説明においては,すべての処理を同時に行うように記述してありますが,デバッグの容易さを考慮すると,

ボス及び敵機の動きを止め( timer メソッドの 057 行目をコメントアウトしておく),自機の弾に対する命中判定を行い,正しく動作することを確認する.

ボス及び敵機の弾に対する命中判定を行い,正しく動作することを確認する.

「ゲームクリア」,及び,「ゲームオーバー」ボタンを削除し,ボス及び敵機を動かし,正しく動作することを確認する.

のような順序に従って作成した方が良いと思います.修正するプログラムは,GamePanel の timer メソッド( GamePanel オブジェクトのメソッド)と Boss 関数だけです.ただし,レベル変更の確認を行うために使用していた「ゲームクリア」,及び,「ゲームオーバー」ボタンを削除するための処理はすべてのプログラム(パネル)に対して行う必要があります..

001 gp = null; // GamePanel オブジェクト 002 003 // 004 // GamePanel の開始 005 // 006 function gp_start() 007 { 008 // GamePanel オブジェクト 009 gp = new GamePanel(); 010 // タイマーのスタート 011 gp.timerID = setInterval('gp.timer()', 10); 012 // キーリスナの追加(キーが押された時) 013 mp.canvas.addEventListener("keydown", gp.onKeyDown, false); 014 mp.canvas.focus(); 015 // ボタンの表示制御 016 document.getElementById('method').style.display = "none"; 017 document.getElementById('start').style.display = "none"; 018 document.getElementById('first').style.display = "none"; 019 document.getElementById('finish').style.display = "none"; 020 } 021 // 022 // GamePanel オブジェクト(プロパティ) 023 // 024 function GamePanel() 025 { 026 this.timerID = -1; 027 this.ct = 0; // カウンタ 028 this.my = new My(); // 自機 029 this.bs = new Boss(); // ボス 030 this.no = 2; // 敵機の数 031 this.em = new Array(); // 敵機 032 this.em[0] = new Enemy(0, this.bs); 033 this.em[1] = new Enemy(1, this.bs); 034 // 敵機の存在 035 this.ex = new Array(); 036 if (mp.level == 1) { 037 this.ex[0] = false; 038 this.ex[1] = false; 039 } 040 else { 041 this.ex[0] = true; 042 this.ex[1] = true; 043 } 044 return this; 045 } 046 // 047 // GamePanel オブジェクト(メソッド timer) 048 // 049 GamePanel.prototype.timer = function() 050 { 051 // 描画 052 gp.draw(); 053 // 移動処理 054 if (gp.ct%3 == 0) 055 gp.my.bl.move(); // 自機の弾 056 if (gp.ct%5 == 0) { 057 gp.bs.move(); // ボスと敵機の移動 058 for (let i1 = 0; i1 < gp.no; i1++) { // 敵機の弾 059 if (gp.ex[i1]) 060 gp.em[i1].bl.move(gp.em[i1]); 061 } 062 } 063 if (gp.ct%10 == 0) 064 gp.bs.bl.move(); // ボスの弾 065 // 自機の弾による命中判定 066 let hit = false; 067 // ボスに対して 068 for (let i1 = 0; i1 < gp.my.bl.no && !hit; i1++) { 069 if (gp.my.bl.ex[i1]) { 070 let xb = gp.my.bl.x[i1]; 071 let yb = gp.my.bl.y[i1]; 072 let w = gp.bs.width / 2 + gp.my.bl.r; 073 let h = gp.bs.height / 2 + gp.my.bl.r; 074 let xt = gp.bs.x + gp.bs.width / 2; 075 let yt = gp.bs.y + gp.bs.height / 2; 076 if (xb > xt-w && xb < xt+w && yb > yt-h && yb < yt+h) { 077 gp.my.bl.ex[i1] = false; 078 gp.bs.h_ct++; 079 if (gp.bs.h_ct > gp.bs.h_max) { 080 hit = true; 081 clearInterval(gp.timerID); // タイマーの停止 082 gcp_start(); // ゲームクリア 083 } 084 } 085 } 086 } 087 // 敵機に対して 088 if (!hit) { 089 for (let i1 = 0; i1 < gp.no && !hit; i1++) { 090 if (gp.ex[i1]) { 091 for (let i2 = 0; i2 < gp.my.bl.no && !hit; i2++) { 092 if (gp.my.bl.ex[i2]) { 093 let xb = gp.my.bl.x[i2]; 094 let yb = gp.my.bl.y[i2]; 095 let w = gp.em[i1].width / 2 + gp.my.bl.r; 096 let h = gp.em[i1].height / 2 + gp.my.bl.r; 097 let xt = gp.em[i1].x + gp.em[i1].width / 2; 098 let yt = gp.em[i1].y + gp.em[i1].height / 2; 099 if (xb > xt-w && xb < xt+w && yb > yt-h && yb < yt+h) { 100 hit = true; 101 gp.ex[i1] = false; 102 gp.my.bl.ex[i2] = false; 103 } 104 } 105 } 106 } 107 } 108 } 109 // ボスの弾による命中判定 110 if (!hit) { 111 for (let i1 = 0; i1 < gp.bs.bl.no && !hit; i1++) { 112 if (gp.bs.bl.ex[i1]) { 113 xb = gp.bs.bl.x[i1]; 114 yb = gp.bs.bl.y[i1]; 115 w = gp.my.width / 2; 116 h = gp.my.width / 2; 117 xt = gp.my.x + w; 118 yt = gp.my.y + h; 119 if (xb > xt-w && xb < xt+w && yb > yt-h && yb < yt+h) { 120 hit = true; 121 clearInterval(gp.timerID); // タイマーの停止 122 gop_start(); // ゲームオーバー 123 } 124 } 125 } 126 } 127 // 敵機の弾による命中判定 128 if (!hit) { 129 for (let i1 = 0; i1 < gp.no && !hit; i1++) { 130 if (gp.ex[i1]) { 131 for (let i2 = 0; i2 < gp.em[i1].bl.no && !hit; i2++) { 132 if (gp.em[i1].bl.ex[i2]) { 133 let xb = gp.em[i1].bl.x[i2]; 134 let yb = gp.em[i1].bl.y[i2]; 135 let w = gp.my.width / 2; 136 let h = gp.my.width / 2; 137 let xt = gp.my.x + w; 138 let yt = gp.my.y + h; 139 if (xb > xt-w && xb < xt+w && yb > yt-h && yb < yt+h) { 140 hit = true; 141 clearInterval(gp.timerID); // タイマーの停止 142 gop_start(); // ゲームオーバー 143 } 144 } 145 } 146 } 147 } 148 } 149 // カウントアップ 150 gp.ct = (gp.ct + 1) % 300; 151 } 152 // 153 // GamePanel オブジェクト(メソッド draw) 154 // 155 GamePanel.prototype.draw = function() 156 { 157 // キャンバスのクリア 158 mp.ctx.clearRect(0, 0, mp.canvas.width, mp.canvas.height); 159 // 描画 160 // 自機と弾 161 mp.ctx.drawImage(gp.my.image, gp.my.x, gp.my.y); 162 for (let i1 = 0; i1 < gp.my.bl.no; i1++) { 163 if (gp.my.bl.ex[i1]) { 164 mp.ctx.beginPath(); 165 mp.ctx.fillStyle = "rgb(0, 255, 0)"; 166 mp.ctx.arc(gp.my.bl.x[i1], gp.my.bl.y[i1], gp.my.bl.r, 0, 2*Math.PI); 167 mp.ctx.fill(); 168 } 169 } 170 // ボスと弾 171 mp.ctx.drawImage(gp.bs.image, gp.bs.x, gp.bs.y); 172 for (let i1 = 0; i1 < gp.bs.bl.no; i1++) { 173 if (gp.bs.bl.ex[i1]) { 174 mp.ctx.beginPath(); 175 mp.ctx.fillStyle = "rgb(255, 165, 0)"; 176 mp.ctx.arc(gp.bs.bl.x[i1], gp.bs.bl.y[i1], gp.bs.bl.r, 0, 2*Math.PI); 177 mp.ctx.fill(); 178 } 179 } 180 // 敵機と弾 181 for (let i1 = 0; i1 < gp.no; i1++) { 182 if (gp.ex[i1]) { 183 mp.ctx.drawImage(gp.em[i1].image, gp.em[i1].x, gp.em[i1].y); 184 for (let i2 = 0; i2 < gp.em[i1].bl.no; i2++) { 185 if (gp.em[i1].bl.ex[i2]) { 186 mp.ctx.beginPath(); 187 mp.ctx.fillStyle = "rgb(255, 0, 0)"; 188 mp.ctx.arc(gp.em[i1].bl.x[i2], gp.em[i1].bl.y[i2], gp.em[i1].bl.r, 0, 2*Math.PI); 189 mp.ctx.fill(); 190 } 191 } 192 } 193 } 194 } 195 // 196 // GamePanel オブジェクト(メソッド onKeyDown) 197 // 198 GamePanel.prototype.onKeyDown = function(event) 199 { 200 if (event.keyCode == 38) // 「↑」キー 201 gp.my.y -= gp.my.v; 202 else if (event.keyCode == 40) // 「↓」キー 203 gp.my.y += gp.my.v; 204 else if (event.keyCode == 37) // 「←」キー 205 gp.my.x -= gp.my.v; 206 else if (event.keyCode == 39) // 「→」キー 207 gp.my.x += gp.my.v; 208 else if (event.keyCode == 32) // 「スペース」キー 209 gp.my.bl.shoot(); 210 } 211 // 212 // My オブジェクト(プロパティ) 213 // 214 function My() 215 { 216 this.image = new Image(); // 自機の画像 217 this.image.src = "image/my.gif"; 218 this.width = 50; // 自機の幅 219 this.height = 51; // 自機の高さ 220 this.x = mp.canvas.width / 2 - this.width / 2; // 自機の位置(横) 221 this.y = mp.canvas.height - this.height - 10; // 自機の位置(縦) 222 this.v = 20; // 移動キーが押されたときの移動量 223 this.bl = new Bullet(); // 弾 224 return this; 225 } 226 // 227 // Bullet オブジェクト(プロパティ) 228 // 229 function Bullet() 230 { 231 this.r = 12; // 弾の半径 232 this.no = 15; // 弾の全数 233 this.no_1 = 5; // 一度に撃てる弾数 234 this.ct = 0; // 現在の弾の数 235 this.x = new Array(); // 弾の位置(横) 236 this.y = new Array(); // 弾の位置(縦) 237 this.v = 30; // 弾の速さ 238 this.fire = false; // 弾を発射中か否か 239 this.ex = new Array(); // 弾の存在 240 for (let i1 = 0; i1 < this.no; i1++) 241 this.ex[i1] = false; 242 } 243 // 244 // Bullet オブジェクト(メソッド move) 245 // 246 Bullet.prototype.move = function() 247 { 248 // 弾の移動 249 for (let i1 = 0; i1 < gp.my.bl.no; i1++) { 250 if (gp.my.bl.ex[i1]) { 251 gp.my.bl.y[i1] -= gp.my.bl.v; 252 if (gp.my.bl.y[i1] < -gp.my.bl.r) 253 gp.my.bl.ex[i1] = false; 254 } 255 } 256 // 次の弾の発射 257 if (gp.my.bl.fire) { 258 if (gp.my.bl.ct < gp.my.bl.no_1) { 259 let sw = true; 260 for (let i1 = 0; i1 < gp.my.bl.no && sw; i1++) { 261 if (!gp.my.bl.ex[i1]) { 262 gp.my.bl.x[i1] = gp.my.x + gp.my.width / 2; 263 gp.my.bl.y[i1] = gp.my.y - gp.my.bl.r; 264 gp.my.bl.ex[i1] = true; 265 sw = false; 266 } 267 } 268 gp.my.bl.ct++; 269 if (gp.my.bl.ct >= gp.my.bl.no_1) { 270 gp.my.bl.fire = false; 271 gp.my.bl.ct = 0; 272 } 273 } 274 } 275 } 276 // 277 // Bullet オブジェクト(メソッド shoot,最初の弾の発射) 278 // 279 Bullet.prototype.shoot = function() 280 { 281 if (!gp.my.bl.fire) { 282 gp.my.bl.fire = true; 283 gp.my.bl.ct = 1; 284 let sw = true; 285 for (let i1 = 0; i1 < gp.my.bl.no && sw; i1++) { 286 if (!gp.my.bl.ex[i1]) { 287 gp.my.bl.x[i1] = gp.my.x + gp.my.width / 2; 288 gp.my.bl.y[i1] = gp.my.y - gp.my.bl.r; 289 gp.my.bl.ex[i1] = true; 290 sw = false; 291 } 292 } 293 } 294 } 295 // 296 // Boss オブジェクト(プロパティ) 297 // 298 function Boss() 299 { 300 this.image = new Image(); // ボス画像 301 this.image.src = "image/boss.gif"; 302 this.width = 66; // ボスの幅 303 this.height = 95; // ボスの高さ 304 let a = 100 + Math.floor((mp.canvas.width - 200 - this.width) * Math.random()); 305 this.x = a; // ボスの位置(横) 306 let b = 10 + Math.floor(20 * Math.random()); 307 this.y = b; // ボスの位置(縦) 308 this.bl = new Bullet_b(); // 弾 309 this.h_ct = 0; // 命中した弾の数 310 this.h_max = 5; // 耐えうる命中した弾の数 311 // 行動パターンの設定 312 this.ct = 1; 313 this.ptn1 = new Array(); 314 this.ptn1[0] = new Array(-5, 0, 50); 315 this.ptn1[1] = new Array(0, 20, 55); 316 this.ptn1[2] = new Array(5, 0, 105); 317 this.ptn1[3] = new Array(0, -20, 110); 318 this.ptn2 = new Array(); 319 this.ptn2[0] = new Array(5, 0, 50); 320 this.ptn2[1] = new Array(0, 20, 55); 321 this.ptn2[2] = new Array(-5, 0, 105); 322 this.ptn2[3] = new Array(0, -20, 110); 323 this.ptn = new Array(); 324 if (this.x > mp.canvas.width/2-this.width/2) 325 this.ptn = this.ptn1; 326 else 327 this.ptn = this.ptn2; 328 return this; 329 } 330 // 331 // Boss オブジェクト(メソッド move) 332 // 333 Boss.prototype.move = function() 334 { 335 // 移動 336 gp.bs.ct++; 337 if (gp.bs.ct > 110) 338 gp.bs.ct = 1; 339 // ボスの位置 340 let k = -1; 341 for (let i1 = 0; i1 < gp.bs.ptn.length-1 && k < 0; i1++) { 342 if (gp.bs.ct <= gp.bs.ptn[i1][2]) 343 k = i1; 344 } 345 if (k < 0) 346 k = gp.bs.ptn.length - 1; 347 gp.bs.x += gp.bs.ptn[k][0]; 348 gp.bs.y += gp.bs.ptn[k][1]; 349 // 敵機の位置 350 if (gp.ex[0]) { 351 gp.em[0].x += gp.bs.ptn[k][0]; 352 gp.em[0].y += gp.bs.ptn[k][1]; 353 } 354 if (gp.ex[1]) { 355 gp.em[1].x += gp.bs.ptn[k][0]; 356 gp.em[1].y += gp.bs.ptn[k][1]; 357 } 358 } 359 // 360 // Bullet_b オブジェクト(プロパティ) 361 // 362 function Bullet_b() 363 { 364 this.r = 12; // 弾の幅 365 this.no = 15; // 弾の全数 366 this.x = new Array(); // 弾の位置(横) 367 this.y = new Array(); // 弾の位置(縦) 368 this.v = 30; // 弾の速さ 369 this.vx = new Array(); // 横方向の弾の速さ 370 this.vy = new Array(); // 縦方向の弾の速さ 371 this.pr = 5; // 弾の発射間隔 372 this.ct = 0; 373 this.ex = new Array(); // 弾の存在 374 for (let i1 = 0; i1 < this.no; i1++) 375 this.ex[i1] = false; 376 } 377 // 378 // Bullet_b オブジェクト(メソッド move) 379 // 380 Bullet_b.prototype.move = function() 381 { 382 // 弾の移動 383 for (let i1 = 0; i1 < gp.bs.bl.no; i1++) { 384 if (gp.bs.bl.ex[i1]) { 385 gp.bs.bl.x[i1] += gp.bs.bl.vx[i1]; 386 gp.bs.bl.y[i1] += gp.bs.bl.vy[i1]; 387 if (gp.bs.bl.x[i1] < -gp.bs.bl.r || 388 gp.bs.bl.x[i1] > mp.canvas.width+gp.bs.bl.r || 389 gp.bs.bl.y[i1] > mp.canvas.height+gp.bs.bl.r) 390 gp.bs.bl.ex[i1] = false; 391 } 392 } 393 // 次の弾の発射 394 gp.bs.bl.ct = (gp.bs.bl.ct + 1) % gp.bs.bl.pr; 395 if (gp.bs.bl.ct == 0) 396 gp.bs.bl.shoot(); 397 } 398 // 399 // Bullet_b オブジェクト(メソッド shoot,弾の発射) 400 // 401 Bullet_b.prototype.shoot = function() 402 { 403 let sw = true; 404 for (let i1 = 1; i1 < gp.bs.bl.no && sw; i1++) { 405 if (!gp.bs.bl.ex[i1]) { 406 sw = false; 407 gp.bs.bl.ex[i1] = true; 408 gp.bs.bl.x[i1] = gp.bs.x + gp.bs.width / 2; 409 gp.bs.bl.y[i1] = gp.bs.y + gp.bs.height + gp.bs.bl.r; 410 let yt = gp.my.y + gp.my.height / 2 - gp.bs.bl.y[i1]; 411 let xt = gp.my.x + gp.my.width / 2 - gp.bs.bl.x[i1]; 412 let ang = Math.atan2(yt, xt); 413 gp.bs.bl.vx[i1] = Math.floor(gp.bs.bl.v * Math.cos(ang) + 0.5); 414 gp.bs.bl.vy[i1] = Math.floor(gp.bs.bl.v * Math.sin(ang) + 0.5); 415 } 416 } 417 } 418 // 419 // Enemy オブジェクト(プロパティ) 420 // 421 function Enemy(sw, bs) 422 { 423 this.image = new Image(); // 敵機画像 424 this.image.src = "image/enemy.gif"; 425 this.width = 27; // 敵機の幅 426 this.height = 41; // 敵機の高さ 427 this.x; // 敵機の位置(横) 428 this.y; // 敵機の位置(縦) 429 this.bl = new Bullet_e(); // 弾 430 // 敵機の初期位置 431 if (sw == 0) { 432 this.x = bs.x - 150 + Math.floor(100 * Math.random()); 433 this.y = bs.y + bs.height - 80 + Math.floor(100 * Math.random()); 434 } 435 else { 436 this.x = bs.x + bs.width + 50 + Math.floor(100 * Math.random()); 437 this.y = bs.y + bs.height - 80 + Math.floor(100 * Math.random()); 438 } 439 return this; 440 } 441 // 442 // Bullet_e オブジェクト(プロパティ) 443 // 444 function Bullet_e() 445 { 446 this.r = 7; // 弾の幅 447 this.no = 5; // 弾の全数 448 this.ct = 0; // 現在の弾の数 449 this.x = new Array(); // 弾の位置(横) 450 this.y = new Array(); // 弾の位置(縦) 451 this.v = 30; // 弾の速さ 452 this.vx = new Array(); // 横方向の弾の速さ 453 this.vy = new Array(); // 縦方向の弾の速さ 454 this.ex = new Array(); // 弾の存在 455 for (let i1 = 0; i1 < this.no; i1++) 456 this.ex[i1] = false; 457 } 458 // 459 // Bullet_e オブジェクト(メソッド move) 460 // 461 Bullet_e.prototype.move = function(em) 462 { 463 if (em.bl.ct < em.bl.no) 464 em.bl.ct++; 465 // 弾の移動 466 let sw = false; 467 for (let i1 = 0; i1 < em.bl.no; i1++) { 468 if (em.bl.ex[i1]) { 469 em.bl.x[i1] += em.bl.vx; 470 em.bl.y[i1] += em.bl.vy; 471 if (em.bl.x[i1] < -em.bl.r || em.bl.x[i1] > mp.canvas.width+em.bl.r || 472 em.bl.y[i1] > mp.canvas.height+em.bl.r) 473 em.bl.ex[i1] = false; 474 else 475 sw = true; 476 } 477 } 478 // 最初の弾の発射 479 if (!sw) 480 em.bl.shoot(em); 481 // 次の弾の発射 482 else { 483 if (em.bl.ct < em.bl.no) { 484 em.bl.x[em.bl.ct] = em.x + em.width / 2; 485 em.bl.y[em.bl.ct] = em.y + em.height + em.bl.r; 486 em.bl.ex[em.bl.ct] = true; 487 } 488 } 489 } 490 // 491 // Bullet_e オブジェクト(メソッド shoot,弾の発射) 492 // 493 Bullet_e.prototype.shoot = function(em) 494 { 495 em.bl.ct = 0; 496 em.bl.ex[0] = true; 497 for (let i1 = 1; i1 < em.bl.no; i1++) 498 em.bl.ex[i1] = false; 499 let ang = 0.25 * Math.PI + 0.5 * Math.PI * Math.random(); 500 em.bl.vx = Math.floor(em.bl.v * Math.cos(ang) + 0.5); 501 em.bl.vy = Math.floor(em.bl.v * Math.sin(ang) + 0.5); 502 em.bl.x[0] = em.x + em.width / 2; 503 em.bl.y[0] = em.y + em.height + em.bl.r; 504 }
309,310 行目( Boss 関数)
  ボスは,1 発の弾だけでは撃墜されないようにするため,命中した弾に対するカウンタ( 309 行目),及び,何発以上の弾が当たると撃墜されるのかを示すプロパティ( 310 行目)を設定しておきます.
068 行目~ 086 行目( timer メソッド)
  自機の弾のボスに対する命中判定を行っています.ボスは,画像の幅と高さのそれぞれに弾の直径を加えた大きさを持つ矩形として扱い,この中に弾の中心が入った場合は命中と判定しています.命中した場合は,命中した弾の数をカウントアップし( 078 行目),その数が上限を超えていたら( 079 行目),ゲームクリアの状態に移行します.
089 行目~ 107 行目( timer メソッド)
  自機の弾の敵機に対する命中判定を行っています.ボスの場合と同様,敵機は,画像の幅と高さのそれぞれに弾の直径を加えた大きさを持つ矩形として扱い,この中に弾の中心が入った場合は命中と判定しています.命中した場合は,その敵機を削除します.
111 行目~ 125 行目( timer メソッド)
  ボスの弾の自機に対する命中判定を行っています.自機は,画像の幅から決まる正方形として扱い,この中に弾の中心が入った場合は命中と判定しています.命中判定を,自機の弾に対する判定より多少甘くしています.命中した場合は,ゲームオーバーになります.
129 行目~ 147 行目( timer メソッド)
  敵機の弾の自機に対する命中判定を行っています.自機は,画像の幅から決まる正方形として扱い,この中に弾の中心が入った場合は命中と判定しています.ボスの弾と同様,命中判定を,自機の弾に対する判定より多少甘くしています.命中した場合は,ゲームオーバーになります.

ステップ6: 完成( BGM 付き)

  参考のため,BGM を付加した例を示しておきます.追加・修正した部分は,以下の通りです.なお,BGM は,平成 25 年度に本学を卒業した斉藤亮太さんに作成してもらいました.

HTML ファイル: 20 行目

GamePanel: 9 ~ 11 行目,84 ~ 85 行目,126 ~ 127 行目,148 ~ 149 行目

01 <!DOCTYPE HTML> 02 <HTML> 03 <HEAD> 04 <TITLE>シューティングゲーム:ステップ6(完成)</TITLE> 05 <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8"> 06 <LINK REL="stylesheet" TYPE="text/css" HREF="../../../master.css"> 07 <SCRIPT TYPE="text/javascript" SRC="main/MainPanel.js"></SCRIPT> 08 <SCRIPT TYPE="text/javascript" SRC="start/StartPanel.js"></SCRIPT> 09 <SCRIPT TYPE="text/javascript" SRC="game/GamePanel.js"></SCRIPT> 10 <SCRIPT TYPE="text/javascript" SRC="clear/GameClearPanel.js"></SCRIPT> 11 <SCRIPT TYPE="text/javascript" SRC="over/GameOverPanel.js"></SCRIPT> 12 </HEAD> 13 <BODY CLASS="eeffee" onLoad="mp_start()"> 14 <H1>シューティングゲーム:ステップ6(完成)</H1> 15 <CANVAS ID="canvas_e" STYLE="background-color: #ffffff;" WIDTH="500" HEIGHT="500" TABINDEX="1"></CANVAS><BR> 16 <A HREF="method.htm" TARGET="method"><BUTTON ID="method" CLASS="std">遊び方</BUTTON></A> 17 <BUTTON ID="start" CLASS="std" onClick="gp_start()">ゲーム開始</BUTTON> 18 <BUTTON ID="first" CLASS="std" onClick="st_start()">最初から再開</BUTTON> 19 <BUTTON ID="finish" CLASS="std" onClick="mp.finish()">ゲーム終了</BUTTON> 20 <AUDIO ID="BGM" LOOP SRC="Shoot_BGM.mp3"></AUDIO> <!-- BGMのために追加 --> 21 </BODY> 22 </HTML>
001 gp = null; // GamePanel オブジェクト 002 003 // 004 // GamePanel の開始 005 // 006 function gp_start() 007 { 008 // BGM の再生 009 document.getElementById('BGM').volume = 0.5; 010 document.getElementById('BGM').play(); 011 document.getElementById('BGM').currentTime = 0.5; // 約0.5秒の空白をスキップ 012 // GamePanel オブジェクト 013 gp = new GamePanel();

댓글