初心者でも2時間くらいでシューティングゲームを作ろう!

ブラウザゲーム、作りたいかあ!

前回の記事

初心者でも2時間でブラウザゲームを作ってWebにアップしよう!

前回、2時間でブラウザゲームを制作する方法を執筆しました。

みなさん、あれからゲーム制作を続けていますでしょうか。

え?前回教わった内容だとロクなゲームを制作できないって?

確かに前回の記事を体得したところで、的をクリックするだけのゲームしか作れませんでした。

どうせならもっとゲームっぽいものを作りたい!と思った人も多いと思います。

今回はゲームの王道シューティングゲームを作れるようになりましょう!

今回は、ゲーム制作のアルゴリズムに注力してますので、前回よりは幾分難しくなっております。今回も前回同様サンプルコードをいじって覚えていくスタイルで行きますので、みなさんぜひコードをいじりながらプログラミングを学んでもらえればと思います。

まずは環境改善 Visual Studio Codeの導入(10分)

前回、プログラムを書くテキストエディタとしてterapadをご紹介しました。

初心者用にシンプルでわかりやすいものを選んだのですが、これだと複雑なプログラムを書くとすぐに行き詰ってしまうでしょう。

そこで今回、本格的なプログラム制作環境を導入して作業環境をリッチにしていきましょう。

以下のリンクから、データをダウンロードしてパパっとインストールしてください。

https://www.microsoft.com/ja-jp/dev/products/code-vs.aspx

この手のインストールは難しくてわかんない!という人は↓のリンクを見ながらいれましょう。

・【ゼロから!】Visual Studio Codeのインストールと使い方

https://eng-entrance.com/texteditor-vscode

一点注意事項として、インストール途中にある以下の赤枠にチェックを入れておいてください。この後の説明ではこの項目の機能を使ってvisual studio codeを起動するからです。

無事にインストール完了したでしょうか。次は実際にサンプルコードをいじりつつ、visual studio codeに慣れていく作業をしていきます。

サンプルコードをvisual studio code で開く(15分)

今回いじるサンプルコードはこちらです。(4/22 バグがあったので修正しました)

Templete

(画像はenchant.js Material からお借りしました。)

ダウンロードして、zipを解凍しましょう。

解凍したら、Templeteフォルダ内で右クリックをして、下の赤枠をクリックしましょう。

(インストール時の設定項目で、[Codeで開く]の欄にチェックをいれないと、この項目はでてきません。)

こんなカッコいい画面が出てくると思います。

もうこれで気分はウキウキのプログラマですね。中学生だったらこれだけで友達に自慢したくなるような画面です。

このVisual Studio Codeですが、細かい使い方については「Visual Studio Code 初心者」でテキトーに検索すれば見つかると思います。今回は、最小限の使い方だけ説明します。

左の項目が「フォルダ内のファイル一覧」となっています。

前回の講座で説明した通り、基本的にScript.jsだけいじればよいので、Script.jsをクリックしましょう。

コードが表示されます。色分けされているので、すんごく見やすくなっているのがわかります。

実際にコードを書いてみると、使いやすさがわかるはずです。

おかしなコードを書くと赤い~~~線でおかしな場所を指摘してくれる校正機能があったり、

現在使える変数名・関数名を予測する自動補完機能があったりります。

原始人が石斧から機関銃に切り替えたような気分を味わうことができるでしょう。

この状態でも全然よいのですが、今回は設定から「データ保存時に自動でコードを整形してくれる機能」を追加します。

左上のタブからファイル⇒基本設定⇒設定を開いてください。

画面下を見てください。上のテキストボックスは検索ボックスです。

editor.formatOnsaveの項目を変更したいので、検索ボックスに「editor.formatOnsave」と書くとその場所に移動することができます。

「editor.formatOnsave」の文の左あたりにマウスを合わせるとペンマークがでるので、クリックして編集を押してください。

true かfalseか選択する欄がでてくるので、trueを選択します。

すると、画面下ように右に変更後の画面が出てきます。

これで設定完了です。

Script.jsに戻ります。

試しに適当にプログラムを書いてみて、Ctrl+s(保存のショートカット)を押してみましょう。

コードが自動整形されます。この機能、めっちゃ便利です。

演習 色々いじって、visual studio codeに慣れよう! (5分)

いよいよプログラム解説へ(合計90分)

いよいよプログラムの解説に入ります。

今回はがっつり90分とってみました。これでさすがに間に合うんじゃないかなあと思います。

とりあえずゲームを起動させましょう。下画面のようにフォルダのindex.htmlをクリックしてください。

こんな画面が出てくると思います。

画面をクリックすると、画面下の剣士が爆弾を飛ばします。

シューティングですね!

演習! しばらく遊んでみよう!(1分)

さて、ここから本格的にアルゴリズムの部分の説明をします。

とりあえず私が持っている最低限のエッセンスを詰め込んだので、ぜひ覚えてもらえればと思います。

わからないことがあっても、なんとなくの理解で進めてみましょう。

実際にプログラムを変更してもらう方が理解は進むと思います。

ちなみにプログラムわかるから、アルゴリズムの要点だけ教えて!という人へ解説しますと、

「敵オブジェクトを生成する度に配列にpushして管理すると、自機の球との衝突判定めっちゃ楽だよ!」

という点が見所です。

画像のプリロード(13行目) (15分)


for (var i = 1; i <= 6; i++) {
eval('var B_Image' + i + '="image/image' + i + '.png"');//eval関数を使ってimage/image〇のURLを取得して、変数B_Imageに格納
eval('game.preload([B_Image' + i + ']);');//ついでにプリロードまで済ませてしまう
}

データロード時に必要な画像を読み込んでおく処理です。

前回とは打って変わって、for文を使って連番で画像を読み込んでいます。

今回使用する画像はimageフォルダ内にあります。image1.png~image6.pngまで連番で設定しているので、これを一気に取得するコードを書いています。

本来、変数名に変数を使うことはできないのですが、eval関数を使用すると、コードを変数名を用いて記述することができます。

個人的に色々試してきましたが、この書き方で画像読み込みするのが一番楽でした。

自機の移動(125行目) (15分)

		//自機
		var S_Hero = new Sprite(32, 32);					//自機のサイズのspriteを宣言(数字は縦横サイズ)
		S_Hero.image = game.assets[B_Image3];				//自機画像
		S_Hero.moveTo(180, 400);							//自機の位置
		S_MAIN.addChild(S_Hero);							//S_MAINシーンに貼る
		S_Hero.time = 0;									//Sin波で自機を左右に移動させるので、カウントが必要
		S_Hero.onenterframe = function () {
			this.time++;									//カウントを1増やす
			this.x = Math.sin(this.time / 10) * 180 + 180;	//Sin波で自機を左右に移動させる
		};

onenterframeで示した関数内は毎フレーム呼び出されます。

今回は、この中でtime変数を1ずつ増やし、そのtimeを角度として移動する、中心180px、振幅180pxのsin波で移動する自機を作りました。

sinとかわからん!という方のために、以下の演習を用意しました。

ぜひ試してみてください。

演習! 以下のコードを書き換えて、どうなるか見てみよう!

(1)this.time/10⇒this.time/30に変更

(2)this.x = Math.sin(this.time / 10) * 180 + 180;

⇒this.x = Math.sin(this.time / 10) * 180 + 10;に変更

(3)this.x = Math.sin(this.time / 10) * 180 + 180;

⇒this.x = Math.sin(this.time / 10) * 10 + 180;に変更

演習を実際にやると、どのパラメータが動作に影響するかわかると思います。

Sin波はゲーム制作において強力な武器になるので、ぜひ使いこなしてください。

ちなみに、今回time変数はS_Heroの変数として持たせているので、

S_Hero内のonenterframe内で使用するときはthis.timeで取得します。

敵の出現(62行目)(15分)

		///////////////////////////////////////////////////
		//メインループ ここに主要な処理をまとめて書こう
		S_MAIN.time = 0;							//S_MAIN内で使用するカウント用変数
		S_MAIN.onenterframe = function () {
			S_Text.text = "現在:" + Score;					//テキストに文字表示 Scoreは変数なので、ここの数字が増える
			this.time++;						//毎フレームカウントを1増やす
			if (this.time >= 60 - Score) {				//カウントが60-Scoreを超えたら
				this.time = 0;

				var S_Monster = new Sprite(32, 32);					//スライムを配置
				S_Monster.image = game.assets[B_Image5];			//スライム画像
				S_Monster.y = 0;									//出現Y座標
				S_Monster.x = Math.random() * 360;					//出現X座標
				S_MAIN.addChild(S_Monster);							//S_MAINシーンに追加
				S_Monster.number = MonsterAry.length;				//自分がMonsterAryのどこにいるか覚えておく(削除するときに使う)
				MonsterAry.push(S_Monster);							//MonsterAry(モンスター管理用配列)に格納
				S_Monster.onenterframe = function () {				//モンスターの動作
					this.y += 2;									//下に降りる
					if (this.y >= 500) {							//画面下に入ったら
						game.popScene();							//S_MAINシーンを外して
						game.pushScene(S_END);						//S_ENDシーンを見せる
					}
					if (this.frame == 2) this.frame = 0;			//フレームを動かす処理
					else this.frame++;								//もし3フレーム以内なら次のフレームを表示
				};

			}
		};

敵の出現処理です。

S_MAIN.time変数を1ずつ増やして、一定以上だと敵モンスターを画面上に配置するようにしています。

if (this.y >= 500) と書かれている通り、もし画面下(this.y>500)に行ってしまったら、

S_END画面を表示するようにしています。

this.frameは現在表示している画像です。実はimage5.pngは以下の3枚画像を合わせた画像(16px*48px)を(16px*16px)のスプライトに代入しています。

このとき、スライムの画像は16px*16pxが3つ分に分割され、this.frame=0の時、左を表示、this,frame=1の時真ん中を表示・・・とthis.frameで表示画像を変更することができるのです。

今回はthis.frameを0~2で毎フレームごとに繰り返す処理を書いています。

シューティングなので、当たり判定の話をします。

今回、S_Monsterを新たに作るたびにMonsterAry配列にS_Monsterを入れています。

MonsterAryの配列は「球と敵との接触処理」で使用するためのS_Monster管理配列です。

なお、生成したS_Monsterは自分が格納されるインデックスをS_Monster.numに持っています。

この管理配列を生成した目的は、当たり判定の項で明らかになります。ぜひ覚えておいてください。

演習! 敵出現プログラムをいじろう!

(1)if (this.time >= 60 – Score)

⇒if (this.time >= 1 – Score) にしてみる。

(2) S_Monster.x = Math.random() * 360;

⇒ S_Monster.x = 100; にしてみる。

クリックで球を出す。そして当たり判定(91行目) (30分)

///////////////////////////////////////////////////
//クリックで球を発射
S_MAIN.ontouchend = function () {
	var S_Bomb = new Sprite(16, 16);				//爆弾画像のspriteを宣言(数字は縦横サイズ)
	S_Bomb.image = game.assets[B_Image4];			//爆弾画像
	S_Bomb.moveTo(S_Hero.x, S_Hero.y);				//自機の位置に持ってくる
	S_MAIN.addChild(S_Bomb);						//S_MAINシーンに貼る
	S_Bomb.onenterframe = function () {				//毎フレーム毎に実行
		this.y -= 5;								//上に進む
		for (var i = 0; i < MonsterAry.length; i++) {	//爆弾とスライムの衝突処理(MonsterAryに入っているすべてのモンスターに対して当たり判定を行う)
			if (MonsterAry[i] != null) {				//もう削除済みなら次のインデックスを見る
				if (Math.sqrt(((this.x - 8) - (MonsterAry[i].x - 8)) * ((this.x - 8) - (MonsterAry[i].x - 8)) + ((this.y - 8) - (MonsterAry[i].y - 8)) * ((this.y - 8) - (MonsterAry[i].y - 8))) < 32) {//衝突判定 Score++; //スコアを1足す var S_Effect = new Sprite(16, 16); //爆発エフェクト S_Effect.moveTo(MonsterAry[i].x, MonsterAry[i].y); //スライム画像と同じ位置に爆発エフェクトを設置 S_MAIN.addChild(S_Effect); //S_MAINシーンに表示 S_Effect.image = game.assets[B_Image6]; //爆発画像 S_Effect.onenterframe = function () { //毎フレーム処理 if (this.frame >= 5) this.parentNode.removeChild(this);			//フレームが最後だったら消える
						else this.frame++;												//そうでなかったら、フレームを1増やす
					};

					MonsterAry[i].parentNode.removeChild(MonsterAry[i]);				//スライムをS_MAINから外す
					MonsterAry[i] = null;												//管理用配列の自分の部分はNULLに置き換える
					this.parentNode.removeChild(this);									//thisは爆弾なので、爆弾を消す
					return;
				}
			}
		}

		if (this.y < -50) this.parentNode.removeChild(this);	//画面外に出たら、S_MAINシーンから外す。
	};
};

ontouchendはクリック時に行われるイベントリスナーです。

S_Bombを自機の座標に生成しています。

S_Bombはonenterframeで毎フレームごとに上へ移動していきます。

当たり判定ですが、MonsterAryという配列に敵のデータはすべて格納されているので、

MonsterAry内の全てのS_Monsterの座標とS_Bombの座標で衝突判定を行っています。

衝突判定は、円の衝突判定を使っています。円の衝突判定はよく使う処理なので丸暗記してください。

衝突後は、S_Monsterは画面から消えて、MonsterAryの該当インデックスにはnullを入れておきます。

MonsterAry[i].parentNode.removeChild(MonsterAry[i]);				//スライムをS_MAINから外す
MonsterAry[i] = null;												//管理用配列の自分の部分はNULLに置き換える
this.parentNode.removeChild(this);									//thisは爆弾なので、爆弾を消す

thisが画面から消したいSpriteオブジェクトとすると

this.parentNode.removeChild(this);という記述でSceneオブジェクトからSpriteオブジェクトを消すことができます。

よく使うので覚えておいてください。

敵に爆弾がぶつかると、爆発エフェクトが発生します。

	var S_Effect = new Sprite(16, 16);									//爆発エフェクト
	S_Effect.moveTo(MonsterAry[i].x, MonsterAry[i].y);					//スライム画像と同じ位置に爆発エフェクトを設置
	S_MAIN.addChild(S_Effect);											//S_MAINシーンに表示
	S_Effect.image = game.assets[B_Image6];								//爆発画像
	S_Effect.onenterframe = function () {								//毎フレーム処理
		if (this.frame >= 5) this.parentNode.removeChild(this);			//フレームが最後だったら消える
		else this.frame++;												//そうでなかったら、フレームを1増やす
	};

このS_Effectが爆発エフェクトです。

S_Effectのonenterframe内で、this.frameを0から毎フレームごとに増やしていき、this.frame=5になったら画面から爆発エフェクトが消えるようになっています。

このコードも演出でよく使う処理です。

演習! 球の当たり判定を変えてみよう!

init関数による初期化 (49行目) (5分)

		function init() {							//初期化用関数
			Score = 0;
			//画面のスライムをすべて削除する
			for (var i = 0; i < MonsterAry.length; i++) {
				if (MonsterAry[i] != null) MonsterAry[i].parentNode.removeChild(MonsterAry[i]);		//画面にスライムが残っていれば削除する
			}
			MonsterAry = [];		//モンスター管理用の配列を初期化
		}
		init();

リトライ機能を付けるとき、ゲーム内で変化した変数を初期値に戻す関数は必ず必要になります。ぜひ初期値を代入する関数を意識して制作してみましょう。

ちなみに、今回はこの関数内で「前回のゲームで残ったスライムを削除」するようにしています。

配列で表示オブジェクトを管理すると、こういったことができるので、とても便利ですよ。

さいごに (10分)

これですべての動作アルゴリズムの説明が終わりました。

このプログラムを自分の自由にいじってみて、ぜひ自分だけのシューティングプログラムを完成させてください。

まだわからないことが多すぎるよ!という人のために、以下演習問題を置いておくので、ぜひ解きながらプログラムを学んでいただけますと幸いです。

演習! 

(1)image3.pngを開いてください。実は自機もframeを変えると動くことがわかります。

131行目のonenterframe内にコードを追加して、自機の騎士をフレーム毎に動くようにプログラムを書き換えましょう。

(2)敵の移動を前に進むだけでなく、自機に向かって進むように書き換えてみましょう。

敵の座標と自分の座標がわかれば、移動方向は求められます。(ベクトルの概念を使ってみよう。)

(3)自機の球を3Wayに変更してみましょう。

球生成を3つにして、移動方向を変えてみると3Wayになります。

以上でした!ご質問などありましたらお気軽にご連絡ください。

スポンサーリンク

シェアする

  • このエントリーをはてなブックマークに追加

フォローする

スポンサーリンク