ボスを作ってみよう(おまけ)

最後にサンプルのオリジナルボスの作り方の総復習として、以下のプログラムを使って見ていくことにします。
これは自サイト用の「The power of the meteorite #15 門を塞ぐ魔物」のボスのプログラムです。
ボスの大きさは250*249(約8ブロック*約8ブロック)
宣伝みたいになってすみません。

テスト用に別のパターン画を利用したもの。


プログラム

var init_f = false;	//  JavaApplet 起動時の初期化をしたかのフラグ

var cursor_x = 1;	//  カーソルで選択しているプレイヤー
var player_gen = 0;	//  現在のプレイヤー
var kettei_c = 0;	//  プレイヤー決定カウンター
var doc = null;

//  JavaApplet から全てのモードで  描画直前に呼び出される
function userJS(Offscreen_g,mode,view_x,view_y) {


	//  JavaApplet 起動時の初期化
	if(init_f == false) {
		init_f = true;

		//  JavaApplet 起動時の初期化
		userInitJS();
	}


	if(mode == 1) {
		//  タイトル画面

		//  JavaScript タイトル画面表示中を呼び出す
		userTitleJS(Offscreen_g);
	}
	else if(mode >= 100  &&  mode < 200) {
		//  ゲーム中
		if(doc.getJSMes() == 1) {
			//  ゲーム開始

			//  JavaApplet からのメッセージをクリアー
			doc.setJSMes(0);

			//  JavaScript ゲーム開始を呼び出す
			userGameStartJS();
		}

		//  JavaScript ゲーム中を呼び出す
		userGameJS(Offscreen_g,view_x,view_y);
	}
	else if(mode == 200) {
		//  ゲームオーバー
		userGameoverJS(Offscreen_g);
	}
	else if(mode == 300) {
		//  エンディング
		userEndingJS(Offscreen_g);
	}
	else if(mode == 400) {
		//  地図画面
		userChizuJS(Offscreen_g);
	}	
}


//  JavaApplet 起動時に JavaScript から1回だけ呼び出される
function userInitJS() {

	doc = document.getElementById("Applet1");
	//  タイトル表示中から、ユーザー操作によるゲーム開始をできなくする。
	doc.setTitleLock();


	//ゲーム中に使う外部のイメージを先読みし、ゲーム中での表示を滑らかにする
	img = new Array(9);
	img[0] = doc.newImageOnLoad("joshua.png");
	img[1] = doc.newImageOnLoad("cecil.png");
	img[2] = doc.newImageOnLoad("blade.png");
	img[3] = doc.newImageOnLoad("mira.png");
	img[4] = doc.newImageOnLoad("blackdrgon_a.png");
	img[5] = doc.newImageOnLoad("blackdrgon_b.png");
	img[6] = doc.newImageOnLoad("blackdrgon_c.png");
	img[7] = doc.newImageOnLoad("blackdrgon_d.png");
	img[8] = doc.newImageOnLoad("blackdrgon_e.png");
}


//  タイトル画面表示中に JavaScript から呼び出される
function userTitleJS(Offscreen_g) {

プレイヤーセレクトにつき省略

}

function userGameStartJS() {
	//ボスの設定
	boss_x = 16*32+1*32;			//ボスのx座標
	boss_y = 21*32+10*32;			//ボスのy座標
	boss_hp = 60;		//ボスのHP

	boss_hp_max = boss_hp;	//ボスの最大HP
	boss_jyoutai = 100;	//ボスの状態は待機中
	boss_x_mini = boss_x - 7*32 - 26;	//ボスの最小のx座標

	boss_sc = 0;		//ボスの射撃カウンター
	boss_bc = 0;		//ボスの爆発カウンター

	boss_x = boss_x + 128;	//ボスをいつもより右に4マス分後ろの配置

	boss_attack_wait = 0;	//ブラックドラゴンの攻撃待ち時間
	boss_attack = 0;	//ブラックドラゴンの攻撃フラグ

	laser_x = 0;		//レーザーのx座標
	laser_x2 = 0;		//レーザーの後ろのx座標
	laser_color = 0;	//レーザーの色

	/*
	キャラクター毎の最大HP
	1 ヨシュア
	2 セシル
	3 ブレイド
	4 ミラ
	*/
	if(player_gen == 1) {
		doc.setMyMaxHP(6);
	}
	else if(player_gen == 2) {
		doc.setMyMaxHP(5);
	}
	else if(player_gen == 3) {
		doc.setMyMaxHP(8);
	}
	
	else {
		doc.setMyMaxHP(10);
	}
	
	doc.showMyHP("HP");
}


function userGameJS(Offscreen_g,view_x,view_y) {

	//各種データ省略
	my_x = doc.getMyX();
	my_y = doc.getMyY();
	my_rx = doc.getMyXReal();
	my_ry = doc.getMyYReal();
	my_vx = doc.getViewXReal();
	my_vy = doc.getViewYReal();
	enemy = doc.getEnemyTotal();
	muki = doc.getMyDirection();






	//スクロールロックをする
	if(my_vx < boss_x_mini-384-32) {
		doc.setScrollLock(boss_x_mini-384);
	}

	//ボスの状態による処理
	if(boss_jyoutai == 50) {
		//ボス死亡
		boss_bc--;	//ボスの爆発カウンターをデクリメント(読みだす度に-1)
		if(boss_bc <= 0) {
			//ボスの爆発カウンターが0以下になったときの処理
			boss_jyoutai = 0;	//ボスの画像を描画しない
			doc.showImage(0,512,320,"blackdrgon_a.png");
			doc.hideGauge();
			//星を出す
			doc.setMapChip(7,23,8);
			doc.setMapChip(11,27,0);
			doc.setMapChip(11,26,0);
			doc.setMapChip(11,25,0);
			doc.setMapChip(11,24,0);
			doc.setMapChip(11,23,0);
			doc.setMapChip(11,22,0);
			doc.setMapChip(11,21,0);
		}
	}

	else if(boss_jyoutai == 100) {
		//ボス待機中。指定位置に主人公が来た場合状態を変更する
		if(my_vx >= boss_x_mini - 384) {
			boss_jyoutai = 200;
		}
	}

	else if(boss_jyoutai == 200) {
		//ボスの状態が変わり、ボスが右からフェードイン
		boss_x = boss_x - 8;
		
		//ボスが指定の位置に立った場合の処理
		if(boss_x <= boss_x_mini) {
			boss_x = boss_x_mini;
			boss_jyoutai = 300;
			boss_sc = 30;
			doc.setMapChip(11,27,29);
			doc.setMapChip(11,26,29);
			doc.setMapChip(11,25,29);
			doc.setMapChip(11,24,29);
			doc.setMapChip(11,23,29);
			doc.setMapChip(11,22,29);
			doc.setMapChip(11,21,29);
			if(my_rx > boss_x) {
				doc.setMyPosition(0,27);
			}
		}
	}

	else if(boss_jyoutai == 300) {
		boss_sc--;
		if(boss_sc <= 0) {
			boss_sc = 30;
			boss_attack = 100;
		}
	}

	//ボスと主人公の当たり判定
	if(boss_jyoutai >= 200) {
		//ボスは生きている

		//x座標とy座標の条件文で当たり判定を作る
		if(my_rx > boss_x-32 && my_rx < boss_x + 250) {
			if(my_ry > boss_y-32 && my_ry < boss_y + 249) {
				/*
					x軸の当たり判定はboss_xからboss_x+250の間
					y軸の当たり判定はboss_yからboss_y+249の間
				*/
				//範囲に入ったので主人公にダメージを与える
				doc.setMyHPDamage(1);

				//主人公のHPが0以下になった場合死亡させる
				if(doc.getMyHP() <= 0) {
					doc.setMyMiss(2);
				}
			}
		}
	}

	//ボスのしっぽ当たり判定
	if(boss_jyoutai >= 200) {
		//ボスは生きている

		atari = doc.attackTail(boss_x,boss_y+70,104,70);

		if(atari >= 1) {
			//ボスに命中
			boss_hp = boss_hp - atari;

			//ボスのHPが0以下になったので死亡処理を行う
			if(boss_hp <= 0) {
				boss_hp = 0;
				boss_jyoutai = 50;
				boss_bc = 20;
				boss_attack = 0;
			}
		}
	}


	//ボス画像の描画を行う
	if(boss_jyoutai >= 50 && boss_x - my_vx < 512) {
		if(boss_jyoutai == 50) {
			//爆発中のボスの画像を描画する
			Offscreen_g.drawImage(img[6],boss_x-my_vx,boss_y-my_vy,null);
		}
		else if(boss_jyoutai == 200 || boss_jyoutai == 300) {
			//ボスが生きている時の画像を描画する
			Offscreen_g.drawImage(img[5],boss_x-my_vx,boss_y-my_vy,null);
		}
		else if(boss_jyoutai == 250) {
			//ボスが口をあけた画像を描画する
			Offscreen_g.drawImage(img[4],boss_x-my_vx,boss_y-my_vy,null);
		}
		else if(boss_jyoutai == 260) {
			//ドラゴンテイル用画像
			Offscreen_g.drawImage(img[8],boss_x-my_vx,boss_y-my_vy,null);
		}
		else if(boss_jyoutai == 270) {
			//ドラゴンブレイズ用画像
			Offscreen_g.drawImage(img[7],boss_x-my_vx,boss_y-my_vy,null);
		}
	}


//ブラックドラゴンの攻撃分岐
	if(boss_attack == 100) {
		rnd = Math.floor(Math.random() * 100);
		if(rnd >= 0 && rnd < 40) {
			//ドラゴンフレイムへ移行
			boss_attack_wait = 30;
			boss_attack = 200;
			boss_jyoutai = 250;
		}

		else if(rnd >= 40 && rnd < 70) {
			//ドラゴンテイルへ移行
			boss_attack_wait = 30;
			boss_attack = 300;
			boss_jyoutai = 260;
		}

		else if(rnd >= 70 && rnd < 80){
			//ドラゴンブレイズへ移行
			boss_attack_wait = 30;
			boss_attack = 400;
			boss_jyoutai = 270;
		}
		else if(rnd >= 80 && rnd <90) {
			//炸裂する眼光へ移行
			boss_attack_wait = 30;
			boss_attack = 500;
			boss_jyoutai = 250;
		}
		else {
			//レーザーへ移行
			boss_attack_wait = 60;
			boss_attack = 600;
			boss_jyoutai = 250;
			xx = 290;
			yy = 170;
			haba = 0;
			takasa = 0;
			laser_x = boss_x;
			laser_x2 = boss_x;
		}
	}

//ドラゴンフレイム
/*口から火炎玉を吐く攻撃
ピカチー
*/
	if(boss_attack == 200) {
		boss_attack_wait--;
		//HP50以上の時
		if(boss_hp >= 50) {
			if(boss_attack_wait == 0) {
				doc.setEnemy(9,25,16);
			}
		}
		//HP40以上の時
		else if(boss_hp >= 40) {
			if(boss_attack_wait == 0) {
				doc.setEnemy(9,25,16);
			}
			else if(boss_attack_wait == 15) {
				doc.setEnemy(9,25,16);
			}
		}
		//HP30以上の時
		else if(boss_hp >= 30) {
			if(boss_attack_wait == 0) {
				doc.setEnemy(9,25,16);
			}
			else if(boss_attack_wait == 10) {
				doc.setEnemy(9,25,16);
			}
			else if(boss_attack_wait == 20) {
				doc.setEnemy(9,25,16);
			}
		}
		//HP20以上の時
		else if(boss_hp >= 20) {
			if(boss_attack_wait == 0) {
				doc.setEnemy(9,25,16);
			}
			else if(boss_attack_wait == 10) {
				doc.setEnemy(9,25,16);
			}
			else if(boss_attack_wait == 20) {
				doc.setEnemy(9,25,16);
			}
			else if(boss_attack_wait == 25) {
				doc.setEnemy(9,25,16);
			}
		}
		//HP20以下の時
		else {
			if(boss_attack_wait == 0) {
				doc.setEnemy(9,25,16);
			}
			else if(boss_attack_wait == 5) {
				doc.setEnemy(9,25,16);
			}
			else if(boss_attack_wait == 10) {
				doc.setEnemy(9,25,16);
			}
			else if(boss_attack_wait == 15) {
				doc.setEnemy(9,25,16);
			}
			else if(boss_attack_wait == 25) {
				doc.setEnemy(9,25,16);
			}
		}
		//ボスの攻撃が終わり待ち時間が指定の数値になった場合、状態を300に戻す
		if(boss_attack_wait <= -10) {
			boss_jyoutai = 300;
		}
	}

//ドラゴンテイル
/*
下から青い球を噴射する
グラーダ岩
*/
	if(boss_attack == 300) {
		boss_attack_wait--;
		if(boss_attack_wait == 0) {
			//しっぽから攻撃を繰り出す
			if(boss_hp >= 50) {
				doc.setEnemy(my_x,29,30);
			}
			else if(boss_hp >= 40) {
				doc.setEnemy(my_x,29,30);
				doc.setEnemy(my_x,29,31);
			}
			else if(boss_hp >= 30) {
				doc.setEnemy(my_x,29,30);
				doc.setEnemy(my_x,29,30);
				doc.setEnemy(my_x,29,31);
				doc.setEnemy(my_x,29,31);
			}
			else if(boss_hp >= 20) {
				doc.setEnemy(my_x,29,30);
				doc.setEnemy(my_x,29,30);
				doc.setEnemy(my_x,29,30);
				doc.setEnemy(my_x,29,31);
				doc.setEnemy(my_x,29,31);
				doc.setEnemy(my_x,29,31);
			}
			else {
				doc.setEnemy(my_x,29,30);
				doc.setEnemy(my_x,29,30);
				doc.setEnemy(my_x,29,30);
				doc.setEnemy(my_x,29,30);
				doc.setEnemy(my_x,29,31);
				doc.setEnemy(my_x,29,31);			
				doc.setEnemy(my_x,29,31);
				doc.setEnemy(my_x,29,31);
			}
		}
		if(boss_attack_wait <= -10) {
			boss_jyoutai = 300;
		}
	}

//ドラゴンブレイズ
/*
火炎玉を連射する
*/
	if(boss_attack == 400) {
		boss_attack_wait--;
		if(boss_hp >= 30) {
			if(boss_attack_wait <= 15 && boss_attack_wait >= 5) {
				//攻撃する
				doc.setEnemy(9,25,16);
			}
		}
		else {
			if(boss_attack_wait <= 25 && boss_attack_wait >= 0) {
				doc.setEnemy(9,25,16);
			}
		}
		if(boss_attack_wait <= -10) {
			boss_jyoutai = 300;
		}
	}

	if(my_vx >= boss_x_mini-384  &&  boss_jyoutai >= 50) {
		doc.showGauge(Math.floor(boss_hp*200/boss_hp_max),"ボスのHP  " + boss_hp + " / " + boss_hp_max);
	}


//炸裂する眼光
	if(boss_attack == 500) {
		boss_attack_wait--;
		if(boss_attack_wait == 10) {
			//攻撃する
			doc.setEnemy(my_x,29,27);
		}
		if(boss_attack_wait <= -10) {
			boss_jyoutai = 300;
		}
	}

//レーザー
	if(boss_attack == 600) {
		boss_attack_wait--;
		//口にエネルギーを蓄える演出
		if(boss_attack_wait  >= 40) {
			doc.setOffscreenColor(0,255,255,100);
			Offscreen_g.fillOval(xx,yy,haba,takasa);
			xx--;
			yy--;
			haba += 3;
			takasa += 3;
		}
		else {
			//口に溜めたエネルギーが縮小する演出
			doc.setOffscreenColor(0,255,255,100);
			Offscreen_g.fillOval(xx,yy,haba,takasa);
			xx++;
			yy++;
			haba -= 3;
			takasa -= 3;
			laser_x -= 32;

			if(laser_x <= -32*7) {
				laser_x2 -= 32;
			}
			//laser_x2 = laser_x + 280;
			if(laser_x > boss_x + 15) {
				laser_x2 = boss_x + 15;
			}
			
			if(my_rx > laser_x && my_rx < laser_x2) {
				if(my_ry > my_vy + 110 && my_ry < my_vy + 170 + 48) {
					//doc.setMyHPDamage(2);
					//if(doc.getMyHP() <= 0) {
						doc.setMyMiss(2);
					//}
				}
			}

			if(laser_x2 <= 0) {
				boss_jyoutai = 300;
			}

			if(laser_color == 0) {
				laser_color = 1;
				doc.setPenColor(0,255,255,100);
			}
			else {
				laser_color = 0;
				doc.setPenColor(0,255,255,192);
			}

			doc.showRect(100,laser_x - my_vx + 40,boss_y - my_vy + 120,laser_x2 - laser_x,64-16);
		}

	}
}


function userInitJS()内の説明

Javaアプレット起動時に実行され、全体の初期化を行っています。
まず、「document.getElementById("Applet1")」の記述を省略しています。
次にsetTitleLockメソッドでタイトル画面をロックしてタイトルクリックでゲームを開始できないようにします。
これはタイトル画面でプレイヤーセレクトを行うためです。
そして、imgの配列にゲーム中で使うすべての画像のイメージオブジェクトを取得します。
drawImageメソッドは画像の読み込みを待ってくれないので、予め画像を読み込んでおかないと画像の取得ができていないにも関わらず
画像を表示しようとするので、一瞬だけ画像が表示されない時間ができます。
FX Update10ではこのような問題は解決されておりますが、画像取得をしている間はゲームが止まってしまいます。
ですので、画像を先に読み込むようにしましょう。


function userGameStartJS()内の説明

ゲーム開始時にゲームの初期設定を行っています。
ボスのパラメータとしてボスのx,y座標、ボスのHPと状態、さらにボスの最小のx座標を用意。
変数boss_attack_waitはボスの攻撃待ち時間を入れるために使い、この値を使って、攻撃させるかどうかを決めます。
変数boss_attackはボスの攻撃状態を宣言しています。乱数を発生させ、その値の範囲によりこの変数にいくら値を代入するか決めます。
サンプルのレーザーもボスの攻撃として利用するのでそのままコピペ
それ以降の文は自サイト用のものなので気にしないでください。
ただ、4人プレイヤーセレクトを利用し、それぞれ最大HPが異なるだけです。
docはdocument.getElementById("Applet1")という文を省略しています。


function userGameJS()内の説明(基本部分)

まずよく使う座標関連のメソッドの記述を簡略化するために別の文を用意しています。
こうすることであとで自分がデバッグ作業をする時に見やすくなるのでこうしておくといいでしょう。

まずお断りしておきますが、実はスクロールロックは使っていません。記述してありますが、
まさおのスクロール設定でスクロールしないようにしています。

次にボスの状態を見ていきましょう
boss_jyoutaiという変数の値によってどういう処理を行うかを決めます。
今回は50でボスの死亡処理です。
boss_jyoutaiの値が50の場合は爆発カウンターの値を1減らします。
そして爆発カウンターが0になったらboss_jyoutaiの値を変更します。
そしてボスのHPを表すゲージを消し、指定した座標に「なにも置かない」を置きます。
これは後ほど出てきますが、主人公をボスの奥まで行かせないように透明のブロックを配置しています。
そのブロックを消すために、「なにも置かない」を指定した座標に配置しています。
そうすることによりボスを倒し、ボスの画像が消えたときに、一緒に透明ブロックも消えます。

100はすぐに200に以降させるだけ。

200はボスを左に移動させます。
ボスが一定の位置に達するとボスのx座標に値を代入して固定し、ボスの状態を300に移行させます。
さらに射撃カウンターに値を入れ、攻撃を決めるための時間を空けます。
そして主人公がボスの奥に行きすぎないように透明のブロックを配置し、万が一主人公がブロックより奥にいる場合は
主人公を指定した座標に飛ばします。

boss_jyoutaiが300の時には射撃カウンターを1減らし、その値が0以下になった場合に
射撃カウンターに値を代入し、ボスの攻撃状態を100にします。

ボスと主人公との当たり判定はボスが生きているとき、すなわちboss_jyoutaiの値が200以上の時に生成します。
このボスで使う画像が大きいので、当たり判定も大きく取ります。
もし、主人公が当たり判定内にいる場合は主人公のHPを1減らします。ただし、この場合にHPが0になっても
主人公は死なないので、HPが0以下になった場合に死亡する処理を施します。

また、このボスはしっぽ攻撃だけを受け付けるので、しっぽの当たり判定を作ります。
attackTailメソッドは指定した範囲内にしっぽ攻撃が入ると1を返すのでこの値を使ってボスのHPを1減らします。
ボスのHが0以下になったとき、ボスの状態を変更し、爆発カウンターを用いてボスの死亡する前の画像を描画する時間を決めます。
またボスが攻撃状態にある時にボスを倒し、ボスの攻撃状態をそのままにするとボスが死んでいるにも関わらず、攻撃を仕掛けるのでこの値を0とします。

次にボスの画像を描画をします。
ボスのそれぞれの状態でどの画像を描画するかを決めてます。
ボスの状態を細分化すればするほど、ボスの画像を多く指定することができます。


function userGameJS()内の説明(攻撃部分)

変数boss_attackの値を利用してボスに複数の攻撃技を持たせます。

boss_attackが100の時、0~99の乱数を発生させ、その値によってどの攻撃をさせるかを決めます。
すべてboss_jyoutaiを変更し、boss_attackを変更し、boss_attack_waitに値を代入します。

boss_jyoutaiが200の時にはボスが大口を開けた画像を描画し、口から攻撃させるようにしています。
またボスのHPに応じて敵を配置する時間を変え、弾数を増やしています。
boss_attack_waitが指定の時間になったら、boss_jyoutaiを300に変更し、次の攻撃の準備に入ります。
そのほかも同様です。

レーザー攻撃だけ敵コードを用いない攻撃なので、少し説明します。
まずこの状態に移行する時に他の攻撃法よりも攻撃時間を長く設定しています。

boss_attack_wait(60~40)

口元に円を描画します。幅と高さの値を増やすことで時間が経つごとに円が大きくなり、
口元でエネルギーを溜めているような演出になります。
円の描画位置を少しずらすことで固定位置から膨らんだように見せないようにしています。
描画位置を固定すると風船が膨らんでいるように見えます。

boss_attack_wait(40~)

まず最初に口元で大きくなった円を小さくしてやることでレーザーを放っているときにそのエネルギーを消費しているように見せます。
レーザーの左端のx座標と右端のx座標の処理の説明をします。
レーザーの左端のx座標をlaser_x,右端の座標をlaser_x2とし、初期値はどちらもボスのx座標とします。
この状態になるとはじめにレーザーの左端のx座標の値を32減らします。
この値が指定した条件になるとレーザーの右端のx座標の値を32減らします。
こうすることで口元からレーザーが発射されているように見えます。
またレーザーの右端のx座標が指定した条件になったときboss_jyoutaiを300に変更し、次の攻撃の準備に入ります。
当たり判定は試行錯誤して決めてください。