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

アクションゲーム(その1)

  ここでは,背景や障害物を 1 枚の画像で表現し,画像内のピクセル単位の位置から,主人公の状況を把握しています.きれいな画像を描くことは可能ですが,特に複雑な画像の場合,状況の把握がかなり面倒です.画面を格子状に区切り,各格子内に小さな画像を配置し,それらの組合せによって背景や障害物を描く方が状況の把握が簡単になります.その方法に関しては,「アクションゲーム(その2)」を参照して下さい.

  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="400" 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>
      </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";
      }
      				

    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 = "ゲーム開始";
      }
      				

    4. GamePanel

        GamePanel は,実際のゲームを実現する部分です.従って,「ゲーム枠の作成」における GamePanel とは,ゲームの種類によってその内容は大きく異なります.今後,このプログラムを完成させていくことになりますが,ここでは,背景(道)及び主人公を表示し,横方向へ移動させる機能だけを持たせています.

      01	gp = null;   // GamePanel オブジェクト
      02	
      03				//
      04				// GamePanel の開始
      05				//
      06	function gp_start()
      07	{
      08						// GamePanel オブジェクト
      09		gp = new GamePanel();
      10						// タイマーのスタート
      11		gp.timerID = setInterval('gp.draw()', 30);
      12						// マウスリスナの追加(マウスボタンが押された時)
      13		mp.canvas.addEventListener("mousedown", gp.onMouseDown);
      14						// ボタンの表示制御
      15		document.getElementById('method').style.display = "none";
      16		document.getElementById('start').style.display = "none";
      17		document.getElementById('first').style.display = "none";
      18		document.getElementById('finish').style.display = "none";
      19	}
      20				//
      21				// GamePanel オブジェクト(プロパティ)
      22				//
      23	function GamePanel()
      24	{
      25		this.timerID = -1;
      26		this.rd = new Road();   // Road オブジェクト
      27		this.hr = new Hero(this.rd);   // Hero オブジェクト
      28		return this;
      29	}
      30				//
      31				// GamePanel オブジェクト(メソッド draw)
      32				//
      33	GamePanel.prototype.draw = function()
      34	{
      35						// キャンバスのクリア
      36		mp.ctx.clearRect(0, 0, mp.canvas.width, mp.canvas.height);
      37						// 移動
      38		gp.rd.x += gp.rd.v_x;
      39		gp.hr.x += gp.hr.v_x;
      40						// 描画
      41		mp.ctx.drawImage(gp.rd.image, gp.rd.x, gp.rd.y);
      42		mp.ctx.drawImage(gp.hr.image, gp.hr.x, gp.hr.y);
      43	}
      44				//
      45				// GamePanel オブジェクト(メソッド onMouseDown)
      46				//
      47	GamePanel.prototype.onMouseDown = function(event)
      48	{
      49		clearInterval(gp.timerID);   // タイマーの停止
      50		gcp_start();
      51	}
      52				//
      53				// Road オブジェクト(プロパティ)
      54				//
      55	function Road()
      56	{
      57		this.image = new Image();   // 背景
      58		this.v_x = -2.0;   // 背景の水平方向移動速度
      59		this.width = (mp.level == 1) ? 643 : 557;   // 背景画像の幅
      60		this.height = 102;   // 背景画像の高さ
      61						// 背景の読み込み
      62		if (mp.level == 1)
      63			this.image.src = "image/road1.jpg";
      64		else
      65			this.image.src = "image/road2.jpg";
      66						// 背景の初期位置
      67		this.x = 0;
      68		this.y = mp.canvas.height - this.height;
      69		return this;
      70	}
      71				//
      72				// Hero オブジェクト(プロパティ)
      73				//
      74	function Hero(rd)
      75	{
      76		this.image = new Image();   // 主人公
      77		this.v_x = 1.0;   // 主人公の水平方向移動速度
      78		this.width = 32;   // 主人公の幅
      79		this.height = 52;   // 主人公の高さ
      80						// 主人公の読み込み
      81		this.image.src = "image/char.jpg";
      82						// 主人公の初期位置
      83		this.x = 0;
      84		this.y = mp.canvas.height - rd.height - this.height;
      85		return this;
      86	}
      				
      09 行目( gp_start 関数)

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

      13 行目( gp_start 関数)

        キャンバスにマウスが押された時に対応するマウスリスナを追加しています.mousedown を使用していますが,click でも構いません.47 行目~ 51 行目において,その具体的な処理を記述しています.ここでは,ゲームレベルが上がった時の処理を確認したいため,マウスボタンを押すことによって「ゲームクリア」画面に移動するように設定してあります.ただし,実際のゲームにおいては,このような処理を行いません.

      26,27 行目( GamePanel 関数)

        今後,その機能を拡張していく可能性がありますので,背景(道)及び主人公は,別のオブジェクト( 55 行目~ 70 行目,及び,74 行目~ 86 行目)として定義してあります.rd,hr は,それらのオブジェクトを記憶するための変数です.

      33 行目~ 43 行目( draw メソッド)

        11 行目で設定したタイマーに従って,30 ms 毎に実行される GamePanel オブジェクトのメソッド draw の定義です.draw では,領域をクリアした後( 36 行目),道及び主人公を移動させ( 38,39 行目),それらを描画しています( 41,42 行目).

      55 行目~ 70 行目( Road 関数)

        Road オブジェクトのプロパティを設定しています.59 行目の ? : は,条件演算子と呼ばれ,一般的には以下のように記述されます.論理式が評価され,その結果が真であれば,式1を評価した結果が変数に代入され,偽であれば,式2を評価した結果が変数に代入されます.
      	変数 = (論理式) ? 式1 : 式2 					
      従って,59 行目は,レベルが 1 であれば,width の値を 643 とし,そうでなければ,557 とするという意味になります.

      74 行目~ 86 行目( Hero 関数)

        Hero オブジェクトのプロパティを設定しています.

    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 = "";
      }
      				

    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 = "現レベルで再開";
      }
      				

  2. ステップ2: ゲームオーバー

      背景画像(道)において,道のない部分(谷)に主人公が移動したときゲームオーバーになるように,プログラムを修正します.修正するプログラムは,GamePanel の Road 関数と GamePanel オブジェクトの draw メソッドです.まず,Road 関数の変更部分から順に説明していきます.

    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()', 30);
    012						// マウスリスナの追加(マウスボタンが押された時)
    013		mp.canvas.addEventListener("mousedown", gp.onMouseDown);
    014						// ボタンの表示制御
    015		document.getElementById('method').style.display = "none";
    016		document.getElementById('start').style.display = "none";
    017		document.getElementById('first').style.display = "none";
    018		document.getElementById('finish').style.display = "none";
    019	}
    020				//
    021				// GamePanel オブジェクト(プロパティ)
    022				//
    023	function GamePanel()
    024	{
    025		this.timerID = -1;
    026		this.rd = new Road();   // Road オブジェクト
    027		this.hr = new Hero(this.rd);   // Hero オブジェクト
    028		return this;
    029	}
    030				//
    031				// GamePanel オブジェクト(メソッド draw)
    032				//
    033	GamePanel.prototype.draw = function()
    034	{
    035						// キャンバスのクリア
    036		mp.ctx.clearRect(0, 0, mp.canvas.width, mp.canvas.height);
    037						// 移動
    038		gp.rd.x += gp.rd.v_x;
    039		gp.hr.x += gp.hr.v_x;
    040						// 描画
    041		mp.ctx.drawImage(gp.rd.image, gp.rd.x, gp.rd.y);
    042		mp.ctx.drawImage(gp.hr.image, gp.hr.x, gp.hr.y);
    043						// 道の状態のチェック
    044		let p  = gp.hr.x - gp.rd.x + gp.hr.width / 2;
    045		let sw = -1;
    046		for (let i1 = 0; i1 < gp.rd.state.length && sw < 0; i1++) {
    047			if (p <= gp.rd.state[i1][0])
    048				sw = gp.rd.state[i1][1];
    049		}
    050		if (sw < 0)   // ゴールを通過
    051			sw = 1;
    052		if (sw > 0) {
    053			clearInterval(gp.timerID);   // タイマーの停止
    054			if (sw == 1)
    055				gop_start();   // ゲームオーバー
    056			else
    057				gcp_start();   // ゲームクリア
    058		}
    059	}
    060				//
    061				// GamePanel オブジェクト(メソッド onMouseDown)
    062				//
    063	GamePanel.prototype.onMouseDown = function(event)
    064	{
    065		clearInterval(gp.timerID);   // タイマーの停止
    066		gcp_start();
    067	}
    068				//
    069				// Road オブジェクト(プロパティ)
    070				//
    071	function Road()
    072	{
    073		this.image = new Image();   // 背景
    074		this.v_x = -2.0;   // 背景の水平方向移動速度
    075		this.width = (mp.level == 1) ? 643 : 557;   // 背景画像の幅
    076		this.height = 102;   // 背景画像の高さ
    077		this.s1 = new Array()
    078		this.s1[0] = new Array(147, 0);
    079		this.s1[1] = new Array(204, 1);
    080		this.s1[2] = new Array(352, 0);
    081		this.s1[3] = new Array(494, 1);
    082		this.s1[4] = new Array(642, 10);
    083		this.s2 = new Array();
    084		this.s2[0] = new Array(73, 0);
    085		this.s2[1] = new Array(139, 1);
    086		this.s2[2] = new Array(213, 0);
    087		this.s2[3] = new Array(330, 1);
    088		this.s2[4] = new Array(404, 0);
    089		this.s2[5] = new Array(482, 1);
    090		this.s2[6] = new Array(556, 10);
    091		this.state = null;   // 背景(道)の状態
    092							 //   state[i][0] : 状態変化の終了位置
    093							 //   state[i][1] : 道の状態
    094							 //                   =0 : 道
    095							 //                   =1 : 谷
    096							 //                   =10 : ゴール
    097						// 背景の読み込みと状態設定
    098		if (mp.level == 1) {
    099			this.image.src = "image/road1.jpg";
    100			this.state = this.s1;
    101		}
    102		else {
    103			this.image.src = "image/road2.jpg";
    104			this.state = this.s2;
    105		}
    106						// 背景の初期位置
    107		this.x = 0;
    108		this.y = mp.canvas.height - this.height;
    109		return this;
    110	}
    111				//
    112				// Hero オブジェクト(プロパティ)
    113				//
    114	function Hero(rd)
    115	{
    116		this.image = new Image();   // 主人公
    117		this.v_x = 1.0;   // 主人公の水平方向移動速度
    118		this.width = 32;   // 主人公の幅
    119		this.height = 52;   // 主人公の高さ
    120						// 主人公の読み込み
    121		this.image.src = "image/char.jpg";
    122						// 背景の初期位置
    123		this.x = 0;
    124		this.y = mp.canvas.height - rd.height - this.height;
    125		return this;
    126	}
    			
    077 行目~ 105 行目( Road 関数)

      道の状態を表すための 2 次元配列を定義し,初期設定しています.2 次元配列は,まず配列を定義し( 077,083 行目),その配列の各要素を再び配列として定義する( 078 行目~ 082 行目,084 行目~ 090 行目)ことによって可能です.Road オブジェクトのプロパティ s1( 5 行 2 列)はレベル 1 における道の状態,また,プロパティ s2( 7 行 2 列)はレベル 2 における道の状態を表しています.これらの状態は,100,104 行目において,現在のレベルに従ってプロパティ state に代入されます.例えば,レベル 1 の場合,state ( s1 )の内容とその意味する所は以下に示すとおりです.

    	state[0][0] = 147;   // 0 から 147 ピクセルまでは道
    	state[0][1] = 0;
    	state[1][0] = 204;   // 148 から 204 ピクセルまでは谷
    	state[1][1] = 1;
    		・・・・・
    	state[4][0] = 642;   // 495 から 642 ピクセルまではゴール
    	state[4][1] = 10;				
      一般に,配列の各要素を配列として定義することによって,多次元配列を定義することが可能です.以下に示すのは 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);				
      配列の項で説明していますように,配列変数 s1,s2,state などは,データを記憶している領域の先頭を指すポインタとみなすことができます.例えば,100 行目において s1 を state に代入していますが,s1 の全てのデータをコピーして state に代入しているわけではなく,s1 と state が同じデータ領域を指すことになるだけです.従って,state を介して各要素の値を変更すれば,s1 の対応する要素の値も変化します(逆も同様).

    044 行目( draw メソッド)

      変数 p には,主人公の現時点における道上の位置を設定しています.なお,gp.hr.width は,主人公画像の幅です.従って,変数 p は,主人公の中心の位置に相当します.

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

      Road オブジェクトのプロパティ state と変数 p の値に基づき,現在位置の状態を調べています.道の上にいる場合は sw = 0,谷にいる場合は sw = 1,ゴールにいる場合(現時点ではあり得ませんが)は,sw = 10 という結果になります.sw の値が負になるのは,ゴールを通り過ぎた場合です.なお,046 行目の length は,Array オブジェクトのプロパティであり,配列の要素数を表します.この場合は,配列 state の行の数を示しています.

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

      谷,または,ゴールにいる場合は,タイマーを停止し( 053 行目),対応するゲームパネルに変更しています.

  3. ステップ3: ジャンプ

      主人公がジャンプできるようにプログラムを修正します.修正するプログラムは,GamePanel の Hero 関数,draw メソッド( GamePanel オブジェクトのメソッド),及び,gp_start 関数です.まず,Hero 関数の変更部分から順に説明していきます.

    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()', 30);
    012						// マウスリスナの追加(マウスボタンが押された時と離された時)
    013		mp.canvas.addEventListener("mousedown", gp.onMouseDown);
    014		mp.canvas.addEventListener("touchstart", gp.onMouseDown);
    015		mp.canvas.addEventListener("touchmove", gp.onMouseDown);
    016		mp.canvas.addEventListener("mouseup", gp.onMouseUp);
    017						// ボタンの表示制御
    018		document.getElementById('method').style.display = "none";
    019		document.getElementById('start').style.display = "none";
    020		document.getElementById('first').style.display = "none";
    021		document.getElementById('finish').style.display = "none";
    022	}
    023				//
    024				// GamePanel オブジェクト(プロパティ)
    025				//
    026	function GamePanel()
    027	{
    028		this.timerID = -1;
    029		this.rd = new Road();   // Road オブジェクト
    030		this.hr = new Hero(this.rd);   // Hero オブジェクト
    031		return this;
    032	}
    033				//
    034				// GamePanel オブジェクト(メソッド draw)
    035				//
    036	GamePanel.prototype.draw = function()
    037	{
    038						// キャンバスのクリア
    039		mp.ctx.clearRect(0, 0, mp.canvas.width, mp.canvas.height);
    040						// 移動
    041		gp.rd.x += gp.rd.v_x;
    042		gp.hr.v_y += (gp.hr.up - gp.hr.down);
    043		gp.hr.x += gp.hr.v_x;
    044		gp.hr.y -= gp.hr.v_y;
    045						// 描画
    046		mp.ctx.drawImage(gp.rd.image, gp.rd.x, gp.rd.y);
    047		mp.ctx.drawImage(gp.hr.image, gp.hr.x, gp.hr.y);
    048						// 道の状態のチェック
    049	/*
    050		let p  = gp.hr.x - gp.rd.x + gp.hr.width / 2;
    051		let sw = -1;
    052		for (let i1 = 0; i1 < gp.rd.state.length && sw < 0; i1++) {
    053			if (p <= gp.rd.state[i1][0])
    054				sw = gp.rd.state[i1][1];
    055		}
    056		if (sw < 0)   // ゴールを通過
    057			sw = 1;
    058		if (sw > 0) {
    059			clearInterval(gp.timerID);   // タイマーの停止
    060			if (sw == 1)
    061				gop_start();   // ゲームオーバー
    062			else
    063				gcp_start();   // ゲームクリア
    064		}
    065	*/
    066	}
    067				//
    068				// GamePanel オブジェクト(メソッド onMouseDown)
    069				//
    070	GamePanel.prototype.onMouseDown = function(event)
    071	{
    072		if (!gp.hr.jump) {
    073			gp.hr.up   = 1.0;
    074			gp.hr.down = 0.5;
    075			gp.hr.v_x  = 2.0;
    076			gp.hr.jump = true;
    077			gp.hr.y--;   // 着地判定のため,1 ピクセルだけジャンプさせておく
    078		}
    079	}
    080				//
    081				// GamePanel オブジェクト(メソッド onMouseUp)
    082				//
    083	GamePanel.prototype.onMouseUp = function(event)
    084	{
    085		gp.hr.up   = 0.0;
    086		gp.hr.down = 0.5;
    087	}
    088				//
    089				// Road オブジェクト(プロパティ)
    090				//
    091	function Road()
    092	{
    093		this.image = new Image();   // 背景
    094		this.v_x = -2.0;   // 背景の水平方向移動速度
    095		this.width = (mp.level == 1) ? 643 : 557;   // 背景画像の幅
    096		this.height = 102;   // 背景画像の高さ
    097		this.s1 = new Array()
    098		this.s1[0] = new Array(147, 0);
    099		this.s1[1] = new Array(204, 1);
    100		this.s1[2] = new Array(352, 0);
    101		this.s1[3] = new Array(494, 1);
    102		this.s1[4] = new Array(642, 10);
    103		this.s2 = new Array();
    104		this.s2[0] = new Array(73, 0);
    105		this.s2[1] = new Array(139, 1);
    106		this.s2[2] = new Array(213, 0);
    107		this.s2[3] = new Array(330, 1);
    108		this.s2[4] = new Array(404, 0);
    109		this.s2[5] = new Array(482, 1);
    110		this.s2[6] = new Array(556, 10);
    111		this.state = null;   // 背景(道)の状態
    112		                     //   state[i][0] : 状態変化の終了位置
    113		                     //   state[i][1] : 道の状態
    114		                     //                   =0 : 道
    115		                     //                   =1 : 谷
    116		                     //                   =10 : ゴール
    117						// 背景の読み込みと状態設定
    118		if (mp.level == 1) {
    119			this.image.src = "image/road1.jpg";
    120			this.state = this.s1;
    121		}
    122		else {
    123			this.image.src = "image/road2.jpg";
    124			this.state = this.s2;
    125		}
    126						// 背景の初期位置
    127		this.x = 0;
    128		this.y = mp.canvas.height - this.height;
    129		return this;
    130	}
    131				//
    132				// Hero オブジェクト(プロパティ)
    133				//
    134	function Hero(rd)
    135	{
    136		this.image = new Image();   // 主人公
    137		this.v_x = 1.0;   // 主人公の水平方向移動速度
    138		this.v_y = 0.0;   // 主人公の垂直方向移動速度
    139		this.up = 0.0;   // 主人公の上向き加速度
    140		this.down = 0;   // 主人公の下向き加速度
    141		this.jump = false;   // 主人公がジャンプ中か?
    142		this.width = 32;   // 主人公の幅
    143		this.height = 52;   // 主人公の高さ
    144						// 主人公の読み込み
    145		this.image.src = "image/char.jpg";
    146						// 背景の初期位置
    147		this.x = 0;
    148		this.y = mp.canvas.height - rd.height - this.height;
    149		return this;
    150	}
    			
    138 行目( Hero 関数)

      主人公の垂直方向の移動速度を保存するためのプロパティを定義しています.

    139,140 行目( Hero 関数)

      主人公に対する上向きの加速度と下向きの加速度です.

    141 行目( Hero 関数)

      ジャンプ中に,さらにジャンプすることを防ぐためのプロパティです.

    042,044 行目( draw メソッド)

      設定された加速度から,現在の速度を計算しています.一般に,現在の速度を v / t,加速度を a / t2 とすると,t 時間後の速度は v + at となります.この速度を使用して,044 行目において,主人公の垂直位置を計算しています.いずれの式においても,t = 1 とみなしています.

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

      この部分をそのまま残しておくと,横方向の座標が谷の部分になるとゲームオーバーになってしまうため,一時的にコメントアウトしています.

    014 行目~ 016 行目( gp_start 関数)

      マウスボタンを離した時に対応するマウスリスナを追加しています( 016 行目).イベントに対する処理は,GamePanel オブジェクトの onMouseUp メソッド( 083 行目~ 087 行目)で実行されます.なお,マウスボタンを押したときの処理は,ステップ1において暫定的に利用していましたが,その内容( 072 行目~ 078 行目)も修正してやる必要があります.なお,014,015 行目は,スマホ等に対応するための記述です.

    072 行目~ 078 行目( onMouseDown メソッド)

      マウスボタンが押された時の処理であり,Hero オブジェクトのプロパティを設定します.マウスボタンを押すと,垂直方向の加速度が設定されます( 073,074 行目).また,水平方向の速度も変化させています( 075 行目).この結果,主人公は,垂直方向は上方向に速度を増しながら,水平方向は一定速度で移動します.また,プロパティ jump を true に設定し,ジャンプ中であることを設定します.なお,後ほど行う処理ですが,着地した場合はこの値を再び false にします.

    083 行目~ 087 行目( onMouseUp 関数)

      マウスボタンが離された時の処理であり,Hero オブジェクトのプロパティを設定します.マウスボタンを離すと,上向きの加速度が 0 に設定されます( 085 行目).この結果,最終的に,主人公は,下方向に速度を増しながら落下することになります.

  4. ステップ4: 完成

      ジャンプした後,着地,ゲームクリア,ゲームオーバーなのど判定を行い,ゲームを完成します.修正するプログラムは,GamePanel の draw メソッド( GamePanel オブジェクトのメソッド)だけです.ステップ3においてコメントとした部分の追加・修正です.

    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()', 30);
    012						// マウスリスナの追加(マウスボタンが押された時と離された時)
    013		mp.canvas.addEventListener("mousedown", gp.onMouseDown);
    014		mp.canvas.addEventListener("touchstart", gp.onMouseDown);
    015		mp.canvas.addEventListener("touchmove", gp.onMouseDown);
    016		mp.canvas.addEventListener("mouseup", gp.onMouseUp);
    017						// ボタンの表示制御
    018		document.getElementById('method').style.display = "none";
    019		document.getElementById('start').style.display = "none";
    020		document.getElementById('first').style.display = "none";
    021		document.getElementById('finish').style.display = "none";
    022	}
    023				//
    024				// GamePanel オブジェクト(プロパティ)
    025				//
    026	function GamePanel()
    027	{
    028		this.timerID = -1;
    029		this.rd = new Road();   // Road オブジェクト
    030		this.hr = new Hero(this.rd);   // Hero オブジェクト
    031		return this;
    032	}
    033				//
    034				// GamePanel オブジェクト(メソッド draw)
    035				//
    036	GamePanel.prototype.draw = function()
    037	{
    038						// キャンバスのクリア
    039		mp.ctx.clearRect(0, 0, mp.canvas.width, mp.canvas.height);
    040						// 移動
    041		gp.rd.x += gp.rd.v_x;
    042		gp.hr.v_y += (gp.hr.up - gp.hr.down);
    043		gp.hr.x += gp.hr.v_x;
    044		gp.hr.y -= gp.hr.v_y;
    045						// 描画
    046		mp.ctx.drawImage(gp.rd.image, gp.rd.x, gp.rd.y);
    047		mp.ctx.drawImage(gp.hr.image, gp.hr.x, gp.hr.y);
    048						// 状態のチェック
    049								// 水平位置のチェック
    050		let p  = gp.hr.x - gp.rd.x + gp.hr.width / 2;
    051		let sw = -1;
    052		for (let i1 = 0; i1 < gp.rd.state.length && sw < 0; i1++) {
    053			if (p <= gp.rd.state[i1][0])
    054				sw = gp.rd.state[i1][1];
    055		}
    056		if (sw < 0)   // ゴールを通過
    057			sw = 1;
    058								// 垂直位置のチェック
    059		else {
    060			if (gp.hr.jump) {
    061				if (gp.hr.y >= mp.canvas.height - gp.rd.height - gp.hr.height) {
    062					if (sw == 0 || sw == 10) {
    063						gp.hr.jump = false;
    064						gp.hr.down = 0.0;
    065						gp.hr.v_x  = 1.0;
    066						gp.hr.v_y  = 0.0;
    067						gp.hr.y    = mp.canvas.height - gp.rd.height - gp.hr.height;
    068					}
    069				}
    070				else
    071					sw = 0;
    072			}
    073		}
    074								// ゲーム状態変更
    075		if (sw > 0) {
    076			clearInterval(gp.timerID);   // タイマーの停止
    077			if (sw == 1)
    078				gop_start();   // ゲームオーバー
    079			else
    080				gcp_start();   // ゲームクリア
    081		}
    082	}
    083				//
    084				// GamePanel オブジェクト(メソッド onMouseDown)
    085				//
    086	GamePanel.prototype.onMouseDown = function(event)
    087	{
    088		if (!gp.hr.jump) {
    089			gp.hr.up   = 1.0;
    090			gp.hr.down = 0.5;
    091			gp.hr.v_x  = 2.0;
    092			gp.hr.jump = true;
    093			gp.hr.y--;   // 着地判定のため,1 ピクセルだけジャンプさせておく
    094		}
    095	}
    096				//
    097				// GamePanel オブジェクト(メソッド onMouseUp)
    098				//
    099	GamePanel.prototype.onMouseUp = function(event)
    100	{
    101		gp.hr.up   = 0.0;
    102		gp.hr.down = 0.5;
    103	}
    104				//
    105				// Road オブジェクト(プロパティ)
    106				//
    107	function Road()
    108	{
    109		this.image = new Image();   // 背景
    110		this.v_x = -2.0;   // 背景の水平方向移動速度
    111		this.width = (mp.level == 1) ? 643 : 557;   // 背景画像の幅
    112		this.height = 102;   // 背景画像の高さ
    113		this.s1 = new Array()
    114		this.s1[0] = new Array(147, 0);
    115		this.s1[1] = new Array(204, 1);
    116		this.s1[2] = new Array(352, 0);
    117		this.s1[3] = new Array(494, 1);
    118		this.s1[4] = new Array(642, 10);
    119		this.s2 = new Array();
    120		this.s2[0] = new Array(73, 0);
    121		this.s2[1] = new Array(139, 1);
    122		this.s2[2] = new Array(213, 0);
    123		this.s2[3] = new Array(330, 1);
    124		this.s2[4] = new Array(404, 0);
    125		this.s2[5] = new Array(482, 1);
    126		this.s2[6] = new Array(556, 10);
    127		this.state = null;   // 背景(道)の状態
    128		                     //   state[i][0] : 状態変化の終了位置
    129		                     //   state[i][1] : 道の状態
    130		                     //                   =0 : 道
    131		                     //                   =1 : 谷
    132		                     //                   =10 : ゴール
    133						// 背景の読み込みと状態設定
    134		if (mp.level == 1) {
    135			this.image.src = "image/road1.jpg";
    136			this.state = this.s1;
    137		}
    138		else {
    139			this.image.src = "image/road2.jpg";
    140			this.state = this.s2;
    141		}
    142						// 背景の初期位置
    143		this.x = 0;
    144		this.y = mp.canvas.height - this.height;
    145		return this;
    146	}
    147				//
    148				// Hero オブジェクト(プロパティ)
    149				//
    150	function Hero(rd)
    151	{
    152		this.image = new Image();   // 主人公
    153		this.v_x = 1.0;   // 主人公の水平方向移動速度
    154		this.v_y = 0.0;   // 主人公の垂直方向移動速度
    155		this.up = 0.0;   // 主人公の上向き加速度
    156		this.down = 0;   // 主人公の下向き加速度
    157		this.jump = false;   // 主人公がジャンプ中か?
    158		this.width = 32;   // 主人公の幅
    159		this.height = 52;   // 主人公の高さ
    160						// 主人公の読み込み
    161		this.image.src = "image/char.jpg";
    162						// 背景の初期位置
    163		this.x = 0;
    164		this.y = mp.canvas.height - rd.height - this.height;
    165		return this;
    166	}
    			
    050 行目~ 055 行目

      ステップ2の場合と同様,Road オブジェクトのプロパティ state と変数 p の値に基づき,現在位置の水平方向の状態を調べています.変数 sw が負の値の場合は,ゴール位置を通り過ぎたことを意味しますので,ゲームオーバーとなります( 056,057 行目).

    060 行目~ 072 行目

      主人公がジャンプ中であった場合の処理です(ジャンプ中でない場合は,ステップ2と同様の扱いで良い).061 行目は,主人公が地面より下に位置するかをチェックしています.正確さを求めるならば,ちょうど着地したか,つまり,
    	if (gp.hr.y == mp.canvas.height - gp.rd.height - gp.hr.height) {				
    という判定にすべきですが,時間的なタイミングから,この条件を満たすことは非常に難しくなります.そこで,最初に地面を通過した段階で着地処理を行っています.勿論,その位置が谷であった場合はゲームオーバーとなりますので,062 行目において谷でないことをチェックしています.着地した場合は,再びジャンプできるようにし( 063 行目),加速度,速度,垂直位置を再設定しています.なお,主人公が地面より上にいる場合は,現在の状態をそのまま継続させます( 070,071 行目).

  5. ステップ4: 完成( BGM 付き)

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

    • HTML ファイル: 20 行目
    • GamePanel: 009 ~ 011 行目,080 ~ 081 行目

    01	<!DOCTYPE HTML>
    02	<HTML>
    03	<HEAD>
    04		<TITLE>アクションゲーム:BGM 付き(完成)</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>アクションゲーム:BGM 付き(完成)</H1>
    15		<CANVAS ID="canvas_e" STYLE="background-color: #ffffff;" WIDTH="400" HEIGHT="300"></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="Action_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.draw()', 30);
    016						// マウスリスナの追加(マウスボタンが押された時と離された時)
    017		mp.canvas.addEventListener("mousedown", gp.onMouseDown);
    018		mp.canvas.addEventListener("touchstart", gp.onMouseDown);
    019		mp.canvas.addEventListener("touchmove", gp.onMouseDown);
    020		mp.canvas.addEventListener("mouseup", gp.onMouseUp);
    021						// ボタンの表示制御
    022		document.getElementById('method').style.display = "none";
    023		document.getElementById('start').style.display = "none";
    024		document.getElementById('first').style.display = "none";
    025		document.getElementById('finish').style.display = "none";
    026	}
    027				//
    028				// GamePanel オブジェクト(プロパティ)
    029				//
    030	function GamePanel()
    031	{
    032		this.timerID = -1;
    033		this.rd = new Road();   // Road オブジェクト
    034		this.hr = new Hero(this.rd);   // Hero オブジェクト
    035		return this;
    036	}
    037				//
    038				// GamePanel オブジェクト(メソッド draw)
    039				//
    040	GamePanel.prototype.draw = function()
    041	{
    042						// キャンバスのクリア
    043		mp.ctx.clearRect(0, 0, mp.canvas.width, mp.canvas.height);
    044						// 移動
    045		gp.rd.x += gp.rd.v_x;
    046		gp.hr.v_y += (gp.hr.up - gp.hr.down);
    047		gp.hr.x += gp.hr.v_x;
    048		gp.hr.y -= gp.hr.v_y;
    049						// 描画
    050		mp.ctx.drawImage(gp.rd.image, gp.rd.x, gp.rd.y);
    051		mp.ctx.drawImage(gp.hr.image, gp.hr.x, gp.hr.y);
    052						// 状態のチェック
    053								// 水平位置のチェック
    054		let p  = gp.hr.x - gp.rd.x + gp.hr.width / 2;
    055		let sw = -1;
    056		for (let i1 = 0; i1 < gp.rd.state.length && sw < 0; i1++) {
    057			if (p <= gp.rd.state[i1][0])
    058				sw = gp.rd.state[i1][1];
    059		}
    060		if (sw < 0)   // ゴールを通過
    061			sw = 1;
    062								// 垂直位置のチェック
    063		else {
    064			if (gp.hr.jump) {
    065				if (gp.hr.y >= mp.canvas.height - gp.rd.height - gp.hr.height) {
    066					if (sw == 0 || sw == 10) {
    067						gp.hr.jump = false;
    068						gp.hr.down = 0.0;
    069						gp.hr.v_x  = 1.0;
    070						gp.hr.v_y  = 0.0;
    071						gp.hr.y    = mp.canvas.height - gp.rd.height - gp.hr.height;
    072					}
    073				}
    074				else
    075					sw = 0;
    076			}
    077		}
    078								// ゲーム状態変更
    079		if (sw > 0) {
    080			document.getElementById('BGM').pause();   // BGMのために追加
    081			document.getElementById('BGM').load();   // BGMのために追加
    082			clearInterval(gp.timerID);   // タイマーの停止
    083			if (sw == 1)
    084				gop_start();   // ゲームオーバー
    085			else
    086				gcp_start();   // ゲームクリア
    087		}
    088	}
    089				//
    090				// GamePanel オブジェクト(メソッド onMouseDown)
    091				//
    092	GamePanel.prototype.onMouseDown = function(event)
    093	{
    094		if (!gp.hr.jump) {
    095			gp.hr.up   = 1.0;
    096			gp.hr.down = 0.5;
    097			gp.hr.v_x  = 2.0;
    098			gp.hr.jump = true;
    099			gp.hr.y--;   // 着地判定のため,1 ピクセルだけジャンプさせておく
    100		}
    101	}
    102				//
    103				// GamePanel オブジェクト(メソッド onMouseUp)
    104				//
    105	GamePanel.prototype.onMouseUp = function(event)
    106	{
    107		gp.hr.up   = 0.0;
    108		gp.hr.down = 0.5;
    109	}
    110				//
    111				// Road オブジェクト(プロパティ)
    112				//
    113	function Road()
    114	{
    115		this.image = new Image();   // 背景
    116		this.v_x = -2.0;   // 背景の水平方向移動速度
    117		this.width = (mp.level == 1) ? 643 : 557;   // 背景画像の幅
    118		this.height = 102;   // 背景画像の高さ
    119		this.s1 = new Array()
    120		this.s1[0] = new Array(147, 0);
    121		this.s1[1] = new Array(204, 1);
    122		this.s1[2] = new Array(352, 0);
    123		this.s1[3] = new Array(494, 1);
    124		this.s1[4] = new Array(642, 10);
    125		this.s2 = new Array();
    126		this.s2[0] = new Array(73, 0);
    127		this.s2[1] = new Array(139, 1);
    128		this.s2[2] = new Array(213, 0);
    129		this.s2[3] = new Array(330, 1);
    130		this.s2[4] = new Array(404, 0);
    131		this.s2[5] = new Array(482, 1);
    132		this.s2[6] = new Array(556, 10);
    133		this.state = null;   // 背景(道)の状態
    134		                     //   state[i][0] : 状態変化の終了位置
    135		                     //   state[i][1] : 道の状態
    136		                     //                   =0 : 道
    137		                     //                   =1 : 谷
    138		                     //                   =10 : ゴール
    139						// 背景の読み込みと状態設定
    140		if (mp.level == 1) {
    141			this.image.src = "image/road1.jpg";
    142			this.state = this.s1;
    143		}
    144		else {
    145			this.image.src = "image/road2.jpg";
    146			this.state = this.s2;
    147		}
    148						// 背景の初期位置
    149		this.x = 0;
    150		this.y = mp.canvas.height - this.height;
    151		return this;
    152	}
    153				//
    154				// Hero オブジェクト(プロパティ)
    155				//
    156	function Hero(rd)
    157	{
    158		this.image = new Image();   // 主人公
    159		this.v_x = 1.0;   // 主人公の水平方向移動速度
    160		this.v_y = 0.0;   // 主人公の垂直方向移動速度
    161		this.up = 0.0;   // 主人公の上向き加速度
    162		this.down = 0;   // 主人公の下向き加速度
    163		this.jump = false;   // 主人公がジャンプ中か?
    164		this.width = 32;   // 主人公の幅
    165		this.height = 52;   // 主人公の高さ
    166						// 主人公の読み込み
    167		this.image.src = "image/char.jpg";
    168						// 背景の初期位置
    169		this.x = 0;
    170		this.y = mp.canvas.height - rd.height - this.height;
    171		return this;
    172	}
    			

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