アクションゲーム(後半)

2章立てにしてみた横スクロールアクション講座の後半です。

それでは16章をスタートしましょう。

15章では「背景の描画」「自キャラの描画」「背景のスクロール」「自キャラのアクション」を解説しました。

残りの基本的な部分というのは、「敵の描画とアクション」「当たり判定」

「ステージクリア・ゲームオーバー」「アイテム」くらいですね。

まず背景の当たり判定、一番下に落ちて死んだ時、ステージをクリアした時の処理から入ります。

今回は1ステージのみでなくクリアすると次のステージに進むような処理も入っています。

前章とは別形式のステージデータが必要です。

1ステージ1ファイルとしてファイル名を「action_mapX.txt」(Xがステージ番号)として読み込みましょう。

サンプルで使用しているデータは次の通りです。

操作キャラ画像敵キャラ画像ステージ1データステージ2データステージ3データ

#define STAGEMAX 3 ; 全ステージ数
#define WX 640
#define WY 480
#define TIP 32
#define TIPX 20
#define TIPY 15
#define MOVEMENT 4
#define HEGIHT 8
#define JUMPMAX 15
#define MYW 16 ; 自キャラの横幅
#define MYH 31 ; 自キャラの高さ(ホントは32だが0からはじまるので)
	stage=1 ; 初めに表示するステージ

*preparation ; 準備
	sdim map,3200
	if stage>STAGEMAX : goto *ending
	bload "action_map"+stage+".txt",map
	strmid startx,map,0,2 : int startx
	strmid starty,map,2,2 : int starty
	notesel map
	notegetnoteget map,1
	strlen mapx,map
	mapx=mapx/TIPY
	buffer 2
	picload "character.bmp"
	buffer 3
	picload "background.bmp"
	buffer 4,TIPX+1*TIP,TIPY*TIP
	buffer 5
	picload "enemy.bmp" ; 後に使用する敵画像
	gsel 0
	gmode 2
	ichi=0
	ichi.1=1
	x=2*TIP : y=13*TIP
	direction=1
	leg=1
	finish=0 ; 終了フラグ

*main
	redraw 0
	gosub *draw_back
	gosub *draw_char
	if finish : goto *preparation
	gosub *hitcheck
	gosub *endcheck
	redraw
	wait 1
	goto *main

*draw_back
	if ichi/TIP!=ichi.1 {
		ichi.1=ichi/TIP
		gsel 4
		count=0
		dim mapinfo,TIPX+2*TIPY
		repeat TIPX+1*TIPY,ichi/TIP*TIPY
			peek TIPid,map,cnt
			if TIPid=0 : break
			int TIPid : TIPid-=48
			mapinfo.count=TIPid
			pos count/TIPY*TIP,count\TIPY*TIP : gcopy 3,TIPid\5*TIP,TIPid/5*TIP,TIP,TIP
			count+
		loop
		gsel 0
	}
	pos 0,0 : gcopy 4,ichi\TIP,0,WX,WY
	return

*draw_char
	if k&5 : leg+ : if leg>2 : leg=0
	pos x,y : gcopy 2,leg*32,direction*64,32,32
	return

*hitcheck ; 当たり判定
	stick k,21,1
	if k&16 : jump=1
	if JUMPMAX>jump.1&jump { ; 上昇中の判定
		info1=TIP-MYW/2+(ichi\TIP)+x/TIP*TIPY+(y-HEGIHT/TIP) ; 自キャラの左上端
		info2=TIP-MYW/2+(ichi\TIP)+MYW+x/TIP*TIPY+(y-HEGIHT/TIP) ; 自キャラの右上端
		if mapinfo.info1<5&(mapinfo.info2<5) : y-=HEGIHT : else : jump.1=JUMPMAX
		jump.1+
	} else { ; 落下時の判定
		info1=TIP-MYW/2+(ichi\TIP)+x/TIP*TIPY+(y+MYH+HEGIHT/TIP) ; 自キャラの左下端
		info2=TIP-MYW/2+(ichi\TIP)+MYW+x/TIP*TIPY+(y+MYH+HEGIHT/TIP) ; 自キャラの右下端
		if mapinfo.info1<5&(mapinfo.info2<5) : y+=HEGIHT : else : jump=0 : jump.1=0
	}
	if k&1 { ; 左移動時の判定
		info1=TIP-MYW/2+(ichi\TIP)-MOVEMENT+x/TIP*TIPY+(y/TIP) ; 自キャラの左上端
		info2=TIP-MYW/2+(ichi\TIP)-MOVEMENT+x/TIP*TIPY+(y+MYH/TIP) ; 自キャラの左下端
		if x>0&(mapinfo.info1<5)&(mapinfo.info2<5) {
			if x/TIP=9&(ichi/TIP>0) : ichi-=MOVEMENT : else : x-=MOVEMENT
		}
		direction=0
	}
	if k&4 { ; 右移動時の判定
		info1=TIP-MYW/2+(ichi\TIP)+MYW+MOVEMENT+x/TIP*TIPY+(y/TIP) ; 自キャラの右上端
		info2=TIP-MYW/2+(ichi\TIP)+MYW+MOVEMENT+x/TIP*TIPY+(y+MYH/TIP) ; 自キャラの右下端
		if mapinfo.info1<5&(mapinfo.info2<5) {
			if x/TIP=9&(ichi/TIP+TIPX<mapx) : ichi+=MOVEMENT : else : x+=MOVEMENT
		}
		direction=1
	}
	return

*endcheck ; 終了判定
	redraw
	if x>=WX : dialog"clear" : stage+ : finish=1 : return
	if y>=(WY-TIP) { ; 画面下端を超えた時の処理
		repeat
			redraw 0
			gosub *draw_back
			gosub *draw_char
			redraw
			if y>WY : break
			y+=HEGIHT
			wait 1
		loop
		finish=-1
	}
	if finish=-1 : dialog"gameover"
	return

*ending ; 全ステージクリア
	dialog"Thank you for playing."

前回のcontrollラベルはhitcheckラベルに変更しています。

当たり判定の式がややこしいですね(^^;

info1info2というのは何箇所も使いまわしていますが、自キャラの端と端座標の取得に利用しています。

自キャラの上下に移動するような時はキャラの左端と右端の上下、

キャラが左右に移動する時はキャラの上端と下端を知る必要があります。

なぜかというと、例えばジャンプした時に天井の当たり判定を入れるわけですが、

自キャラの一箇所しか判定しない場合、その座標が通行可である限り他の部分が移動し続けられますよね。

中央一箇所だけでもいけそうな気がしますが、体半分ほどずらせばブロックがあってもすり抜けてしまいます。

両端共通行可の時にだけ移動できるようにすれば矛盾は起こりません。

前章ではジャンプした分だけ下降すればよかったですが、穴はジャンプしなくても下降しなければなりません。

ですので上昇と下降は別処理として、ジャンプ中は上昇判定、

ジャンプ中以外は下降判定(横移動時は横の判定も)だけをします。

自キャラ位置のチップ番号を求める式は「X座標÷チップサイズ×Y方向の数+(Y座標×チップサイズ)」でした。

落下して画面下端まで行く時ですが、キャラの上端が画面下端まで落ちるとチップ番号が変わってしまいます。

もう少しわかりやすく言うと、自キャラの左上端が自キャラ位置を表していて、画面Yサイズが480です。

キャラが落ちるとキャラの頭の上端が画面外に出て行かないと不恰好なので

キャラYサイズを480以上まで増やして下に進めます。

そうするとチップ番号が、自分より一歩前の列の一番上端に変わってしまいます。

そこが通行可なチップなら問題ないですが通行不可だと、自分の下のマスが通行不可(地面)であると誤認して

画面外に出る前に止まってしまいます。

そうなるのを防ぐために自キャラY座標が480になったら下部当たり判定をする前に判定をなくして

通常の落下とは別に強制的にY座標を進めてやればいいわけです。

サンプルではその処理をendcheckラベルでしています。

別の処理に話を変えます。

サンプルの初期位置は決められていますね。XはまだいいとしてY位置が決められていると、

あの位置より高い所に地面があると上下左右通行不可チップなので身動きが取れなくなってしまいます。

それを防ぐには2つの方法が思いつきます(他にもあると思いますが...)。

1つは、初期位置情報を変数に入れておく方法。

こうすると好きな高さから始められます。ただその情報もステージデータに入れておく必要が出てきます。

もう1つは自キャラの下マスが通行可だと落下するのを利用して、初期Y座標を画面上端にしてしまう方法です。

こうすると自然に地面まで落下するのでどんな高さでも大丈夫です。

ただ、スタート時に落ちていくのが見えて不恰好ですし

、規定位置までに天井(通行不可チップ)があると通行可のマップまで落下しません。

上記サンプルでは1つめの方法を取り入れようとしているところです(まだ完全には取り入れていません)。

preparationラベルの始めにstartxstartyという変数が新たに追加されて、

マップ情報はファイル2行目(0からだと数えて1行目)に変更されています。

後は「x=2*TIP : y=13*TIP」を「x=startx*TIP : y=starty*TIP」に変えればOKです。

他の修正として、「落下中はジャンプできないようにする」とかですかね…。

ジャンプせずに穴に落ちる時ジャンプボタンを押すと落下回避できてしまいます。

コレの修正も非常に簡単で、落下判定処理行の「if mapinfo.info1<5&(mapinfo.info2<5) : y+=HEGIHT」を、

if mapinfo.info1<5&(mapinfo.info2<5) : y+=HEGIHT : jump=1 : jump.1=JUMPMAX

とすればよいですね(elseはそのままです)。

さぁそれでは敵を配置してみましょう。

パラメータが多いのでファイルから読み込むのではなく元々配列変数に入れてみましょうか…。

preparationラベルに下記パラメータを挿入してください。

	dim enemysp,2 ; それぞれの敵の移動速度
	dim eneMYHp,2 ; それぞれの敵の初期耐久力
	dim enmid,ENMMAX ; 配置する敵番号
	dim enmapp,ENMMAX ; 出現ステージ
	dim enmx,ENMMAX ; 配置するチップのX方向番号
	dim enmy,ENMMAX ; 配置するチップのY方向番号
	dim enmhp,ENMMAX ; 残り耐久力
	dim enmleg,ENMMAX ; どちらの足が前に出ているか
	dim enmdirect,ENMMAX ; 敵の進行方向
	dim enmjump,ENMMAX ; 敵がジャンプ・落下中か
	enemysp=2,3
	eneMYHp=1,2
	enmid=0,0,0,0,0,0,0,0,0,0,1,1,1,0,1
	enmapp=1,1,1,1,2,2,2,2,3,3,3,3,3,3,3
	enmdirect=0,0,0,1,0,1,1,0,1,1,0,0,0,0
	enmx=11 * TIP, 24 * TIP, 28 * TIP, 32 * TIP, 8 * TIP, 14 * TIP, 23 * TIP, 35 * TIP, 0, 16 * TIP, 19 * TIP, 28 * TIP, 38 * TIP, 48 * TIP, 56 * TIP
	enmy=13 * TIP, 7 * TIP, 7 * TIP, 12 * TIP, 12 * TIP, 3 * TIP, 9 * TIP, 13 * TIP, 3 * TIP, 3 * TIP, 3 * TIP, 1 * TIP, 2 * TIP, 2 * TIP, 4 * TIP
	repeat ENMMAX
		tmp=enmid.cnt
		enmhp.cnt=eneMYHp.tmp ; それぞれの耐久力を設定
	loop

ENMMAXは敵処理を拡張しやすいように敵数を定数にしたものです。

#define ENMMAX 15 ; 全敵キャラ数

サンプルでは全15体使用していますのでENMMAXを使用している行よりも前に上記のように定義してください。

そして敵の行動や自キャラ・壁との当たり判定をする下記のenemyラベルを追加してください。

*enemy ; 敵の行動と当たり判定
	repeat ENMMAX
		if enmhp.cnt=0 : continue ; 死んでいるなら描画しない
		if enmapp.cnt!=stage : continue ; 出現ステージではないのなら飛ばす
		if ichi-TIP<enmx.cnt&(ichi+WX>enmx.cnt) { ; 画面内に敵の配置位置が入っているか
			if enmy.cnt+TIP+enemysp.tmp>=WY {
				enmy.cnt+=enemysp.tmp
				if enmy.cnt>WY : enmhp.cnt=0 : continue : else : continue ; 穴に落ちると死ぬ
			}
			info1=ichi\TIP+enmx.cnt+enemysp.tmp-ichi/TIP*TIPY+(enmy.cnt+TIP-1+enemysp.tmp/TIP) ; 敵キャラの左下端
			info2=ichi\TIP+TIP-1+enmx.cnt-ichi/TIP*TIPY+(enmy.cnt+TIP-1+enemysp.tmp/TIP) ; 敵キャラの右下端
			if mapinfo.info1<5&(mapinfo.info2<5) { ; 次の進むべきマスの地面がない
				enmjump=1
			} else {
				enmjump=0
			}
			if enmjump { ; 宙に浮いている
				enmy.cnt+=enemysp.tmp
			} else { ; 地面についている
				enmleg.cnt+
				if enmdirect.cnt { ; 進行方向が右の場合
					info1 = ichi \ TIP + TIP - 1 + enmx.cnt - ichi
					info1 = info1 / TIP * TIPY + (enmy.cnt + enemysp.tmp / TIP) ; 敵キャラ右上
					info2 = ichi \ TIP + TIP - 1 + enmx.cnt - ichi
					info2 = info2 / TIP * TIPY + (enmy.cnt + TIP - 1 / TIP) ; 敵キャラ右下
					if mapinfo.info1<5&(mapinfo.info2<5) {
						enmx.cnt+=enemysp.tmp
					} else {
						enmdirect.cnt=0
					}
					if mapx*TIP<enmx.cnt : enmhp.cnt=0 ; ステージ右端を越えると死んだ状態
				} else { ; 進行方向が左の場合
					info1 = ichi \ TIP + enmx.cnt - ichi
					info1 = info1 / TIP * TIPY + (enmy.cnt + enemysp.tmp / TIP) ; 敵キャラ左上
					info2 = ichi \ TIP + enmx.cnt - ichi
					info2 = info2 / TIP * TIPY + (enmy.cnt + TIP - 1 / TIP) ; 敵キャラ左下
					if mapinfo.info1<5&(mapinfo.info2<5) {
						enmx.cnt-=enemysp.tmp
					} else {
						enmdirect.cnt=1
					}
					if enmx.cnt+TIP<0 : enmhp.cnt=0 ; ステージ左端を越えると死んだ状態
				}
			}
			tmp=enmid.cnt
			pos enmx.cnt-ichi,enmy.cnt
			gcopy 5,enmleg.cnt/10\2+(enmdirect.cnt*2)*TIP,tmp*TIP,TIP,TIP
			if ichi + x < (enmx.cnt + TIP) & (TIP - MYW / 2 + ichi + x + MYW > enmx.cnt) {
				if fenmy.cnt + TIP > y & (y + MYH > enmy.cnt) {
					finish=-1 ; 接触すると死ぬ
				}
			}
		}
	loop
	return

敵の移動は0,1で左右の移動しかアクションがありませんが別のパラメータを設定して

ジャンプしたり、攻撃を仕掛けてきたり、停止したりすると少しは面白くなるかもしれません(ぉ

あ、当然mainラベルにこのenemyラベルに行ける様、他のと同じように「gosub*enemy」と足してくださいネ。

コレを入れずに実行して「敵が出ない」と思った人が最低1人はいることでしょう(笑

できるだけ「gosub *endcheck」より手前に挿入してください。

コレより後だとゲームオーバーメッセージが表示されません。

そうそう…出てくる敵に耐久値を設定していますが、

ダメージを与える手段を取り入れてませんので全て倒せません(いわゆる避けゲーというやつですね

あのサンプル3ステージをノーコンテニューでクリアできた人はなかなかですよ(笑

マ●オのように踏み潰すことで倒せたり、アイテム等を使用することで倒せるようにしてみてもよいでしょう。

そういえばアイテムも入れておりませんが、もし取り入れたい場合は…頑張って気合で乗り切ってください(爆

大体はこんな感じでOKかと思いますが、細かい所ではジャンプしてステージクリアした時の処理や

ジャンプ中に左右キーを押しても足をバタつかせないようにする、などの処理が必要になってくると思います。

その辺の修正、得点・自キャラ残数と言った追加等は各自で改良してみてください。

現時点で発覚しているバグがありますが各自で直すなりしてください^^;

どういうバグか言っておくと、自キャラ移動時に敵キャラが地面に落下するとめり込む、というものです。

一応スクリプトはコチラから。