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

ブロック崩し

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

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

    1. HTML ファイル

        「ゲーム枠の作成」における HTML ファイルとほとんど同じですが,「ゲームクリア」ボタンと「ゲームオーバー」ボタンは除き,ラケットを左右に移動させるため,「左」ボタンと「右」ボタンが追加してあります.

      <!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="300" HEIGHT="300"></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="left" CLASS="std" onClick="gp.move(0)">左</BUTTON>
      	<BUTTON ID="right" CLASS="std" onClick="gp.move(1)">右</BUTTON>
      </BODY>
      </HTML>
      				

    2. MainPanel

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

      mp = null;   // MainPanel オブジェクト
      
      			//
      			// MainPanel の開始
      			//
      function mp_start()
      {
      					// キャンバス情報
      	let canvas = document.getElementById('canvas_e');   // キャンバス要素の取得
      	let ctx    = canvas.getContext('2d');   // キャンバスからコンテキストを取得
      					// MainPanel オブジェクト
      	mp = new MainPanel(canvas, ctx);
      					// StartPanel の表示
      	st_start();
      }
      			//
      			// MainPanel オブジェクト(プロパティ)
      			//
      function MainPanel(canvas, ctx)
      {
      	this.canvas = canvas;   // キャンバス要素
      	this.ctx    = ctx;   // キャンバスのコンテキスト
      	this.level  = 1;   // ゲームレベル
      	return this;
      }
      			//
      			// MainPanel オブジェクト(メソッド)
      			//
      MainPanel.prototype.finish = function()
      {
      					// キャンバスのクリア
      	mp.ctx.clearRect(0, 0, mp.canvas.width, mp.canvas.height);
      					// ボタンを非表示
      	document.getElementById('method').style.display = "none";
      	document.getElementById('start').style.display = "none";
      	document.getElementById('first').style.display = "none";
      	document.getElementById('finish').style.display = "none";
      	document.getElementById('left').style.display = "none";
      	document.getElementById('right').style.display = "none";
      }
      				

    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('left').style.display = "none";
      	document.getElementById('right').style.display = "none";
      	document.getElementById('start').innerHTML = "ゲーム開始";
      }
      				

    4. GamePanel

        GamePanel,実際のゲームを実現するプログラムです.従って,「ゲーム枠の作成」における GamePanel とは,ゲームの種類によってその内容は大きく異なります.今後,このプログラムを完成させていくことになりますが,ここでは,ブロック,ラケット,及び,ボールを表示しています.

      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.draw()', 50);
      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('left').style.display = "";
      018		document.getElementById('right').style.display = "";
      019	}
      020				//
      021				// GamePanel オブジェクト(プロパティ)
      022				//
      023	function GamePanel()
      024	{
      025		this.timerID = -1;
      026		this.blk = new Block();   // Block オブジェクト
      027		this.rk = new Racket();   // Racket オブジェクト
      028		this.bl = new Ball(this.blk);   // Ball オブジェクト
      029		return this;
      030	}
      031				//
      032				// GamePanel オブジェクト(メソッド draw)
      033				//
      034	GamePanel.prototype.draw = function()
      035	{
      036						// キャンバスのクリア
      037		mp.ctx.clearRect(0, 0, mp.canvas.width, mp.canvas.height);
      038						// 描画
      039								// ブロック
      040		for (let i1 = 0; i1 < gp.blk.row; i1++) {
      041			for (let i2 = 0; i2 < gp.blk.col; i2++) {
      042				if (gp.blk.ex[i1][i2])
      043					mp.ctx.drawImage(gp.blk.block, gp.blk.width*i2, gp.blk.height*i1);
      044			}
      045		}
      046								// ラケット
      047		mp.ctx.beginPath();
      048		mp.ctx.fillStyle = "rgb(0, 255, 0)";
      049		mp.ctx.fillRect(gp.rk.x, gp.rk.y, gp.rk.width, gp.rk.height);
      050		mp.ctx.fill();
      051								// ボール
      052		mp.ctx.beginPath();
      053		mp.ctx.fillStyle = "rgb(255, 0, 0)";
      054		mp.ctx.arc(gp.bl.x, gp.bl.y, gp.bl.r, 0, 2*Math.PI);
      055		mp.ctx.fill();
      056						// 移動
      057		gp.bl.x += gp.bl.vx;
      058		gp.bl.y += gp.bl.vy;
      059	}
      060				//
      061				// GamePanel オブジェクト(メソッド move)
      062				//
      063	GamePanel.prototype.move = function(sw)
      064	{
      065		clearInterval(gp.timerID);   // タイマーの停止
      066		if (sw == 0)
      067			gcp_start();
      068		else
      069			gop_start();
      070	}
      071				//
      072				// Block オブジェクト(プロパティ)
      073				//
      074	function Block()
      075	{
      076		this.block = new Image();   // ブロックの画像
      077		this.width = 75;   // ブロックの幅
      078		this.height = 38;   // ブロックの高さ
      079		this.row = 2;   // ブロックの行数
      080		this.col = 4;   // ブロックの列数
      081		this.number = this.row * this.col;   // ブロックの数
      082		this.ex = new Array();   // ブロックの状態(存在するか否か)
      083		for (let i1 = 0; i1 < this.row; i1++) {
      084			this.ex[i1] = new Array();
      085			for (let i2 = 0; i2 < this.col; i2++)
      086				this.ex[i1][i2] = true;
      087		}
      088						// ブロック画像の読み込み
      089		this.block.src = "image/block.jpg";
      090		return this;
      091	}
      092				//
      093				// Racket オブジェクト(プロパティ)
      094				//
      095	function Racket()
      096	{
      097		this.width;   // ラケットの幅
      098		this.height = 20;   // ラケットの高さ
      099		this.x;   // ラケットの横位置
      100		this.y;   // ラケットの縦位置
      101						// 幅と位置の設定
      102		if (mp.level == 1)
      103			this.width = 80;
      104		else
      105			this.width = 40;
      106		this.x = mp.canvas.width / 2 - this.width / 2;
      107		this.y = mp.canvas.height - this.height;
      108		return this;
      109	}
      110				//
      111				// Ball オブジェクト(プロパティ)
      112				//
      113	function Ball(blk)
      114	{
      115		this.r = 7;   // ボールの半径
      116		this.x;   // ボールの横位置
      117		this.y;   // ボールの縦位置
      118		this.vx;   // ボールの横方向速度成分
      119		this.vy;   // ボールの縦方向速度成分
      120						// 位置と速度の設定
      121		this.x  = this.r + Math.floor(Math.random() * (mp.canvas.width - 2 * this.r));
      122		this.y  = blk.height * blk.row + 10;
      123		this.vx = 0;
      124		this.vy = 0;
      125		return this;
      126	}
      				
      009 行目( gp_start 関数)

        GamePanel オブジェクトを生成し,その結果をグローバル変数 gp( 001 行目)に代入しています.具体的には,023 行目~ 030 行目に記述された GamePanel 関数が実行されます.

      011 行目( gp_start 関数)

        タイマーを設定しています.このタイマーにより,50 ms 毎に,GamePanel オブジェクトの draw メソッド( 034 行目~ 059 行目)が実行されます.

      017,018 行目( gp_start 関数)

        ラケットを左右に移動させるための「左」ボタンと「右」ボタンを表示しています.これらのボタンがクリックされた時の処理は,GamePanel オブジェクトの move メソッド( 063 行目~ 070 行目)で実行されますが,現時点では,それぞれ,ゲームクリア及びゲームオーバーの状態に移行するようにプログラムしてあります.

      26 行目~ 28 行目( GamePanel 関数)

        今後,その機能を拡張していく可能性がありますので,ブロック,ラケット,及び,ボールは,別のオブジェクト( 074 行目~ 091 行目,095 行目~ 109 行目,及び,113 行目~ 126 行目)として定義してあります.ここでは,それらに対するオブジェクトを生成しています.

      040 行目~ 045 行目( draw メソッド)

        存在するブロックを表示しています.ブロックが存在するか否かを表現するために,Bolck オブジェクト( 074 行目~ 091 行目)で定義されているプロパティ ex ( 2 次元配列)を使用しています.

      047 行目~ 050 行目( draw メソッド)

        ラケットを緑色の塗りつぶされた矩形として描画しています.

      052 行目~ 055 行目( draw メソッド)

        ボールを赤色の塗りつぶされた円として描画しています.

      057,058 行目( draw メソッド)

        ボールを移動しています.ただし,現時点では,ボールの速度 gp.bl.vx,gp.bl.vy を 0 としていますので移動しません.

      074 行目~ 091 行目( Block 関数)

        Block オブジェクトのプロパティを設定しています.プロパティ ex は,ブロックが存在するか否かを表すための 2 次元配列です.ここに示すように,2 次元配列は,まず配列を定義し( 082 行目),その配列の各要素を再び配列として定義する( 084 行目)ことによって可能です.初期状態では,すべてのブロックが存在しますので,すべての要素を true で初期設定しています.084 行目~ 086 行目の代わりに,次の 1 行でも構いません.
      	this.ex[i1] = new Array(true, true, true, true);					
        一般に,配列の各要素を配列として定義することによって,多次元配列を定義することが可能です.以下に示すのは 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);					
      095 行目~ 109 行目( Racket 関数)

        Racket オブジェクトのプロパティを設定しています.ゲームレベルによってラケットの幅を変更する( 102 行目~ 105 行目)と共に,その初期位置を画面下部の中央としています( 106,107 行目).

      113 行目~ 126 行目( Ball 関数)

        Ball オブジェクトのプロパティを設定しています.ボールの横位置はランダム,また,縦位置はブロックの 10 ピクセル下に設定してあります.

    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('left').style.display = "none";
      	document.getElementById('right').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('left').style.display = "none";
      	document.getElementById('right').style.display = "none";
      	document.getElementById('start').innerHTML = "現レベルで再開";
      }
      				

  2. ステップ2: ボールの動き

      ボールを移動させ,壁に当たると跳ね返るようにプログラムを修正します.また,画面切り替えの確認は終了しましたので,ボタンをクリックすることによってラケットを左右に移動可能にします.修正するプログラムは,GamePanel の Ball 関数,draw メソッド( GamePanel オブジェクトのメソッド),及び,move メソッド( GamePanel オブジェクトのメソッド)です.まず,Ball 関数の変更部分から順に説明していきます.

    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.draw()', 50);
    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('left').style.display = "";
    018		document.getElementById('right').style.display = "";
    019	}
    020				//
    021				// GamePanel オブジェクト(プロパティ)
    022				//
    023	function GamePanel()
    024	{
    025		this.timerID = -1;
    026		this.blk = new Block();   // Block オブジェクト
    027		this.rk = new Racket();   // Racket オブジェクト
    028		this.bl = new Ball(this.blk);   // Ball オブジェクト
    029		return this;
    030	}
    031				//
    032				// GamePanel オブジェクト(メソッド draw)
    033				//
    034	GamePanel.prototype.draw = function()
    035	{
    036						// キャンバスのクリア
    037		mp.ctx.clearRect(0, 0, mp.canvas.width, mp.canvas.height);
    038						// 描画
    039								// ブロック
    040		for (let i1 = 0; i1 < gp.blk.row; i1++) {
    041			for (let i2 = 0; i2 < gp.blk.col; i2++) {
    042				if (gp.blk.ex[i1][i2])
    043					mp.ctx.drawImage(gp.blk.block, gp.blk.width*i2, gp.blk.height*i1);
    044			}
    045		}
    046								// ラケット
    047		mp.ctx.beginPath();
    048		mp.ctx.fillStyle = "rgb(0, 255, 0)";
    049		mp.ctx.fillRect(gp.rk.x, gp.rk.y, gp.rk.width, gp.rk.height);
    050		mp.ctx.fill();
    051								// ボール
    052		mp.ctx.beginPath();
    053		mp.ctx.fillStyle = "rgb(255, 0, 0)";
    054		mp.ctx.arc(gp.bl.x, gp.bl.y, gp.bl.r, 0, 2*Math.PI);
    055		mp.ctx.fill();
    056						// 移動
    057		gp.bl.x += gp.bl.vx;
    058		gp.bl.y += gp.bl.vy;
    059						// 壁に衝突したときの処理
    060		let sw = 0;
    061								// 下へ移動中
    062		if (gp.bl.vy > 0) {
    063			if (gp.bl.y >= mp.canvas.height-gp.bl.r) {   // 下の壁に衝突
    064				gp.bl.y  = mp.canvas.height - gp.bl.r;
    065				gp.bl.vy = -gp.bl.vy;
    066				sw       = 1;
    067			}
    068		}
    069								// 上へ移動中
    070		else {
    071			if (gp.bl.y <= gp.bl.r) {   // 上の壁に衝突
    072				gp.bl.y  = gp.bl.r;
    073				gp.bl.vy = -gp.bl.vy;
    074				sw       = 1;
    075			}
    076		}
    077						// 上下の壁に衝突していない場合
    078		if (sw == 0) {
    079								// 右方向へ移動中
    080			if (gp.bl.vx > 0) {
    081				if (gp.bl.x >= mp.canvas.width-gp.bl.r) {   // 右の壁に衝突
    082					gp.bl.x  = mp.canvas.width - gp.bl.r;
    083					gp.bl.vx = -gp.bl.vx;
    084				}
    085			}
    086								// 左方向へ移動中
    087			else {
    088				if (gp.bl.x <= gp.bl.r) {   // 左の壁に衝突
    089					gp.bl.x  = gp.bl.r;
    090					gp.bl.vx = -gp.bl.vx;
    091				}
    092			}
    093		}
    094	}
    095				//
    096				// GamePanel オブジェクト(メソッド move)
    097				//
    098	GamePanel.prototype.move = function(sw)
    099	{
    100		if (sw == 0)
    101			gp.rk.x -= (gp.rk.width - 5);
    102		else
    103			gp.rk.x += (gp.rk.width - 5);
    104	}
    105				//
    106				// Block オブジェクト(プロパティ)
    107				//
    108	function Block()
    109	{
    110		this.block = new Image();   // ブロックの画像
    111		this.width = 75;   // ブロックの幅
    112		this.height = 38;   // ブロックの高さ
    113		this.row = 2;   // ブロックの行数
    114		this.col = 4;   // ブロックの列数
    115		this.number = this.row * this.col;   // ブロックの数
    116		this.ex = new Array();   // ブロックの状態(存在するか否か)
    117		for (let i1 = 0; i1 < this.row; i1++) {
    118			this.ex[i1] = new Array();
    119			for (let i2 = 0; i2 < this.col; i2++)
    120				this.ex[i1][i2] = true;
    121		}
    122						// ブロック画像の読み込み
    123		this.block.src = "image/block.jpg";
    124		return this;
    125	}
    126				//
    127				// Racket オブジェクト(プロパティ)
    128				//
    129	function Racket()
    130	{
    131		this.width;   // ラケットの幅
    132		this.height = 20;   // ラケットの高さ
    133		this.x;   // ラケットの横位置
    134		this.y;   // ラケットの縦位置
    135						// 幅と位置の設定
    136		if (mp.level == 1)
    137			this.width = 80;
    138		else
    139			this.width = 40;
    140		this.x = mp.canvas.width / 2 - this.width / 2;
    141		this.y = mp.canvas.height - this.height;
    142		return this;
    143	}
    144				//
    145				// Ball オブジェクト(プロパティ)
    146				//
    147	function Ball(blk)
    148	{
    149		this.r = 7;   // ボールの半径
    150		this.x;   // ボールの横位置
    151		this.y;   // ボールの縦位置
    152		this.v = 8;   // ボールの速度
    153		this.vx;   // ボールの横方向速度成分
    154		this.vy;   // ボールの縦方向速度成分
    155						// 位置と速度の設定
    156		this.x = this.r + Math.floor(Math.random() * (mp.canvas.width - 2 * this.r));
    157		this.y = blk.height * blk.row + 10;
    158		let a = this.v * Math.cos(45.0 * Math.PI / 180.0);
    159		this.vy = Math.floor(a);
    160		if (this.x < mp.canvas.width / 2)
    161			this.vx = Math.floor(a);
    162		else
    163			this.vx = -Math.floor(a);
    164		return this;
    165	}
    			
    152 行目( Ball 関数)

      ボールの速度を設定しています.

    158 行目( Ball 関数)

      Math オブジェクトのメソッド cos を使用して,45 度の余弦を求め,その方向の速度成分(縦方向の速度成分)を計算しています.

    160 行目~ 163 行目( Ball 関数)

      初期状態において,ボールが中央より左にあった場合は右方向へ,そうでない場合は,左方向へ移動を開始します.

    062 行目~ 076 行目( draw メソッド)

      上下の壁に衝突したときの処理を行っています.時間的なタイミング上,正確に上下の壁の位置と一致した場合に処理を行うことは不可能です(勿論,場合によっては可能ですが).そこで,少し壁にめり込んだ位置で処理を行います.そのため,垂直方向の座標を,壁の位置に強制的に修正しています( 064 行目,072 行目).また,065 行目,073 行目は,縦方向の速度を逆方向に設定しています.これらの点は,左右の壁についても同様です.

    080 行目~ 092 行目( draw メソッド)

      上下の壁に衝突しなかった場合,左右の壁に衝突したときの処理を行っています.

    098 行目~ 104 行目( move メソッド)

      「左」ボタン,または,「右」ボタンをクリックした時の処理であり,ラケットを左右に移動させます.

  3. ステップ3: ブロック崩し

      ボールがブロックに当たったとき,そのブロックを消去する処理を行っています.修正するプログラムは,GamePanel の draw メソッド( GamePanel オブジェクトのメソッド)だけです.

    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.draw()', 50);
    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('left').style.display = "";
    018		document.getElementById('right').style.display = "";
    019	}
    020				//
    021				// GamePanel オブジェクト(プロパティ)
    022				//
    023	function GamePanel()
    024	{
    025		this.timerID = -1;
    026		this.blk = new Block();   // Block オブジェクト
    027		this.rk = new Racket();   // Racket オブジェクト
    028		this.bl = new Ball(this.blk);   // Ball オブジェクト
    029		return this;
    030	}
    031				//
    032				// GamePanel オブジェクト(メソッド draw)
    033				//
    034	GamePanel.prototype.draw = function()
    035	{
    036						// キャンバスのクリア
    037		mp.ctx.clearRect(0, 0, mp.canvas.width, mp.canvas.height);
    038						// 描画
    039								// ブロック
    040		for (let i1 = 0; i1 < gp.blk.row; i1++) {
    041			for (let i2 = 0; i2 < gp.blk.col; i2++) {
    042				if (gp.blk.ex[i1][i2])
    043					mp.ctx.drawImage(gp.blk.block, gp.blk.width*i2, gp.blk.height*i1);
    044			}
    045		}
    046								// ラケット
    047		mp.ctx.beginPath();
    048		mp.ctx.fillStyle = "rgb(0, 255, 0)";
    049		mp.ctx.fillRect(gp.rk.x, gp.rk.y, gp.rk.width, gp.rk.height);
    050		mp.ctx.fill();
    051								// ボール
    052		mp.ctx.beginPath();
    053		mp.ctx.fillStyle = "rgb(255, 0, 0)";
    054		mp.ctx.arc(gp.bl.x, gp.bl.y, gp.bl.r, 0, 2*Math.PI);
    055		mp.ctx.fill();
    056						// 移動
    057		gp.bl.x += gp.bl.vx;
    058		gp.bl.y += gp.bl.vy;
    059						// 壁に衝突したときの処理
    060		let sw = 0;
    061								// 下へ移動中
    062		if (gp.bl.vy > 0) {
    063			if (gp.bl.y >= mp.canvas.height-gp.bl.r) {   // 下の壁に衝突
    064				gp.bl.y  = mp.canvas.height - gp.bl.r;
    065				gp.bl.vy = -gp.bl.vy;
    066				sw       = 1;
    067			}
    068		}
    069								// 上へ移動中
    070		else {
    071			if (gp.bl.y <= gp.blk.row*gp.blk.height) {
    072				let k = -1;
    073										// 横方向のブロック位置
    074				for (let i1 = 1; i1 < gp.blk.col && k < 0; i1++) {
    075					if (gp.bl.x <= i1*gp.blk.width)
    076						k = i1 - 1;
    077				}
    078				if (k < 0)
    079					k = gp.blk.col - 1;
    080										// ブロックとの衝突
    081				for (let i1 = gp.blk.row; i1 >= 0 && sw == 0; i1--) {
    082					if (gp.bl.y <= i1*gp.blk.height+gp.bl.r) {
    083						if (i1 == 0 || gp.blk.ex[i1-1][k]) {
    084							gp.bl.y  = i1 * gp.blk.height + gp.bl.r;
    085							gp.bl.vy = -gp.bl.vy;
    086							sw       = 1;
    087							if (i1 > 0) {
    088								gp.blk.ex[i1-1][k] = false;
    089								gp.blk.number--;
    090								if (gp.blk.number == 0) {   // ゲームクリア
    091									clearInterval(gp.timerID);   // タイマーの停止
    092									gcp_start();
    093								}
    094							}
    095						}
    096					}
    097				}
    098			}
    099		}
    100						// 上下の壁に衝突していない場合
    101		if (sw == 0) {
    102								// 右方向へ移動中
    103			if (gp.bl.vx > 0) {
    104				if (gp.bl.x >= mp.canvas.width-gp.bl.r) {   // 右の壁に衝突
    105					gp.bl.x  = mp.canvas.width - gp.bl.r;
    106					gp.bl.vx = -gp.bl.vx;
    107				}
    108			}
    109								// 左方向へ移動中
    110			else {
    111				if (gp.bl.x <= gp.bl.r) {   // 左の壁に衝突
    112					gp.bl.x  = gp.bl.r;
    113					gp.bl.vx = -gp.bl.vx;
    114				}
    115			}
    116		}
    117	}
    118				//
    119				// GamePanel オブジェクト(メソッド move)
    120				//
    121	GamePanel.prototype.move = function(sw)
    122	{
    123		if (sw == 0)
    124			gp.rk.x -= (gp.rk.width - 5);
    125		else
    126			gp.rk.x += (gp.rk.width - 5);
    127	}
    128				//
    129				// Block オブジェクト(プロパティ)
    130				//
    131	function Block()
    132	{
    133		this.block = new Image();   // ブロックの画像
    134		this.width = 75;   // ブロックの幅
    135		this.height = 38;   // ブロックの高さ
    136		this.row = 2;   // ブロックの行数
    137		this.col = 4;   // ブロックの列数
    138		this.number = this.row * this.col;   // ブロックの数
    139		this.ex = new Array();   // ブロックの状態(存在するか否か)
    140		for (let i1 = 0; i1 < this.row; i1++) {
    141			this.ex[i1] = new Array();
    142			for (let i2 = 0; i2 < this.col; i2++)
    143				this.ex[i1][i2] = true;
    144		}
    145						// ブロック画像の読み込み
    146		this.block.src = "image/block.jpg";
    147		return this;
    148	}
    149				//
    150				// Racket オブジェクト(プロパティ)
    151				//
    152	function Racket()
    153	{
    154		this.width;   // ラケットの幅
    155		this.height = 20;   // ラケットの高さ
    156		this.x;   // ラケットの横位置
    157		this.y;   // ラケットの縦位置
    158						// 幅と位置の設定
    159		if (mp.level == 1)
    160			this.width = 80;
    161		else
    162			this.width = 40;
    163		this.x = mp.canvas.width / 2 - this.width / 2;
    164		this.y = mp.canvas.height - this.height;
    165		return this;
    166	}
    167				//
    168				// Ball オブジェクト(プロパティ)
    169				//
    170	function Ball(blk)
    171	{
    172		this.r = 7;   // ボールの半径
    173		this.x;   // ボールの横位置
    174		this.y;   // ボールの縦位置
    175		this.v = 8;   // ボールの速度
    176		this.vx;   // ボールの横方向速度成分
    177		this.vy;   // ボールの縦方向速度成分
    178						// 位置と速度の設定
    179		this.x = this.r + Math.floor(Math.random() * (mp.canvas.width - 2 * this.r));
    180		this.y = blk.height * blk.row + 10;
    181		let a = this.v * Math.cos(45.0 * Math.PI / 180.0);
    182		this.vy = Math.floor(a);
    183		if (this.x < mp.canvas.width / 2)
    184			this.vx = Math.floor(a);
    185		else
    186			this.vx = -Math.floor(a);
    187		return this;
    188	}
    			
    071 行目

      この if 文により,ボールがブロックの存在する位置より上にあるときだけ,072 行目~ 097 行目の処理を行います.

    074 行目~ 079 行目

      ボールの中心に基づき,水平位置として,どのブロックの位置にいるかを調べています(配列 gp.blk.ex の列の位置を決める).このプログラムでは,横方向はボールの中心を基準にして衝突判定を行っているため,ボールの一部がブロックの角をかすったようなときは,衝突にならない場合があります.また,ボールが上部へ移動中だけに衝突判定を行っているため,跳ね返ったボールが他のブロックに衝突しても衝突とはみなしません.

    081 行目

      この for 文によって,ブロックの行の数だけ,082 行目~ 096 行目が繰り返されます.ブロックまたは上の壁に衝突すると,変数 sw の値が 1 に設定され,繰り返し文の外に出ます.一番下のブロックから調べているため,i1 の値がブロックの行数から始まり,順に減らされている点に注意してください.

    082 行目

      この if 文によって,ボールが壁または i1 行目のブロックに衝突している場合に,083 行目~ 095 行目の処理が行われます.

    083 行目~ 095 行目

      083 行目の if 文により,i1 が 0 の場合(上の壁に衝突),または,(i1-1) 行 k 列にブロックが存在した場合(ブロックに衝突)に以下の処理が行われます.ボールの位置と速度を修正し,sw の値を 1 に設定した後( 084 行目~ 086 行目),衝突したのがブロックであった場合( 087 行目~ 094 行目)は,そのブロックを消去し,ブロックの数を減らしています( 088,089 行目).すべてのブロックが消去された場合は,ゲームクリアとなります( 090 行目~ 093 行目).

  4. ステップ4: 完成

      ボールがラケットに当たったときの処理を行っています.修正するプログラムは,GamePanel の draw メソッド( GamePanel オブジェクトのメソッド)だけです.063 行目~ 073 行目において,ラケットに当たったか否かの判定を行っています.当たった場合はボールを跳ね返し,当たらなかった場合はゲームオーバーとなります.

    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.draw()', 50);
    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('left').style.display = "";
    018		document.getElementById('right').style.display = "";
    019	}
    020				//
    021				// GamePanel オブジェクト(プロパティ)
    022				//
    023	function GamePanel()
    024	{
    025		this.timerID = -1;
    026		this.blk = new Block();   // Block オブジェクト
    027		this.rk = new Racket();   // Racket オブジェクト
    028		this.bl = new Ball(this.blk);   // Ball オブジェクト
    029		return this;
    030	}
    031				//
    032				// GamePanel オブジェクト(メソッド draw)
    033				//
    034	GamePanel.prototype.draw = function()
    035	{
    036						// キャンバスのクリア
    037		mp.ctx.clearRect(0, 0, mp.canvas.width, mp.canvas.height);
    038						// 描画
    039								// ブロック
    040		for (let i1 = 0; i1 < gp.blk.row; i1++) {
    041			for (let i2 = 0; i2 < gp.blk.col; i2++) {
    042				if (gp.blk.ex[i1][i2])
    043					mp.ctx.drawImage(gp.blk.block, gp.blk.width*i2, gp.blk.height*i1);
    044			}
    045		}
    046								// ラケット
    047		mp.ctx.beginPath();
    048		mp.ctx.fillStyle = "rgb(0, 255, 0)";
    049		mp.ctx.fillRect(gp.rk.x, gp.rk.y, gp.rk.width, gp.rk.height);
    050		mp.ctx.fill();
    051								// ボール
    052		mp.ctx.beginPath();
    053		mp.ctx.fillStyle = "rgb(255, 0, 0)";
    054		mp.ctx.arc(gp.bl.x, gp.bl.y, gp.bl.r, 0, 2*Math.PI);
    055		mp.ctx.fill();
    056						// 移動
    057		gp.bl.x += gp.bl.vx;
    058		gp.bl.y += gp.bl.vy;
    059						// 壁に衝突したときの処理
    060		let sw = 0;
    061								// 下へ移動中
    062		if (gp.bl.vy > 0) {
    063			if (gp.bl.y >= gp.rk.y-gp.bl.r) {   // ラケットの位置より下か?
    064				if (gp.bl.x >= gp.rk.x && gp.bl.x <= gp.rk.x+gp.rk.width) {   // ラケット?
    065					gp.bl.y  = gp.rk.y - gp.bl.r;
    066					gp.bl.vy = -gp.bl.vy;
    067				}
    068				else {   // ゲームオーバー
    069					clearInterval(gp.timerID);   // タイマーの停止
    070					gop_start();
    071				}
    072				sw = 1;
    073			}
    074		}
    075								// 上へ移動中
    076		else {
    077			if (gp.bl.y <= gp.blk.row*gp.blk.height) {
    078				let k = -1;
    079										// 横方向のブロック位置
    080				for (let i1 = 1; i1 < gp.blk.col && k < 0; i1++) {
    081					if (gp.bl.x <= i1*gp.blk.width)
    082						k = i1 - 1;
    083				}
    084				if (k < 0)
    085					k = gp.blk.col - 1;
    086										// ブロックとの衝突
    087				for (let i1 = gp.blk.row; i1 >= 0 && sw == 0; i1--) {
    088					if (gp.bl.y <= i1*gp.blk.height+gp.bl.r) {
    089						if (i1 == 0 || gp.blk.ex[i1-1][k]) {
    090							gp.bl.y  = i1 * gp.blk.height + gp.bl.r;
    091							gp.bl.vy = -gp.bl.vy;
    092							sw       = 1;
    093							if (i1 > 0) {
    094								gp.blk.ex[i1-1][k] = false;
    095								gp.blk.number--;
    096								if (gp.blk.number == 0) {   // ゲームクリア
    097									clearInterval(gp.timerID);   // タイマーの停止
    098									gcp_start();
    099								}
    100							}
    101						}
    102					}
    103				}
    104			}
    105		}
    106						// 上下の壁に衝突していない場合
    107		if (sw == 0) {
    108								// 右方向へ移動中
    109			if (gp.bl.vx > 0) {
    110				if (gp.bl.x >= mp.canvas.width-gp.bl.r) {   // 右の壁に衝突
    111					gp.bl.x  = mp.canvas.width - gp.bl.r;
    112					gp.bl.vx = -gp.bl.vx;
    113				}
    114			}
    115								// 左方向へ移動中
    116			else {
    117				if (gp.bl.x <= gp.bl.r) {   // 左の壁に衝突
    118					gp.bl.x  = gp.bl.r;
    119					gp.bl.vx = -gp.bl.vx;
    120				}
    121			}
    122		}
    123	}
    124				//
    125				// GamePanel オブジェクト(メソッド move)
    126				//
    127	GamePanel.prototype.move = function(sw)
    128	{
    129		if (sw == 0)
    130			gp.rk.x -= (gp.rk.width - 5);
    131		else
    132			gp.rk.x += (gp.rk.width - 5);
    133	}
    134				//
    135				// Block オブジェクト(プロパティ)
    136				//
    137	function Block()
    138	{
    139		this.block = new Image();   // ブロックの画像
    140		this.width = 75;   // ブロックの幅
    141		this.height = 38;   // ブロックの高さ
    142		this.row = 2;   // ブロックの行数
    143		this.col = 4;   // ブロックの列数
    144		this.number = this.row * this.col;   // ブロックの数
    145		this.ex = new Array();   // ブロックの状態(存在するか否か)
    146		for (let i1 = 0; i1 < this.row; i1++) {
    147			this.ex[i1] = new Array();
    148			for (let i2 = 0; i2 < this.col; i2++)
    149				this.ex[i1][i2] = true;
    150		}
    151						// ブロック画像の読み込み
    152		this.block.src = "image/block.jpg";
    153		return this;
    154	}
    155				//
    156				// Racket オブジェクト(プロパティ)
    157				//
    158	function Racket()
    159	{
    160		this.width;   // ラケットの幅
    161		this.height = 20;   // ラケットの高さ
    162		this.x;   // ラケットの横位置
    163		this.y;   // ラケットの縦位置
    164						// 幅と位置の設定
    165		if (mp.level == 1)
    166			this.width = 80;
    167		else
    168			this.width = 40;
    169		this.x = mp.canvas.width / 2 - this.width / 2;
    170		this.y = mp.canvas.height - this.height;
    171		return this;
    172	}
    173				//
    174				// Ball オブジェクト(プロパティ)
    175				//
    176	function Ball(blk)
    177	{
    178		this.r = 7;   // ボールの半径
    179		this.x;   // ボールの横位置
    180		this.y;   // ボールの縦位置
    181		this.v = 8;   // ボールの速度
    182		this.vx;   // ボールの横方向速度成分
    183		this.vy;   // ボールの縦方向速度成分
    184						// 位置と速度の設定
    185		this.x = this.r + Math.floor(Math.random() * (mp.canvas.width - 2 * this.r));
    186		this.y = blk.height * blk.row + 10;
    187		let a = this.v * Math.cos(45.0 * Math.PI / 180.0);
    188		this.vy = Math.floor(a);
    189		if (this.x < mp.canvas.width / 2)
    190			this.vx = Math.floor(a);
    191		else
    192			this.vx = -Math.floor(a);
    193		return this;
    194	}
    			

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