この記事はブロックされています。続きを読みたい方はログインをして下さい。会員ではない方は新規会員登録をして下さい。


Webプログラミング講座中級 第二回 クリックゲームを作る

前回はiPhoneとアンドロイドアプリ開発が行えるmonaca(モナカ)の説明とJQuery mobileを少し説明しました。
今回からは簡単なクリック()ゲームを発展させてゲームっぽくしていきます。
前回作ったゲームはゲームというにはあまりにもお粗末なものでした。ただクリックするだけだったので、これを発展させます。
ゲームらしくするためにイメージを複数用意して、タップしてもよいイメージとタップしてはいけないイメージの二種類用意します。こういった「してもよい」とか「してはいけない」といったルールをと呼びます。
ゲーム作りはこの制約をプログラム的に決めて行くことといっても過言ではありません。

ゲームは適切な制約を持たせることでゲームらしくなります。サッカーでもプレイヤーがボールを手で抱えたらサッカーにはならないし、サッカーの面白さはなくなりますよね。
RPGゲームでもフィールドをどこでも歩いていけて、ゲーム序盤からラスボスの場所までいきなり歩いていけたら面白くありません。フィールドや街にはプレイヤーが行けるところと行けないところを作る必要があります。

また戦闘でプレイヤーが死ぬことがなければ、プレイヤー自身の戦略というものも不要になので、プレイヤーはなにも考えなくなります。これもゲームとしてはつまりません。プレイヤー自身が不利になることでプレイヤーのコントロールテクニックが必須となります。制約は厳しすぎたり理不尽過ぎるのはいけません。プレイヤーの努力や知恵によって制約を突破できると面白くなります。

逆にプレイヤーがゲームのバグや意図しない操作によって、いともたやすく制約を突破できてしまうのもゲームとしては致命的です。この辺の駆け引きをゲームバランスと呼びます。
非常に奥が深いわけですが、とりあえずは簡単なものから作っていきましょう。

まずイメージを複数用意します。3つくらいでいいでしょう。
3つのうち1つはタップしてはいけないイメージで、1つだけが正解です。
正解したら正解したことを、間違ったら間違ったことをプレイヤーに教えます。
イメージは毎回同じ位置だとプレイヤーが学習してしまうので、毎回ランダムに変わる必要があります。
こんなところです。

まずは簡単なものから作っていきます。ベタに画像を表示してタップしたらメッセージが出ます。

<!DOCTYPE html>
<html lang="ja">
	<head>
		<title>タップゲームサンプル1</title>
		<meta name="viewport" content="width=device-width, initial-scale=1.0,minimum-scale=0.5, maximum-scale=2.0">
		<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
		<meta http-equiv="Content-Language" content="ja" />
		<meta http-equiv="Content-Style-Type" content="text/css" />
		<meta http-equiv="Content-Script-Type" content="text/javascript" />
		<link rel="stylesheet" href="//ajax.googleapis.com/ajax/libs/jquerymobile/1.4.2/jquery.mobile.min.css" />
		<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
		<script src="//ajax.googleapis.com/ajax/libs/jquerymobile/1.4.2/jquery.mobile.min.js"></script>

		<style type="text/css">
			#stage{ position: relative; }
			#hit_msg
			{
				color: white;
				font-size: 200%;
				position: absolute;
				top: 50%;
				text-align: center;
				width: 100%;
				display: none;
			}
			#chara
			{
				background-color: red;
				text-align: center;
			}
		</style>

	</head>

	<body>

	<h1>タップゲームサンプル1</h1>
	<br />
	ゾンビ画像だけをタップ()せよ!<br />

	<div id="container">
		<div id="stage">
			<div id="hit_msg"></div>
			<div id="chara"></div>
		</div>
	</div>

	<script>

		//スクリプトが全て読み込まれたら実行
		$( function() {

			//乱数で1~3までの自然数を得る
			$rnd = Math.floor(  * 3 + 1 );

			//変数に画像を代入する
			$img_civilian 	= "../images/02-civilian.png";	//民間人画像
			$img_enemy1 	= "../images/02-enemy01.png";	//ゾンビ1画像
			$img_enemy2 	= "../images/02-enemy02.png";	//ゾンビ2画像

			//1がでたら
			if( $rnd == 1 )
			{
				//画像を<div id="chara">に3つ追加
			    $( "#chara" ).append( '<img src="' + $img_civilian + '" />' );
			    $( "#chara" ).append( '<img src="' + $img_enemy1 + '" />' );
			    $( "#chara" ).append( '<img src="' + $img_enemy2 + '" />' );
			}
			//2がでたら
			else if( $rnd == 2 )
			{
				//画像を<div id="chara">に3つ追加
			    $( "#chara" ).append( '<img src="' + $img_enemy1 + '" />' );
			    $( "#chara" ).append( '<img src="' + $img_civilian + '" />' );
			    $( "#chara" ).append( '<img src="' + $img_enemy2 + '" />' );
			}
			//3がでたら
			else if( $rnd == 3 )
			{
				//画像を<div id="chara">に3つ追加
			    $( "#chara" ).append( '<img src="' + $img_enemy1 + '" />' );
			    $( "#chara" ).append( '<img src="' + $img_enemy2 + '" />' );
			    $( "#chara" ).append( '<img src="' + $img_civilian + '" />' );
			}


			//ゾンビ画像がタップ()されたら
			$( "#chara img[ src *= enemy ]" ).on( 'tap' , function(){

				//メッセージを表示
				$( "#hit_msg" ).css( 'display' , 'block' );
				//テキストを空にする
			    $( "#hit_msg" ).text( "" );
				//テキストを追加
			    $( "#hit_msg" ).text( "ゾンビだ!" );

			});


			//民間人画像がタップ()されたら
			$( "#chara img[ src *= civilian ]" ).on( 'tap' , function(){

				//メッセージを表示
				$( "#hit_msg" ).css( 'display' , 'block' );
				//テキストを空にする
			    $( "#hit_msg" ).text( "" );
				//テキストを追加
			    $( "#hit_msg" ).text( "しまった民間人だ!" );

			});


		});

	</script>

	</body>

</html>

▼サンプル

サンプルを実行してみると明らかに問題があることが分かります。
画面をリロードする度にゾンビと民間人の位置は変わるのですが、クリックをした時に二回目にはシャッフルされません。
またプログラムソースを見てみると乱数を作って、3つの画像に応じて3つの条件分岐を設定しています。
しかし、この方法では画像を増やす度にif文で条件分岐を増やす必要があり不便です。画像が3つのうちはいいのですが数十種類存在すると、位置をシャッフルするパターンは数百と増えていき、とてもif文で賄うことはできません。これは初心者がよくやってしまう失敗パターンです。

これを改作してみます。

<!DOCTYPE html>
<html lang="ja">
	<head>
		<title>タップゲームサンプル2</title>
		<meta name="viewport" content="width=device-width, initial-scale=1.0,minimum-scale=0.5, maximum-scale=2.0">
		<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
		<meta http-equiv="Content-Language" content="ja" />
		<meta http-equiv="Content-Style-Type" content="text/css" />
		<meta http-equiv="Content-Script-Type" content="text/javascript" />
		<link rel="stylesheet" href="//ajax.googleapis.com/ajax/libs/jquerymobile/1.4.2/jquery.mobile.min.css" />
		<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
		<script src="//ajax.googleapis.com/ajax/libs/jquerymobile/1.4.2/jquery.mobile.min.js"></script>

		<style type="text/css">
			#stage{ position: relative; }
			#hit_msg
			{
				color: white;
				font-size: 200%;
				position: absolute;
				top: 50%;
				text-align: center;
				width: 100%;
				display: none;
			}
			#chara
			{
				background-color: red;
				text-align: center;
			}
		</style>

	</head>

	<body>

	<h1>タップゲームサンプル2</h1>
	<br />
	ゾンビ画像だけをタップ(クリック)せよ!<br />

	<div id="container">
		<div id="stage">
			<div id="hit_msg"></div>
			<div id="chara"></div>
		</div>
	</div>

	<script>

		//スクリプトが全て読み込まれたら実行
		$( function() {

			//配列に画像を代入する
			A_img = new Array
			(
				"../images/02-civilian.png",	//民間人画像
				"../images/02-enemy01.png",		//ゾンビ1画像
				"../images/02-enemy02.png"		//ゾンビ2画像
			);


			//配列の中身をランダムに並び替える
			A_img.sort
			(
				function()
				{
					return Math.random() - 0.5;
				}
			);

			//配列をループさせ
			for( i = 0; i < A_img.length; i++ )
			{
				//imgタグを出力する <img src="../images/02-civilian.png">
			    $( "#chara" ).append( '<img src="' + A_img[i] + '" />' );
			}


			//ゾンビ画像がタップ(クリック)されたら
			$( "#chara img[ src *= enemy ]" ).on( 'tap' , function(){

				//メッセージを表示
				$( "#hit_msg" ).css( 'display' , 'block' );
				//テキストを空にする
			    $( "#hit_msg" ).text( "" );
				//テキストを追加
			    $( "#hit_msg" ).text( "ゾンビだ!" );

				//画像を削除する
				$( "#chara img[ src *= enemy ]" ).remove();
				$( "#chara img[ src *= civilian ]" ).remove();

				//配列の中身をランダムに並び替える
				A_img.sort
				(
					function()
					{
						return Math.random() - 0.5;
					}
				);

				//配列をループさせ
				for( i = 0; i < A_img.length; i++ )
				{
					//imgタグを出力する <img src="../images/02-civilian.png">
				    $( "#chara" ).append( '<img src="' + A_img[i] + '" />' );
				}


			});


			//民間人画像がタップ(クリック)されたら
			$( "#chara img[ src *= civilian ]" ).on( 'tap' , function(){

				//メッセージを表示
				$( "#hit_msg" ).css( 'display' , 'block' );
				//テキストを空にする
			    $( "#hit_msg" ).text( "" );
				//テキストを追加
			    $( "#hit_msg" ).text( "しまった民間人だ!" );

				//画像を削除する
				$( "#chara img[ src *= enemy ]" ).remove();
				$( "#chara img[ src *= civilian ]" ).remove();

				//配列の中身をランダムに並び替える
				A_img.sort
				(
					function()
					{
						return Math.random() - 0.5;
					}
				);

				//配列をループさせ
				for( i = 0; i < A_img.length; i++ )
				{
					//imgタグを出力する <img src="../images/02-civilian.png">
				    $( "#chara" ).append( '<img src="' + A_img[i] + '" />' );
				}


			});


		});

	</script>

	</body>

</html>

▼サンプル

ポイントは配列変数を用意して、その中にイメージのパスとファイル名を代入し「.sort()メソッド」と乱数の組合せてランダムに並べ替えてループ処理でランダムにソートされた配列をイメージタグとして出力しているところです。
配列は普通の変数が紙一枚であれば、ノートに相当します。1つの名前で複数の値を管理することができます。
これであれば配列にファイルを追加するだけで自動的にシャッフルされたイメージが表示されます。

//配列の中身をランダムに並び替える
A_img.sort
(
	function()
	{
		return Math.random() - 0.5;
	}
);

配列のソートなのですが、補足しておきます。「.sort()メソッド」は配列を並び替えるメソッドです。
「$array」があったらこのように使います。

$array = $array.sort();

こうすると配列がアルファベット順に並び替わります。
しかし、中身が数字だった場合はどうかというと数字の順番でソートされます。
単にソートしただけでは「0…1…2…3…4…」と並び替わるだけなのでシャッフルになりません。
そこで、「Math.random()」の登場です。この関数は「0~1」の値をランダムに得ることが出来ます。

例えばこんな感じになります。

Math.random() = 0.8297623253955364
Math.random() = 0.4525977650684866
Math.random() = 0.4953000824664153
Math.random() = 0.2156630963614533
Math.random() = 0.7482990295409434
Math.random() = 0.36396304948526914
Math.random() = 0.6886751526318338
Math.random() = 0.8392532189753433
Math.random() = 0.1255229211932436
Math.random() = 0.021935090905922938

ここで注意したいのは「Math.random()」バラつきがあるのです。
なぜかというと「Math.random()」ではぴったり0と1は出ないからです。小数点は出るのですがぴったりということはないので中央値に値が集中してしまいます。なので0と1の中央である0.5を引いてあげます。

「.sort()」の中に「function()」というものが出てきますが、これは「無名関数」と呼ばれるもので処理に割り込むことができます。通常の「.sort()」メソッドではソートだけでシャッフルはできません。
そこで無名関数を使って「.sort()」メソッドを拡張しています。

「無名関数」は難しい概念ですが、ちょくちょく出てくるので頭の隅に置いといて下さい。
因みに自然数の乱数を得る場合は以下のようにします。

$rnd = Math.floor( Math.random() * 3 + 1 );

1~3の乱数を得ることができます。

$rnd = Math.floor( Math.random() * 4 );

としたいところですが、前述したように乱数にはズレがあるので、「3 + 1」とするのがベターです。
特にプログラム的にそれほど問題でなければこだわる必要はありません。

話は戻って、まだまだ問題点があります。
イメージを一度クリックするとメッセージが表示されてシャッフルされるのですが、二回目からはシャッフルされない問題です。もう1つはまったく同じ処理が複数存在することです。

例えば、配列の中身をランダムに並び替えてループ処理で表示させる処理が3つも存在しています。
プログラミングではまったく同じ処理はひとつにまとめるのが定石です。

ひとつにまとめるには関数というものを使います。
関数は自動販売機のようなものです。硬貨投入口にコイン(値)を入れると取り出し口から商品(結果)を出してくれます。コインの種類や合計、選択する商品ボタンによって商品を変えることもできます。

関数は「function()」キーワードを使って作ることが出来ます。
関数を使って改作してみます。

<!DOCTYPE html>
<html lang="ja">
	<head>
		<title>タップゲームサンプル3</title>
		<meta name="viewport" content="width=device-width, initial-scale=1.0,minimum-scale=0.5, maximum-scale=2.0">
		<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
		<meta http-equiv="Content-Language" content="ja" />
		<meta http-equiv="Content-Style-Type" content="text/css" />
		<meta http-equiv="Content-Script-Type" content="text/javascript" />
		<link rel="stylesheet" href="//ajax.googleapis.com/ajax/libs/jquerymobile/1.4.2/jquery.mobile.min.css" />
		<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
		<script src="//ajax.googleapis.com/ajax/libs/jquerymobile/1.4.2/jquery.mobile.min.js"></script>

		<style type="text/css">
			#stage{ position: relative; }
			#hit_msg
			{
				color: white;
				font-size: 200%;
				position: absolute;
				top: 50%;
				text-align: center;
				width: 100%;
				display: none;
			}
			#chara
			{
				background-color: red;
				text-align: center;
			}
		</style>

	</head>

	<body>

	<h1>タップゲームサンプル3</h1>
	<br />
	ゾンビ画像だけをタップ(クリック)せよ!<br />

	<div id="container">
		<div id="stage">
			<div id="hit_msg"></div>
			<div id="chara"></div>
		</div>
	</div>

	<script>

		//スクリプトが全て読み込まれたら実行
		$( function() {

			//配列に画像を代入する
			A_img = new Array
			(
				"../images/02-civilian.png",	//民間人画像
				"../images/02-enemy01.png",		//ゾンビ1画像
				"../images/02-enemy02.png"		//ゾンビ2画像
			);


			//配列の中身をランダムに並び替えループでイメージを出力する
			SHOW_SHUFFLE_IMG( A_img );


			//ゾンビ画像がタップ(クリック)されたら
			$( "#chara img[ src *= enemy ]" ).on( 'tap' , function(){

				//メッセージを表示
				$( "#hit_msg" ).css( 'display' , 'block' );
				//テキストを空にする
			    $( "#hit_msg" ).text( "" );
				//テキストを追加
			    $( "#hit_msg" ).text( "ゾンビだ!" );

				//画像を削除する
				$( "#chara img[ src *= enemy ]" ).remove();
				$( "#chara img[ src *= civilian ]" ).remove();

				//配列の中身をランダムに並び替えループでイメージを出力する
				SHOW_SHUFFLE_IMG( A_img );

			});


			//民間人画像がタップ(クリック)されたら
			$( "#chara img[ src *= civilian ]" ).on( 'tap' , function(){

				//メッセージを表示
				$( "#hit_msg" ).css( 'display' , 'block' );
				//テキストを空にする
			    $( "#hit_msg" ).text( "" );
				//テキストを追加
			    $( "#hit_msg" ).text( "しまった民間人だ!" );

				//画像を削除する
				$( "#chara img[ src *= enemy ]" ).remove();
				$( "#chara img[ src *= civilian ]" ).remove();

				//配列の中身をランダムに並び替えループでイメージを出力する
				SHOW_SHUFFLE_IMG( A_img );

			});

		});

	//配列の中身をランダムに並び替えループでイメージを出力する
	function SHOW_SHUFFLE_IMG( A_img )
	{

		//配列の中身をランダムに並び替える
		A_img.sort
		(
			function()
			{
				return Math.random() - 0.5;
			}
		);

		//配列をループさせ
		for( i = 0; i < A_img.length; i++ )
		{
			//imgタグを出力する <img src="../images/02-civilian.png">
		    $( "#chara" ).append( '<img src="' + A_img[i] + '" />' );
		}

	}
	</script>

	</body>

</html>

▼サンプル

スッキリしました。しかし、そうなるとまだ他にも共通のコードがあります。

//メッセージを表示
$( "#hit_msg" ).css( 'display' , 'block' );
//テキストを空にする
$( "#hit_msg" ).text( "" );
//テキストを追加
$( "#hit_msg" ).text( "ゾンビだ!" );

//画像を削除する
$( "#chara img[ src *= enemy ]" ).remove();
$( "#chara img[ src *= civilian ]" ).remove();

上記と下記は「ゾンビだ!」と「しまった民間人だ!」以外は全て一緒です。

//メッセージを表示
$( "#hit_msg" ).css( 'display' , 'block' );
//テキストを空にする
$( "#hit_msg" ).text( "" );
//テキストを追加
$( "#hit_msg" ).text( "しまった民間人だ!" );

//画像を削除する
$( "#chara img[ src *= enemy ]" ).remove();
$( "#chara img[ src *= civilian ]" ).remove();

これも以下のようにまとめられます。差分は引数で可変させて対応します。

function HIT( $msg )
{
	//メッセージを表示
	$( "#hit_msg" ).css( 'display' , 'block' );
	//テキストを空にする
	$( "#hit_msg" ).text( "" );
	//テキストを追加
	$( "#hit_msg" ).text( $msg);

	//画像を削除する
	$( "#chara img[ src *= enemy ]" ).remove();
	$( "#chara img[ src *= civilian ]" ).remove();
}

使用する場合は「HIT( “しまった民間人だ!” );」と引数に表示したいメッセージを指定することで柔軟に対応できます。

次に結構ハマりやすいポイントなのですが、JavaScriptで動的に作った要素は注意が必要です。

JavaScriptはファイルを上から順に下に読み込まれていきます。全て読み込まれた後に実行されるのですが、読み込まれた後に動的に追加したり削除すると、それは「後出し」の情報となるので関知されない、という困ったことが起きます。
「.append()」で動的に追加とか「.remove()」で動的に削除するメソッドを使った場合にはイベントが動かない場合があります。

メソッドでイベントで定義した時に存在している要素であれば操作できるのですが、後で追加されたものは「.on()」メソッドで定義された時に存在しなかったので「オレ知らねー」となります。
そこで後付の要素にも対応するために再検索をかける必要があります。再検索には要素名を指定します。

$( "#chara img[ src *= civilian ]" ).on( 'tap' , function(){ …処理… } );

だったのを以下のように書き換えます。

$( "#chara" ).on( 'tap' , "#chara img[ src *= civilian ]" , function(){ …… } );

後者の違いはなにかというと、引数が1つ増えています。これはイベントを割り当てる要素名を特定するためのものです。
前者では「$( “#chara img[ src *= civilian ]” ).on」と直接画像の要素をしていました。
「#chara img[ src *= civilian ]」というのは、要素に付けられたIDが「chara」の中に含まれる「img」タグの「src」属性に「civilian」という文字を含むイメージという意味となります。「*=」は含まれるという意味です。因みに「=」で完全一致です。

これはJQueryのセレクタと呼ばれる強力な機能です。初級講座で少し説明しましたが、情報量が多いので参考ページを見て下さい。このサイトが分かりやすいです。

▼【jQuery】要素を指定するセレクタの使い方 まとめ

jQueryを使用してDOMの操作を行いたい場合に,セレクタの指定を簡単に行う方法を紹介します.使い方を覚えてしまえば要素の取得を最小限に抑えられ,処理の高速化にもつながります.

話は戻って前者では「$( “#chara img[ src *= civilian ]” ).on」で要素を探すのですが、「HIT()関数」の中で削除されてしまっています。その後で新規に追加されています。

//画像を削除する
$( "#chara img[ src *= enemy ]" ).remove();
$( "#chara img[ src *= civilian ]" ).remove();

削除されたものと新規に追加されたものは見た目は一緒なのですが、プログラミング的にはまったく別物です。なので探しても当然ありません。そこで、もう一度HTML内を探すことになります。
探す時に範囲を特定します。イメージは「chara」のDIVタグの中にあるので「$( “#chara” )」と指定します。
別に「$( “body” )」としてもいいのですが、捜索範囲が広くなり過ぎてメモリを多く使うので非効率的です。
次に「tap」の後に引数を追加して「#chara img[ src *= civilian ]」と指定することで新規に追加された画像を特定しているというわけです。

なんとなく分かったでしょうか?
最初から後者の書き方にしておけば後付けの要素にも対応できるので便利です。ハマりやすいので覚えておいて下さい。

まとめると以下のようになります。

関連記事