ヘビゲーム(完成)


留まることを知らない黄色い団子のようなブサイクな謎の宇宙生物「HEBI」を、
ブロックに囲まれた草原内のエサ(蝶)まで誘導しパワーアップさせましょう。
HEBIは非常に打たれ弱く、外壁と内壁、成長した自分自身に激突しただけで縮み上がってしまい、
3回ぶつけてしまうと完全に消滅してしまいます。
お腹いっぱいになる頃、上部に帰り道が現れるのでぶつけない様にして通り抜けてください。
難易度が上がる全5ステージを見事クリアすることでHEBIはハッピーエンドを迎えます。
「ショボい画像だ」というのはさておき、
塗りつぶしていただけの領域を画像に置き換えているだけでも面白そうに見えませんか?
ヒトは初めて目の当たりにする物事を五感(視覚、聴覚、嗅覚、味覚、触覚)のうち、
「まずは見ることから始める」機会が多く、見た目だけで判断されてしまうことも少なくないので、
ゲームであれば、プレイしてもらうために画像を使用して立派な出来栄えに仕上げると良いでしょう。


まずはステージ毎の設定を変更してみます。
尚、ステージ数は前回の3から5へと変更しています。
 1
 2
 3
 4
5
6
7
8
9
10
11
*start
	randomize
	stage = 0
	life = 3
	mission = 30, 40, 60, 80, 100
	infosize = 200
	deflen = 4, 5, 6, 8, 10	// ステージ開始直後の長さ
	speed = 10, 9, 8, 7, 6	// ステージ毎の進行速度
	font msmincho, 32, 1
	gosub *init
	return
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
*init
	size = 20
	clear = 0
	map = (ginfo_winx - infosize) / size, ginfo_winy / size
	dim tip, map * map.1
	repeat map * 2
		tip((cnt \ map) + cnt / map * (map * map.1 - map)) = 1
	loop
	repeat map.1 * 2
		tip(cnt \ map.1 * map + cnt / map.1 * (map - 1)) = 1
	loop
	repeat 16
		tip(((map.1 - 4) / 2 + cnt / 4) * map + (map - 4) / 2 + cnt \ 4) = 1
	loop
	gosub *draw_tips
	len  = deflen.stage // 長さを初期化
	item = 0
	dim dir, len
	dim posx, len
	dim posy, len
	repeat len
		dir.cnt = 4
		posx.cnt = deflen(length(deflen) - 1) - cnt // パーツ毎の初期位置を設定
		posy.cnt = 2
	loop
	return
1
2
3
4
5
6
7
8
9
10
11
12
13
*main
	repeat speed.stage, 1 // ステージ毎のループ回数(進行速度)を変更
		stick key
		if key & 5 : await (speed.stage - cnt) * 10 : break : else : await 10
	loop
	redraw 0
	gosub *move_char
	gosub *hantei
	gosub *draw_back
	gosub *draw_item
	gosub *draw_char
	redraw 1
	goto *main
前回、ステージ制を導入して、ステージ毎にクリアのノルマを設定しましたが、 今回はコレらに加え、ステージ初期のキャラの長さおよびステージ毎の進行速度を変化させてみました。 変更は上記の3ラベル内の青字箇所となります。 次にノルマ(ステージクリアに必要な長さ到達)達成すれば、 それ以上アイテムを取られない(表示されない)ように変更してみましょう。 何通りか方法はあると思いますが、次のようにすると一箇所だけで変更完了です。
 1
 2
 3
4
5
6
7
8
9
10
11
12
13
14
*draw_item
	if item = 0 {
		repeat 1
			if clear : itemx = -1 : break // ノルマ達成なら画面外に指定(=通行不可&非表示)
			itemx = rnd(map.0)
			itemy = rnd(map.1)
			if tip(itemy * map + itemx) : continue cnt
		loop
		item = 1
	}
	color 200, 255, 255
	i = itemx * size, itemy * size
	boxf i.0, i.1, i.0 + size - 1, i.1 + size - 1
	return
ステージクリア条件を満たしているなら、ランダムに表示するアイテムの位置を画面外にすることで、 通行できない位置により「クリア条件を満たせばアイテム位置と自キャラが重なっても無視する」処理が、 表示されない位置により「クリア条件を満たせばアイテムを非表示にする」処理がそれぞれ不要となります。 次に「クリア条件を達成して自キャラが画面外に出て行く時、情報表示の体長が縮む」問題を解決しましょう。 自キャラの座標は配列要素番号でもあるために、単純に画面外にずらすだけだとうまくいかないので、 自キャラの体長を1マス分ずつ縮める方法で「画面外へのゲート通過」を演出していました。 実際に体長を縮めているために情報表示の体長も1ずつ縮むのは仕方のないことですが、 プレイヤーにその情報を表示する必要は全くありませんね。 先ほど「クリア条件に達したらアイテムが表示されない」ようになりましたので、 自キャラの体長は変わることがありません。 よって、クリアゲートを通過してない時、以下のように情報表示エリアは書き換えないように変更します。
 1
 2
3
4
5
6
7
8
9
10
11
12
*draw_back
	pos 0, 0 : gcopy 1, , , map * size, map.1 * size
	if tip(posy * map + posx) ! 2 {
		color 255, 255, 200 : boxf ginfo_winx - infosize : color
		pos ginfo_winx - infosize + 10
		pos ,  10 : mes "ステージ" + strf("%3d", stage + 1)
		pos ,  50 : mes "ライフ" + strf("%5d", life)
		pos ,  90 : mes "体長" + strf("%7d", len)
		pos , 130 : mes "残り" + strf("%7d", mission.stage - len)
		pos , 170 : mes "成長率" + strf("%4d%%", (len - deflen.stage) * 100 / (mission.stage - deflen.stage))
	}
	return
なぜ、3行目が「クリアしたら書き換えない『if clear = 0』という条件」でないのかと言うと、 *hanteiラベルの処理が*draw_backラベルよりも先に処理されるので、 「if clear = 0」という条件ではクリア条件達成時に成長率100%の状態が情報表示されない為です。 「if tip(posy * map + posx) ! 2」では、クリアゲート通過時以外は描画する条件となりまして、 クリア条件達成〜通過するまでのターンは繰り返し描画される形となります。 現状で描画されると困るのは、「通過時の体長が縮んでしまう問題」を表示させない為だけであるので、 このような条件で全く問題ないのです。 9行目と10行目の変更は*draw_backラベル書き換えのついでに行ってみたもので、 クリア達成まであとどれくらい成長が必要なのかが分かるような残りアイテム数と、 現在どれくらい成長したかが分かるように成長率を情報表示エリアに追加してみました。 もし、完成図にあるような最長記録も付けるなら、以下のように付け加えてみてください。
 1
 2
 3
 4
 5
 6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
*move_char
	if posx = itemx & posy = itemy {
		dir(len) = dir(len - 1)
		posx(len) = posx(len - 1)
		posy(len) = posy(len - 1)
		len++
		if len > best : best = len // 最長記録を超えたなら記録を更新
		item = 0
	}
	if tip(posy * map + posx) ! 2 {
		repeat len - 1
			dir(len - cnt - 1) = dir(len - cnt - 2)
			posx(len - cnt - 1) = posx(len - cnt - 2)
			posy(len - cnt - 1) = posy(len - cnt - 2)
		loop
		if key & 1 : dir = dir / 2 + (dir <= 1) * 8
		if key & 4 : dir = dir * 2 - (dir >= 8) * 15
		posx += (dir = 4) - (dir = 1)
		posy += (dir = 8) - (dir = 2)
	} else {
		len--
	}
	return
 1
 2
 3
 4
 5
 6
 7
 8
9
10
11
12
13
*draw_back
	pos 0, 0 : gcopy 1, , , map * size, map.1 * size
	if tip(posy * map + posx) ! 2 {
		color 255, 255, 200 : boxf ginfo_winx - infosize : color
		pos ginfo_winx - infosize + 10
		pos ,  10 : mes "ステージ" + strf("%3d", stage + 1)
		pos ,  50 : mes "ライフ" + strf("%5d", life)
		pos ,  90 : mes "体長" + strf("%7d", len)
		pos , 130 : mes "最長" + strf("%7d", best)
		pos , 170 : mes "残り" + strf("%7d", mission.stage - len)
		pos , 210 : mes "成長率" + strf("%4d%%", (len - deflen.stage) * 100 / (mission.stage - deflen.stage))
	}
	return
さて、それでは今回のメインでもある「塗りつぶしから画像への変更による見た目アップ」作戦に移りましょう。 コチラの使用画像ファイルをダウンロードしてください。 20×20マスサイズのチップを横に並べた画像ファイルで左から順に、 [下地][][情報領域][アイテム][キャラ(左)][キャラ(上)][キャラ(右)][キャラ(下)][キャラ(体)]です。 この画像をカレントフォルダに置いて、以下のようにスクリプトを書き換えていきます。
 1
 2
 3
 4
 5
 6
 7
 8
9
10
11
12
13
14
15
*start
	randomize
	stage = 0
	life = 3
	mission = 30, 40, 60, 80, 100
	infosize = 200
	deflen = 4, 5, 6, 8, 10
	speed = 10, 9, 8, 7, 6
	buffer 2
	picload "hebi.bmp"
	gsel 0
	gmode 2
	font msmincho, 32, 1
	gosub *init
	return
 1
 2
 3
4
5
6
7
8
*draw_tips
	buffer 1
	repeat map * map.1
		pos cnt \ map * size, cnt / map * size
		gcopy 2, (tip.cnt = 1) * size, , size, size // フィールドを描画
	loop
	gsel 0
	return
 1
 2
 3
4
5
6
7
8
9
10
11
12
13
14
15
*draw_back
	pos 0, 0 : gcopy 1, , , map * size, map.1 * size
	if tip(posy * map + posx) ! 2 {
		repeat infosize / size * (ginfo_winy / size)
			pos cnt \ (infosize / size) * size + ginfo_winx - infosize, cnt / (infosize / size) * size
			gcopy 2, size * 2, , size, size // 情報表示領域下地を描画
		loop
		pos ginfo_winx - infosize + 10
		pos ,  10 : mes "ステージ" + strf("%3d", stage + 1)
		pos ,  50 : mes "ライフ" + strf("%5d", life)
		pos ,  90 : mes "体長" + strf("%7d", len)
		pos , 140 : mes "残り" + strf("%7d", mission.stage - len)
		pos , 170 : mes "成長率" + strf("%4d%%", (len - deflen.stage) * 100 / (mission.stage - deflen.stage))
	}
	return
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
*draw_item
	if item = 0 {
		repeat 1
			if clear : itemx = -1 : break
			itemx = rnd(map.0)
			itemy = rnd(map.1)
			if tip(itemy * map + itemx) : continue cnt
		loop
		item = 1
	}
	i = itemx * size, itemy * size
	pos i.0, i.1 : gcopy 2, size * 3, , size, size // アイテムを描画
	return
1
2
3
4
5
6
7
8
9
10
11
*draw_char
	repeat len
		i = posx.cnt * size, posy.cnt * size
		pos i.0, i.1
		if cnt ! 0 | tip(posy * map + posx) = 2 {
			gcopy 2, size * 8, , size, size // キャラの体を描画
		} else {
			gcopy 2, ((dir.cnt >= 2) + (dir.cnt >= 4) + (dir.cnt >= 8) + 4) * size, , size, size // キャラの頭を描画
		}
	loop
	return
起動後、一度しか処理されない*startラベル内で仮想バッファに画像をロードし、 *draw_tipsラベル内で壁と緑地帯を画像チップに置き換えます。 5行目の「(tip.cnt = 1) * size」は、マスが壁(=1)である時だけ壁チップを描画するもので、 通常の緑地帯(=0)や、クリアゲート(=2)マスは緑地帯チップを描画するような式となっています。 *draw_backラベル内では、完成図にあるように情報表示領域の背景をチップ画像描画に置き換えています。 *draw_itemラベル内でアイテムチップに、*draw_charラベル内でキャラクタの画像に置き換えています。 今までは、キャラの頭の先から尻尾の先まで単色塗り潰しだったので関係ありませんでしたが、 画像に置き換える場合、常に下向きのものでもソレはソレでアリなような気がしますが、 左なら左、下なら下…というような進行方向に見合った画像に置き換えるべきですね。 *draw_charラベル内の5行目の条件式で「cnt ! 0 | tip(posy * map + posx) = 2」とあります。 cntが0か否かの判断で頭とソレ以外のパーツを切り替えているのはわかると思いますが、 「tip(posy * map + posx) = 2」が何を意味しているかは分かりにくいかもしれませんので補足を入れます。 「先頭がクリアゲートに達したら1マスずつ縮めていく」方法を取っているので、 顔となる先頭部は通過後消えていなくては通過しているのではなく縮んでいるだけのようにしか見えません。 そのため、クリアゲート通過後は、顔のパーツ部も体のパーツに置き換えているのです。 8行目のgcopy命令のX基点座標パラメータにややこしい式が入っているのは、 if命令による切り分けを行わず1つにまとめたもので、展開した下記と同じ処理となります。
 1
 2
 3
 4
 5
 6
 7
8
9
10
11
12
13
14
*draw_char
	repeat len
		i = posx.cnt * size, posy.cnt * size
		pos i.0, i.1
		if cnt ! 0 | tip(posy * map + posx) = 2 {
			gcopy 2, size * 8, , size, size // キャラの体を描画
		} else {
			if dir.cnt = 1 : gcopy 2, 4 * size, , size, size // キャラの頭(左向き)を描画
			if dir.cnt = 2 : gcopy 2, 5 * size, , size, size // キャラの頭(上向き)を描画
			if dir.cnt = 4 : gcopy 2, 6 * size, , size, size // キャラの頭(右向き)を描画
			if dir.cnt = 8 : gcopy 2, 7 * size, , size, size // キャラの頭(下向き)を描画
		}
	loop
	return
以上で画像チップによる置き換えは完了です。 ダイアログメッセージはテキストを指定するだけでディスプレイ上にメッセージを表示できる便利な機能ですが、 ゲームで使用すると雰囲気が壊れてしまう場合もありますので、 今回の最後に、「ゲームらしくダイアログを独自のダイアログに変更してみる」例を紹介します。 既存のスクリプトを変更することなく独自のダイアログに置き換える方法として、 #undef#deffuncを使い、既存のdialog機能を置き換えてみましょう。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#module
#undef dialog
#deffunc dialog str msg, int mode, str ttlmsgg, local btnmsg, local btnid, local i
	if mode = 0 : btnid = 0 : btnmsg = "次へ"
	if mode = 1 : btnid = 0 : btnmsg = "再挑戦"
	if mode = 2 : btnid = 6, 7 : btnmsg = "はい", "いいえ"
	i.0 = 450, 200, 120 // ボックス幅, ボックス高, ボタン幅
	buffer 3, i, i.1
	font msmincho, 25
	color , 150, 100 : boxf 1, 1, ginfo_winx - 2, ginfo_winy - 2
	// タイトル
	color 255, 255, 255
	pos 10, 5 : mes ttlmsgg
	// ボタン	
	color 240, 255, 240
	boxf 5, 35, i - 6, i.1 - 6
	foreach btnmsg
		i.3 = i / length(btnmsg) * cnt + i / length(btnmsg) / 2 - (i.2 / 2) // ボタンの基点座標
		color , 150, 100
		boxf i.3, i.1 - 65, i.3 + i.2, i.1 - 20
		color 240, 255, 240
		boxf i.3 + 2, i.1 - 63, i.3 + i.2 - 2, i.1 - 22
		color , 50
		pos ginfo_winx : mes btnmsg.cnt // 文字列の長さ計測(ダミー)
		pos i.3 + (i.2 - ginfo_mesx) / 2, i.1 - 55 : mes btnmsg.cnt // ボタン中央に表示
	loop
	// メッセージ
	pos 10, 40 : mes msg
	color , 150, 100
	// 描画
	gsel 0, 1
	redraw 1
	pos (ginfo_winx - i) / 2, (ginfo_winy - i.1) / 2 : gcopy 3, , , i, i.1
	// 押下ボタンチェック
	i.4 = -1 // 押下ボタン番号
	repeat
		stick i.5
		if i.5 & 16 + 32 : i.4 = 0 : break // [Spece]キー・[Enter]キーはボタン0を意味する
		if i.5 & 256 {
			foreach btnmsg
				i.3 = i / length(btnmsg) * cnt + i / length(btnmsg) / 2 - (i.2 / 2)
				i.5 = mousex - ginfo_cx, mousey - ginfo_cy
				if i.5 > i.3 & i.5 < i.3 + i.2 & i.6 > i.1 - 65 & i.6 < i.1 - 20 {
					i.4 = cnt
					break
				}
			loop
		}
		if i.4 ! -1 : break
		wait 1
	loop
	return btnid(i.4) // ボタンIDから対応したstatに変換する
#global
上記をスクリプトの先頭に記述すればdialog命令の置き換えが完了です。 といっても、既存のdialog機能全てを置き換えているわけではなく、 使用している「確認ボタンのみ」と「Yes/No選択ボタン」の2通りだけしか対応してませんし、 dialogの第1パラメータに数値を指定することや、タイトル部の第3パラメータの省略は出来ません。 また、メインメッセージ部に4行以上を指定する、1列に全角17文字以上入れる等でもおかしくなります。 あくまで既存のもので影響が出ない程度のものなので、ボタン3つ以上に対応させたり、 メインメッセージがはみ出ないよう自動で改行させるには、ソレに見合った機能を実装する必要があります。 以上をもってヘビゲームのサンプルは終了としますが、コレが全てではありません。 時間制限を設けたり、進行速度が変化するアイテムを配置するのも良いでしょう。 いろいろと手を加える等改良を繰り返し、より一層面白さを引き立てるゲームにしてみください。 今回完成したスクリプトはコチラからどうぞ。