ヘビゲーム(改善)


今回は上図の通り、外壁を含んだ障害物の設置、ステージ及びキャラクタの情報表示、
見た目ではわかりませんが、キー反応感度の上昇等の機能改善を行います。


まずはキー反応感度を良くする部分から変えて行きます。
1
2
3
4
5
6
7
8
9
10
11
12
13
*main
	repeat 10, 1
		stick key
		if key & 5 : await (10 - 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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
*move_char
	if posx = itemx & posy = itemy {
		dir(len) = dir(len - 1)
		posx(len) = posx(len - 1)
		posy(len) = posy(len - 1)
		len++
		item = 0
	}
	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
	// stick key, 5
	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)
	return
前回まで「await 100」で、(約)1秒間のウェイトを設けてキーチェックは1回。 今回は1回分のウェイトを10分の1に変更してソレを10回繰り返し、キーチェックも10回行います。 キーが押されたら、次のターンまでキーの確認はする必要がないのでループを抜けるわけですが、 そのままブレイクするだけだと、ループの1回目と10回目とで、 1ターンのウェイト時間が顕著に現れるので、ソレを防ぐために1ターンのウェイトを合わせているのです。 コレでキーの感度は改善することと思います。 尚、メインループ内でウェイトと共にキーチェックを行うようにしましたので、 *move_charラベル内で行っていたキーチェックは削除してください。 続いて、障害物を配置する為、初期化を行う*initラベル内を書き換えます。
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
*init
	size = 20									// マスサイズ縮小
	map = ginfo_winx / 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  = 5
	item = 0
	dim dir, len
	dim posx, len
	dim posy, len
	repeat len
		dir.cnt = 4
		posx.cnt = 6 - cnt
		posy.cnt = 2
	loop
	return
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
*draw_tips
	buffer 1
	// とりあえず全マス通行可能な下地で塗りつぶす
	color 255, 200, 200
	boxf , , map * size, map.1 * size
	// 個別に通行不可能マスを塗りなおしていく
	color 100
	repeat map * map.1
		if tip.cnt = 1 {
			i = cnt \ map * size, cnt / map * size
			boxf i.0, i.1, i.0 + size - 1, i.1 + size - 1
		}
	loop
	gsel 0
	return
 1
 2
 3
*draw_back
	pos 0, 0 : gcopy 1, , , map * size, map.1 * size // マップ画像をコピーするだけ
	return
コレは後でも良いのですが、外壁・内壁分の移動範囲が狭まるということで、 まずは2行目の1マス分のサイズを40から20へと縮めます。コレで大分動きやすいスペースができましたね。 次に各マスが通行できる(=0)か否(1)かを保持する変数を用意して、外壁及び内壁に1をセットします。 上記の通り、0は通行可能な下地を、1は通行不可能である壁をそれぞれ描画するわけですが、 通行不可能な壁が動いたりしない限り、一度書いたマップをターン毎に全マス描画し直すのはムダですね。 そこでまず、*initラベルの通行不可能マスが決定した段階で仮想バッファに描画しておきます。 上記では描画処理を分離するため、14行目で全マス描画を行う処理へとサブルーチンジャンプさせてます。 そして、マップを描画していた*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
24
25
26
27
28
29
30
31
32
33
34
35
36
37
*hantei
	if tip(posy * map + posx) = 1 {
		i = -1
	} else {
		repeat len - 1
			i = cnt
			repeat len - i - 1, i + 1
				if posx.i = posx.cnt & posy.i = posy.cnt {
					i = -2
					break
				}
			loop
			if i = -2 : break
		loop
	}
	if i < 0 {
		life--
		if i = -1 : s = "壁" : else : s = "自分"
		if life {
			dialog "長さ" + len + "で" + s + "に激突!", 1, "残り" + life + "回で終了です"
			gosub *init
		} else {
			dialog "長さ" + len + "で" + s + "に激突!\nやり直しますか?", 2, "ゲームオーバー"
			if stat = 6 : gosub *start : else : end
		}
	}
	if len >= mission.stage {
		stage++
		if stage >= length(mission) {
			dialog "長さ" + mission(stage - 1) + "達成おめでとう", , "ゲームクリア!"
			end
		} else {
			dialog "長さ" + mission(stage - 1) + "達成です", , "ステージ" + stage + "クリア"
			gosub *init
		}
	}
	return
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
*draw_item
	if item = 0 {
		repeat 1
			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
判定処理*hanteiラベル内で変える箇所は2行目だけです。 ウィンドウ外に出るとぶつかるという処理から、先頭部分が通行不可能な壁にぶつかるという処理に変更。 アイテムが壁上に配置されないように変更するのは、*draw_itemラベル内の3、6、7行目です。 ランダムな位置にアイテムを配置しようと試みた後に、壁(通行不可能マス)かをチェックして、 もしも、通行不可能なマスであれば再度別の位置を探します。 コレらの変更で、通行不可能なマスが通行不可能な処理として正しく動作するようになりましたね。 続いての改善点は、ステージクリア条件を達成したら、その場で終了させるのではなく、 完成図のように外壁上部を開けて、ソレを通過させることでクリアとなるように変更しましょう。
 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
*hantei
	// ゴールを通過してないなら衝突チェック
	if len >= 0 {
		if tip(posy * map + posx) = 1 {
			i = -1
		} else {
			repeat len - (len ! 0)
				i = cnt
				repeat len - i - 1, i + 1
					if posx.i = posx.cnt & posy.i = posy.cnt {
						i = -2
						break
					}
				loop
				if i = -2 : break
			loop
		}
		if i < 0 {
			life--
			if i = -1 : s = "壁" : else : s = "自分"
			if life {
				dialog "長さ" + len + "で" + s + "に激突!", 1, "残り" + life + "回で終了です"
				gosub *init
			} else {
				dialog "長さ" + len + "で" + s + "に激突!\nやり直しますか?", 2, "ゲームオーバー"
				if stat = 6 : gosub *start : else : end
			}
		}
		// まだゴールに穴を開けてないなら開ける
		if len >= mission.stage & clear = 0 {
			clear = 1
			tip(map / 2 - 1) = 2
			tip(map / 2 - 0) = 2
			gosub *draw_tips
		}
	} else {
		stage++
		if stage >= length(mission) {
			dialog "長さ" + mission(stage - 1) + "達成おめでとう", , "ゲームクリア!"
			end
		} else {
			dialog "長さ" + mission(stage - 1) + "達成です", , "ステージ" + stage + "クリア"
			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 / 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 map.1 * 2
		tip(cnt \ map.1 * map + cnt / map.1 * (map - 1)) = 1
	loop
	gosub *draw_tips
	len  = 5
	item = 0
	dim dir, len
	dim posx, len
	dim posy, len
	repeat len
		dir.cnt = 4
		posx.cnt = 6 - cnt
		posy.cnt = 6
	loop
	return
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
*move_char
	if posx = itemx & posy = itemy {
		dir(len) = dir(len - 1)
		posx(len) = posx(len - 1)
		posy(len) = posy(len - 1)
		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-- // 1ターン毎に短くしていく(クリアゲートを通っているように見える)
	}
	return
元々壁であるマスを通行可能マスにするので、配列変数tipを0にする方法でも問題ないのですが、 0は「通行可能マス」1は「壁マス」に加えて、2は「ゴールマス(クリアゲート)」として使います。 まずは、*hanteiラベル内30〜34行目を追加し、クリア条件に達したら壁に穴を開けます。 ターン毎にクリアゲートを再描画するのはムダなので、変数clearを開けた事を示すフラグにより制御しますが、 このclearフラグをクリア後に初期化し忘れてしまうと、次回からはクリア条件に達しても穴は開けられず、 延々とマップ内を回るハメになりますので注意してください。 さて、クリアゲートを通過する際、単純に座標を変えていくだけでは先頭部のY座標はマイナスとなるので、 範囲外エラーとなってしまう配列要素がマイナスとなる部分の衝突判定をするワケにも行かず、 マイナスの場合はチェックしない等のトラップ処理を入れる必要があります。 クリアゲートを通過した部分は座標を変更しない方法にすることで、徐々に縮めるように演出できますが、 コレの場合もターンを増す毎に先頭部と同じ座標が増えていくことになるので、 *hanteiラベル内の自分自身との衝突チェックで「同じマス=ぶつかった」となってしまい、 ソレを回避するためにクリアゲートを通過してない時だけ衝突チェックを行うようにする必要があります。 他にシンプルでわかりやすい別のやり方があるかもしれませんが、 当講座ではクリアゲートを通過した時、キャラの長さを縮めることで、あたかも通過している様に演出してます。 このやり方なら、座標がマイナスになることも、自分自身と衝突してしまう問題も避けられますね。 *hanteiラベル内で自キャラの体長が0になる(=完全に通過する)まで初期の判定を行うので、 「repeat len - 1」のままでは、体長が0になったときにおかしくなります。 そこで7行目のように「repeat len - (len ! 0)」に置き換えることで、 体長が1以上ある時は今まで通り、0になった時はループさせないように処理を制御することが出来ます。 最後に、ウィンドウ右側にステージ及びキャラクタの情報表示エリアを設定しましょう。
 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
*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 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  = 5
	item = 0
	dim dir, len
	dim posx, len
	dim posy, len
	repeat len
		dir.cnt = 4
		posx.cnt = 6 - cnt
		posy.cnt = 6
	loop
	return
 1
 2
 3
 4
 5
 6
 7
 8
 9
*start
	randomize
	stage = 0
	life = 3
	mission = 30, 40, 60
	infosize = 200		// 情報表示エリア幅を定義
	font msmincho, 32, 1	// 情報表示文字のサイズを設定
	gosub *init
	return
 1
 2
 3
 4
 5
 6
 7
 8
*draw_back
	pos 0, 0 : gcopy 1, , , map * size, map.1 * size
	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)
	return
設定するには*initラベル内4行目のマップサイズを書き換えます。 例えばエリア幅200ピクセルを定数値のまま使用すると複数個所に設定する際にやり辛いので、 ゲーム開始後一度しか通らない*startラベル内で変数infosize(=200)を定義しましょう。 固定された数なので#define#constで書いてしまっても問題ありませんが、 定義する*startラベルよりスクリプト上部に定数が書かれることのないようにしてください。 コレで表示エリアの準備が整ったので、後は背景を描画する*draw_backラベル内を書き換えます。 サンプルのようにマップやキャラの情報を適当に表示してみてください。 今回の改善で、ほぼゲームとして完成したも同然ですね。 今回で使われたスクリプトはコチラからどうぞ。 次回は少しだけ味付けをして幕を閉じたいと思います。