情報学部 菅沼ホーム JavaScript 目次 索引

シューティングゲーム

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

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

    1. HTML ファイル

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

      <!DOCTYPE HTML>
      <HTML>
      <HEAD>
      	<TITLE>シューティングゲーム:ステップ1</TITLE>
      	<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8">
      	<META NAME=viewport CONTENT="width=device-width, initial-scale=1">
      	<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>
      				

    2. 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	}
      				

    3. 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";
      }
      				

    4. 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 オブジェクトのメソッドです.ゲーム完成時には削除されます.

    5. 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";
      }
      				

    6. 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. ステップ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. ステップ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. ステップ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. ステップ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. ステップ6: 完成

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

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

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

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

    のような順序に従って作成した方が良いと思います.修正するプログラムは,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 メソッド)

      敵機の弾の自機に対する命中判定を行っています.自機は,画像の幅から決まる正方形として扱い,この中に弾の中心が入った場合は命中と判定しています.ボスの弾と同様,命中判定を,自機の弾に対する判定より多少甘くしています.命中した場合は,ゲームオーバーになります.

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

情報学部 菅沼ホーム JavaScript 目次 索引