初心者でも二時間ぐらいでシナリオ付きゲームを作ろう! ~ゲーム制作編~

シナリオ付きゲームを作って周囲を感動の渦に巻き込みたい!

さて、↓前回の記事の実践編となります。

初心者でも二時間ぐらいでシナリオ付きゲームを作ろう! ~ゲームシナリオアイデア編~

今回もゲームファイルをお渡ししますので、自由にいじってゲーム制作を覚えてください。

今回使うゲームファイル

▼↓のファイルクリックしてダウンロードしてください。

Scenario.zip

ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー

・プログラム詳しい人向け

複雑なゲームを制作する時はclass、const、letが使用可能なES6以降の文法で制作した方が、より可読性の高いプログラムを作ることができます。

しかし、現状一つ前のバージョンであるES5までしか対応していないブラウザがまだまだあるのが現状です。(主にIE・・・!)

解決策として、ES6以降の文法をES5の文法に書き換える(トランスパイルする)ことができるbabel.jsがあります。どうせ後二年後にはES6が全ブラウザで実装されるので、せっかくならES6の文法で書いてしまった方がよいかもしれません。

ちなみにこの記事はES5の文法で書きます。なので変数宣言はvarを使います。

ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー

まずは自分で遊んでみましょう。ダウンロードしたファイルにあるindex.htmlを起動することで、ゲームを遊ぶことができます。

(完成したゲームは自由に公開してもよいですが、必ずindex.htmlにこのページのリンクと作者名:作っちゃうおじさんを記入してください。サイトの宣伝になるので!)

そして、これからこのプログラムをいじっていくことになりますが、

今回は特別企画として

「自分の考えたシナリオ付きゲームコンテスト」

を開催したいと思います。

たぶん、この記事を全部見終える頃には自由にシナリオ付きゲームが作れていると思いますので、

ぜひ自分の作った、あっと驚くようなゲームを以下のメールアドレスにお送りください。

お送りしてくれた暁には、専用ページにて紹介したいと思います。

ぜひ、奮って応募してくださいね!

【応募先】

mail: tukuchauojisan@gmail.com

データをzip圧縮してお送りください。

期間:2019/1/15~2019/12/31 (予定)

それでは、プログラムの説明に移りたいと思います。

プログラム内に大量のコメントアウトで説明を書いていますので、それを頼りに理解に努めてください。

また、今回も制作言語はjavascript (ライブラリにenchant.jsを使用)です。

便利な拡張機能をvisual studio codeで使えるようにする

今回もvisual studio code  (以下vscodeと呼ぶ)でプログラミングをしていきます。

まだインストールしていない方は上のリンクからダウンロードをしてください。

また、いくつかjavascriptを書きやすくするための拡張機能を使います。

とりあえず、全部入れてラクラクな環境を作ってください。

(1)保存時にコードを自動整形できるようにする

第二回で説明した「コードを自動で成形してくれる機能」を追加します。

第二回を見ていない方は以下の記事にジャンプして、説明を見てください。

ジャンプ後、ctrl+Fを押すと出てくる右上の検索機能のテキスト入力欄に「データ保存時に自動でコードを整形してくれる機能」と入力して検索をかけるとすぐに見つかります。

https://hothukurou.firebird.jp/blog/post-713

ちなみに、「データの保存」はvscodeもそうですが大抵のテキストエディタではctrl+Sで行えます。

ショートカットキーは覚えると効率よく作業ができるので、ぜひ使ってみてください。

(2)構文エラーを赤で表示させる

ESLintという拡張機能を使うことで、文法的におかしい部分を赤く表示することができます。

これを使うと細かいミスが減るので是非入れましょう。

以下に外部の説明ページを紹介します。

・Visual Studio CodeにESLint 機能を追加する -galife

https://garafu.blogspot.com/2016/12/vscode-eslint.html

(3){} を色で表示する

これは個人的に便利だと思った拡張機能なのでおススメするのですが、

Bracket Pair Colorizer というツールを以下のように入れてください。

{}が色分けされてものすごく見やすくなります。

他にも色々便利な拡張機能はあるのですが、そろそろゲーム制作に移りましょう。

各シーン解説と、ゲームを面白くする狙いについて

今回のプログラムは462行、Sceneオブジェクトが合計5シーンもあるので、結構混乱するかと思いますが一つ一つ分解して説明します。

各シーンを設置した狙いも含めて書いていますので、参考にしてください。

ちなみに、Sceneオブジェクトは全部大文字で記入することにしています。

BUTTONPUSH (149行~)

指が左右に動きます。クリックすると指が下に落ちて、ボタンを押せると次へ進みます。

ボタンを押せないとゲームオーバーシーンへ飛びます。

序盤に単純なゲームをプレイさせることで、プレイヤーにゲームの感情移入を促す効果があります。

ボタンを押すという「主体的にシナリオに参加する行為」を行うことで、プレイヤーの感情移入は一気に高まる、という作戦です。

別シーンからこのシーンを呼び出す時はgoBUTTONPUSH() 関数を使います。

goBUTTONPUSH関数を実行すると、このシーンを呼び出して、かつ初期化することができます。このシーンはゲームオーバー後のリトライ時に何度も呼び出されるので、そのたびに指の位置を上に置き直す等の初期化処理が必要になるのです。

ちなみに、今回5シーン全てに初期化関数を用意しています。

COMMETNOVEL (230行~)

ここからノベルパートです。クリックイベントで次のページに進みます。

ノベルで表示する内容は29行に書いています。ここで、プレイヤーに「自分がメテオスイッチを押してしまった」という緊急事態を把握させます。

プレイヤーは最初のシーンでボタンを押してしまったので、このノベル内容を深い感情移入を持って眺めることができる、という作戦です。

ATTACKMOON  (282行~)

月に特攻するシーンです。

月にぶつかれば次に進みます。外したらゲームオーバーに飛びます。

十分に感情移入させることができていれば、ここでゲームオーバーしてもすぐに再プレイすることになるでしょう。

月を破壊したい!メテオスイッチを押してしまったという、過去の過ちを精算したい!

・・・と思わせることができれば、この手法が上手くいった!ということになります。

ENDING (353行~)

エンディングのノベルです。

ここまで到達したプレイヤーに、最後のハッピーエンドを提示して、さわやかな余韻を残します。

ここで、このゲームで示したかった主題を提示してあげましょう。

今回は「過去の失敗を持って人は大人になる」でした。

主題に対して正直クソみたいな内容なので、誰かもっと良い内容を作ってください。

GAMEOVER  (405行~)

最後のシーンです。他シーンから飛んできた場合、どのシーンから飛んできたかを調べ、それによって結果テキストを変更します。

ゲームを布教させるためのSNS連携ボタンもここに設置しましょう。その方がプレイヤー経由でより多くのゲームを遊んでもらえます。

ゲームの構成解説

ノベルシーンの作り方(アルゴリズムとデータ構造を学ぼう)

今回はシナリオ付きゲームということで、ノベルシーンをどのように制作しているか気になっている方も多いと思うので、説明いたします。

28行目を見ましょう。ノベルのデータが格納されていると思います。

なんとなく想像つくと思いますが、ここをいじるとテキストが変更されます。

配列の中に連想配列が格納されています。

配列?連想配列?と疑問に思った方もいると思うので、解説URLをあげます。

https://www.sejuku.net/blog/27965

ただ、説明を呼んでも頭の中に???がたくさん浮かぶと思いますので、とりあえず使えればいいの精神で使い方を説明します。

例えばpushCometNovel[2].textには

“・・・ディスプレイには、隕石の落下時間が表示されている。”

が入っています。

pushCometNovel[index]のindexを0から進めていくことで、順々にテキストを取得することができます。

typeには文字を表示するtext以外に、背景画像を変更するimg、次のシーンに飛ぶjumpが入ります。そして、type:text以外の時は次にtype:textが来るシーンまで一気に処理されます。

こんな感じでノベルのデータを格納すると、編集もしやすいので使いやすそうです。

プログラムを書く時、どんなロジックで作ろうかと悩む方が多いと思いますが、

その際に「いかにしてデータ構造を作るか」を考えることが大切になります。

上記のようなデータ構造であれば、編集も容易に作れるから楽だろうなと思って作ってみました。このあたりは他のノベルツールのデータ構造を参考にしています。

ちなみに、type:imgの時はimgに書いてある画像番号を読み込みます。

imageフォルダの中にimage+(画像番号)と書いてあるファイルがあります。

こいつを14行目の以下コードで読み込んでB_IMG配列に画像のパスを格納しています。

B_IMG[読み込みたい画像番号]でimageフォルダ内の画像パスを表示できるようにしており、img:6と書かれていれば、image6.pngが読み込まれるという寸法です。

一応type:sndを作って音も任意のタイミングで鳴らせるようにしましたが、音を鳴らす機会がなかったので、使っていません。

次はこのデータ構造を表示するアルゴリズムについて話していきましょう。

このノベルのアルゴリズム部分に関してはプログラム初心者の方は流しながら見てください。

ちょっと難しい話が続きます。まあ、上記のデータ構造さえ理解できれば、自由にゲームを改造することができると思うので、「ああ、こんな感じでデータを解析して画面にひょうじしているんだな~」程度の認識で良いと思います。

さて、ちょっと難しい話がはじまります。

253行目をご覧ください。

上記のコードで「クリック時にノベルデータに沿ってノベルを進める処理」を実装しています。意外と短くてビックリされたかと思います。

では、順を追って説明します。

253行目の以下のコードで「文字が順に表示されるオブジェクト」を設置します。

setCrawlingText()関数は私が自作した関数で、「文字を順に表示することができるオブジェクト(crawlingTextオブジェクト)」を返す関数です。

var COMMETNOVELCrawlText = setCrawlingText("white");

文字を順に表示できる仕組みについてはクラスの概念などちょっと難しい話をする必要があるので省略します。ともかく、文字が順にぴゅーっと出てくる便利なオブジェクトを作っちゃうおじさんが実装してくれたから、そのまま使おうという認識で大丈夫です。

細かい使い方は後で説明します。

ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー

・プログラム詳しい方

enchant.jsではES5の仕様でも使える独自のClass定義方法があり、これを用いています。文字を表示するLabelクラスを継承して、onenterframe内で文字を順に表示する様にしています。

文字の表示速度変更や、表示文字を消去するなど便利なメゾットを用意しているので92行目のsetCrawlingText()関数を眺めながら自由に使ってください。

(ここまで)

ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー

さて、アルゴリズムの説明に移ります。

ontouchend=function(){(クリック時の処理)} でクリック時に命令を飛ばします。

this.stateには次に表示するノベルの番号が入っています。

this.state=0からクリックする度に1,2・・・と増やしていくことで次のノベルを表示させようとしています。

テキストが全て表示されるまでは、次のテキストを表示させないようにします。

COMMETNOVELCrawlText.check()関数は、現在テキストを表示途中ならfalse,表示完了すればtrueを返します。

if (

COMMETNOVELCrawlText.check()) {   (テキストが表示完了した時の処理)  }と書くことでテキスト表示完了時の処理を書くことができます。
var nextNovel =pushCommetNovel[this.state]; でnextNovelに次に表示するノベルデータを代入します。
例えばpushCommetNovel[2]なら
{ type: “text”, text: “・・・ディスプレイには、隕石の落下時間が表示されている。”, img: “”, snd: “”, scene: “” },
が中に入っています。
type:textなら次のテキストを表示するためにテキストを表示します。
crawlingTextオブジェクトのview関数を実行することで次の文章を表示することができます。
オブジェクトの関数?なにそれ?と思うかもしれませんが、以下のように書くことで実行することができます。
COMMETNOVELCrawlText.view(pushCommetNovel[this.state].text);
シーンに設置したCOMMETNOVELCrawlTextオブジェクトに.view(”表示する文字列”)で新しい文字列を表示することができます。
そして、this.stateを1足すことで、次のノベルデータを読み込むのです。
さて、typeがtextでない時、つまり背景画像を変更するimgだったり次のシーンへ移動するjumpだったりする時には以下の処理が働きます。
while (nextNovel.type != "text") { //そのノベルデータのtypeがtextでなければ、画像変更・音表示・画面遷移かである。
if (nextNovel.type == "img") { //imgなら画像を変更
COMMETNOVELBackImage.image = game.assets[B_IMG[nextNovel.img]]; //nextNovel.imgに画像番号を入れたので、その番号の画像に背景を変更
} else if (nextNovel.type == "snd") { //sndなら音を鳴らす
//ここにSEを鳴らす処理を加える(今回は使っていない)
} else if (nextNovel.type == "jump") { //jumpなら画面遷移で別シーンに移動する
if (nextNovel.scene == "attack") { //sceneにattackが入っているなら
goAttackMoon(); //ATTACKMOONに画面遷移する
return;
}
}
this.state++; //typeがtext以外ならstateを1足して
nextNovel = pushCommetNovel[this.state]; //nextNovelに次のノベルデータを読み込む
}</div>




whileはwhile(条件式)と書くことで、条件式を満たすまで中の処理を繰り返すことになります。

で、nextNovel.typeがtextでない限り、次のノベルデータを読み込んで処理することになります。

type==imgなら、背景の画像を別の指定した画像に変更します。

COMMETNOVELBackImage.image = game.assets[B_IMG[nextNovel.img]];   //nextNovel.imgに画像番号を入れたので、その番号の画像に背景を変更

type==jumpならsceneに書かれた文字列に従って、別のシーンへ移動させます。

今回は、ATTACKMOONシーン (月と戦闘機が表示される画面)に移動させるため、その為に必要な関数goAttackMoon()関数を呼び出しています。

                        
if (nextNovel.scene == "attack") {                              //sceneにattackが入っているなら</div>



    goAttackMoon();                                         //ATTACKMOONに画面遷移する</div>



    return;
}

type==sndの時、以下のコードで指定した音を表示させる処理を書いています。

game.assets[B_SND[next]].clone().play();</div>

以上で、アルゴリズムの説明は以上です。はじめにデータ構造を良いモノにできたので、実装はとてもシンプルに作れたことがわかります。

初心者の方は難しかったと思うので、まあ飛ばし飛ばし眺めながら、以下の演習を行ってください。

演習1 ノベルの内容を自分のオリジナルに変更してみよう!

演習2 表示する画像を変更しよう!

ちなみに、毎回ボタンを押すシーンを挟むのはめんどいので、450行目(最後の方)のgoBUTTONPUSH()関数をコメントアウト(//をつける)して、その下にgoCometNovel()と記入してください。

これで、次からはCOMETNOVELシーンから開始することができます。

ある程度いじってノベルゲームの使い方がわかったら、この講座も8割終了です。

エンディングのノベルも同じような話なので、残りのゲーム部分の話を軽く進めます。

便利な関数を作ろう!

ゲームの部分を話す前に、今回工夫した点をお話します。

ゲーム内でよく使う「文章を表示するlabelオブジェクトを作成する関数」「文章の後ろに半透明の黒を作成する関数」「背景に一色の色を塗ったシーンを作成する関数」を作成しました。

例えば、var scene=setScene(“blue”);と書くと、背景が青のSceneオブジェクトが生成されます。

部品部品を作って、組み合わせていくと、コードもすっきりと可読性の高いものになるので、色々試してみると良いと思います。

		//便利な関数群を定義
		//主にオブジェクト生成用の関数

		//ラベルオブジェクトを返す関数
		function setLabel(text) {
			//ポイント表示テキスト
			var label = new Label(); 					//テキストはLabelクラス
			label.font = "20px Meiryo";				//フォントはメイリオ 20px 変えたかったらググってくれ
			label.color = 'rgba(255,255,255,1)';		//色 RGB+透明度 今回は白
			label.width = 400;							//横幅指定 今回画面サイズ400pxなので、width:400pxだと折り返して二行目表示してくれる
			label.moveTo(0, 30);						//移動位置指定
			label.text = text;					//テキストに文字表示 Pointは変数なので、ここの数字が増える
			return label;						//作成したラベルを返す
		}

		//背景に引数の色を塗ったシーンを返す関数
		function setScene(color) {
			var scene = new Scene();					//シーン作成
			scene.backgroundColor = color; 			//S_MAINシーンの背景は黒くした
			return scene;
		}

		//テキスト裏にうす黒い背景をつける関数(引数で)
		function setTextBack(height) {					//テキスト裏に半透明の黒を貼るための関数
			var sprite = new Sprite(400, height);		//引数を縦の長さにして、
			sprite.backgroundColor = "black";			//このspriteを黒背景で塗りつぶす
			sprite.opacity = 0.5;						//透明度0.5で半透明にする
			return sprite;								//作成した半透明黒spriteを返す
		}

ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー

・プログラム詳しい方用

ES6以降の環境であれば、各シーンごとにClass作るとめっちゃ見やすいです。

初期化メゾットinit()、シーン表示メゾットview()を作れば十分使いやすい構成になるはず。

各シーン毎に別ファイルに書いて、後でwebpackで一つのファイルとして使うと見やすいと思います。

ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー

BUTTONPUSHシーンのアルゴリズム解説

さて、ここからは各ゲームパートの解説に移ります。

まずは下記シーンの解説から。

190行目のonenterframe関数内を見てください。

この関数で、手の動きを一通り記述しています。


		//手を動かす部分の毎フレーム処理
		BUTTONPUSH.onenterframe = function () {
			if (this.state == 0) {	//はじめはこれが呼ばれる。初期化処理
				this.time = 0;
				this.state = 1;
			} else if (this.state == 1) {	//手がSin波で左右に移動する
				hand.x = Math.sin(this.time / 10) * 160 + 180;
				hand.y = 30;
				this.time++;
			} else if (this.state == 2) {	//手が落ちていく
				hand.y += 10;
				if (hand.y >= 480) {		//手のY軸が一定以下で当たり判定処理
					if (hand.x > 170 && hand.x < 210) { //もし手がスイッチを押していたらstate=3に移動  game.assets[B_SND[2]].clone().play(); this.state = 3; } else { this.state = 9999; //失敗時はstate=9999に移動 } } } else if (this.state >= 3 && this.state <= 60) {//state=3になるとstateを時間計測として利用して1ずつ増やし、stateが60になったら次のシーンへ移動 this.state++; if (this.state == 60) { goCommetNovel(); //COMMETNOVELへ移動 } } else if (this.state == 9999) { //外した時の処理 hand.y += 10; //手はとりあえず画面下まで移動する if (hand.y >= 650) {						//yが650以降なら
					goGameOver(1);							//ゲームオーバーシーンへ移動 引数1は現在の状況が1ということ。goGameOver関数を見ると引数で現在状況を理解している
				}
			}
		}

this.stateで現在の状況を表しています。

this.state=0はgoPushButton()関数が呼ばれた直後のフレームで呼び出されます。

内容は、この後使うthis.timeを0にする処理だけで、すぐにthis.state=1になります。

this.state=1では、手が左右に移動する処理を書いています。

手のY座標を30px固定にして、this.timeの値を1ずつ加算しつつ、この値を角度にしたsin波でX座標を決めています。 +180の180が中心軸で、*160の160が振幅/2の値になります。

このあたりは、実際の動きを見ながら、よしなに調整をしました。

this.state=1の時のみ、ontouchend(クリック命令)からthis.state=2に飛ぶことができます。

		BUTTONPUSH.ontouchend = function () {				//BUTTONPUSHの画面クリック処理
			if (this.state == 1) {							//手がSin波で動いている状態state=1ならstate=2へ遷移する
				this.state = 2;
				game.assets[B_SND[1]].clone().play();
			}
		}

this.state=2では、手は下に落ちていきます。

				if (hand.y >= 480) {		//手のY軸が一定以下で当たり判定処理
					if (hand.x > 170 && hand.x < 210) {	//もし手がスイッチを押していたらstate=3に移動 
						game.assets[B_SND[2]].clone().play();
						this.state = 3;
					} else {
						this.state = 9999;		//失敗時はstate=9999に移動
					}
				}

手の位置が480px越えたら、ボタン押したかの判定を行います。
ボタンと手の画像がテキトーなので、手のx軸がどの位置ならボタン押したように見えるのかを実際に動かしながら調整したところ、170px<x<210px だったので、 これを分岐にしています。 ボタンを押せた->this.state=3;
ボタンを押せなかった->this.state=9999;

this.state=3の処理はちょっと特殊です。
3<=this.state<=60の間、毎フレームごとにthis.stateの値を加算して、ちょっとしたウエイト処理を作っています。
で、this.state=60になったら次のノベルシーンに飛びます。

			} else if (this.state >= 3 && this.state <= 60) {//state=3になるとstateを時間計測として利用して1ずつ増やし、stateが60になったら次のシーンへ移動
				this.state++;
				if (this.state == 60) {
					goCommetNovel();							//COMMETNOVELへ移動
				}

自分はよく使うウエイト処理の実現方法ですが、this.waittimeみたいな別の変数を用意してあげた方が、みやすいかもしれません。
適宜使い分けて、自分が楽な方で実装しましょう。

this.state=9999の時は、y座標に進み続けます。
そして、yが画面外に出てしまったら(y>=650)、ゲームオーバー画面に飛びます。

			} else if (this.state == 9999) {				//外した時の処理
				hand.y += 10;								//手はとりあえず画面下まで移動する
				if (hand.y >= 650) {						//yが650以降なら
					goGameOver(1);							//ゲームオーバーシーンへ移動 引数1は現在の状況が1ということ。goGameOver関数を見ると引数で現在状況を理解している
				}

goGameOver(1);でゲームオーバー画面を表示します。
引数の1は何かというと、ゲームオーバー時に「どのシーンから飛んだか」を数字で表しています。
1->ボタン押し失敗した時
2->最後の特攻を失敗した時
3->エンディング見れた時

で、gameoverシーンで数字を参照することで、結果表示テキストを変更しているのです。

さて、一通り説明したので、演習です。

演習! 自分で適当なワンクリックタイミングゲームを作ってみよう!

ホテルのウェルカムドリンクみたいなものです。

シンプルであればあるほど、ユーザーは最初のとっかかりを掴みやすいものです。

テキトーにsin波でタイミング当てるゲームを一つ、作ってみてください。

ATTACKMOON のアルゴリズム説明

さて、次はこのシーンの解説をします。

さっきのボタンを押す処理よりも簡単です。

316行目をご覧ください。

onenterframe内をみると、動きがわかると思います。

this.state=0の時、飛行機を0~90度の範囲で回転させています。

			if (this.state == 0) {									//飛行機の向きが動いているシーン
				this.time++;
				plane.rotation = Math.sin(this.time / 10) * 45 - 45;	//反復動作はとりあえずsinを使う。planeのrotationは度数入力が必要なので0~90が代入されるようにしている。
			}

planeは飛行機オブジェクトです。このオブジェクトの向きはplane.rotationで変更することができます。
このrotationは度数表記(0~360度)で数字を入れる必要があるので、45度を基準としたsin波で±45度this.timeの値が変わるごとに動くようにしました。

ここで、ontouchend関数内をみると、this.state=0の時のみ、クリックするとthis.state=1に飛べるようになっています。

ここで、「向いている方向に飛行機を飛ばす処理」を書く必要があります。

以下コードをご覧ください。

		ATTACKMOON.ontouchend = function () {										//クリック時処理
			if (this.state == 0) {													//state=0の時だけ処理する
				var planeRad = plane.rotation * Math.PI * 2 / 360;					//飛行機の角度計算 度数をrad表記に変更するため(PI*2/360)を乗算している
				plane.vx = Math.cos(planeRad) * 10;									//計算した角度[rad]からx方向移動量vxを決定
				plane.vy = Math.sin(planeRad) * 10;									//計算した角度[rad]からy方向移動量vyを決定
				this.state = 1;														//stateを1にして移動開始
				game.assets[B_SND[1]].clone().play();
			}
		}

plane.rotationは、飛行機の向きが格納されています。
Math.sin(),Math.cos()関数はラジアン表記なので、度数表記にMath.PI*2/360を掛け算してあげる必要があります。
計算後、ラジアン表記の角度(planeRad)を使うことでcos(planeRad)はX座標,sin(planeRad)はY座標を持つ移動ベクトルを作成することができます。

(このあたりは高校数学のベクトルの概念を使っています。覚えていない方は調べてみましょう!)

vx,vyは毎フレーム毎に現在座標に加算する値です。
this.state=1の時、planeの座標に毎フレーム毎にvx,vyを加算してあげることで、任意の方向へ飛ばす処理を作ることができます。

				plane.x += plane.vx;								//vx,vyは角度から計算した移動量。これを毎フレーム足していく
				plane.y += plane.vy;								//

				var planeCenter = { x: plane.x + 30, y: plane.y + 30 };		//飛行機の中心座標
				var cometCenter = { x: comet.x + 50, y: comet.y + 50 };		//隕石の中心座標
				var vector = {												//飛行機の座標ー隕石の座標で二点間のベクトル定義
					x: planeCenter.x - cometCenter.x,
					y: planeCenter.y - cometCenter.y
				};
				var dist = Math.sqrt(vector.x * vector.x + vector.y * vector.y);	//ベクトルから二点間の距離を計算
				if (dist < 60) { //二点間が60px未満なら当たったことにする game.assets[B_SND[3]].clone().play(); goEnding(); //ENDING sceneに移動 } if (plane.x > 420 || plane.y < -100) {								//飛行機が画面外に出たらgameover
					this.state = 9999;												//stateを9999にする。 state=9999はゲームオーバー処理を書く
				}

そして、隕石と飛行機の当たり判定を考えています。
隕石と飛行機の中心座標を計算して、この二点の距離(dist)を計算します。
distの値が60px未満なら、隕石と衝突したとみなし、goEndingでエンディングに飛びます。

外した時は、飛行機はどこまでも飛んでいきます。
plane.xの値が420px以上の値(画面右枠外)になるか、plane.yの値が-100px以下の値(画面上枠外)になったら、ゲームオーバー画面に飛びます。

前回のシューティングでも使用した「二点の距離から当たり判定を行う」方法です。
ゲームプログラミングではしょっちゅう使う技なので、理解できるとゲームを作りやすくなると思います。

演習! 的に向かって当てるゲームを作ろう!

さて、隕石に飛行機を当てるゲームの解説が終わったところで、演習です。

イラスト差し替えだけでも、なんでもいいので、的に向かって当てるゲームを作ってみてください。

プログラムに変更を加えるならば、重力を使って放物線を描くようにするだけでも、ゲームとしての面白さが変わっていくんじゃないかと思います。

これで、一通りこのプログラムの要点を説明することができました。

さて、これからあなたはこのプログラムを改造して、

自分だけのシナリオ付きゲームを作成しなければなりません・・・・!

ゲーム制作の指針・・・!

アルゴリズムの要点は説明できたので、これからどう動けばゲームが完成するのか、説明したいと思います。

(1)シナリオを考える!

前回の記事を参考に、まずはシナリオを作ってみましょう。

今回のプログラムをそのまま改造するならば「最初のゲームの結果、やばいことになった!」

と進んでいくような物語になりそうです。

このあたりは、風呂にでも入りながらうんうんと唸って考えてみましょう。

(2)ゲームで使うイラストを作成する!

イラストを変えたらゲームの印象はガラッと変わるものです。

イラストを頑張って独自の世界観を構築しましょう。

(3)ゲームの内容を改造する!

プログラムをいじって、自分のシナリオやイラストに会うようなゲームを作りましょう。

困ったらsin波で動かせば、それっぽいのができるのでオススメです。

(4)最後まで作りきろう!

ここまで作れれば、後は最後まで作るのみです。

ぜひモチベーションを落とさないよう、時には妥協を重ねて、最後まで完成させてください。

コツは、あんま難しいことをせず、自分のできる範囲で作っていくことです。

経験上、2足以上で飛ぶと、できなさすぎてモチベーションが地面に突き刺さります。

このあたりは、自分との戦いですね。

完成した暁には、ゲームをネットにアップロードして、

多くのユーザーからたくさんのイイネ!をもらいましょう!

アップロードしない方も、ぜひ以下アドレスに自分の作ったゲームをお送りください!

このサイトにて、展示させていただきます!

【応募先】

mail: tukuchauojisan@gmail.com

データをzip圧縮してお送りください。

期間:2019/1/15~2019/12/31 (予定)

たくさんのご応募、お待ちしております!

以上!わからない点がありましたら、twitterやメール、このコメント欄にぜひご連絡ください

作っちゃうおじさんtwitter

シェアする

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

フォローする