HSP-TECH 第8弾

2D Field Walk Script Ver.1.2

2001.3.26 HSP-TECH Written & Programed by Smith
Written Fix 2001.4.1/4.24

前回は、Playerがフィールドを歩き回るところまで可能となりましたが、これではまださみしいですね。
そこで今回は、モンスターを出現させうろちょろと歩いてもらおうというものです。
なお、マップチップ及びキャラクター画像は、「RPGツクール2000対応素材集」より お借りしてます。 キャラクタ画像(Player)は、ちょこさんの「けまのほーむぺーじ」から お借りしてます。
 この場を借りてあらためてお礼を申し上げたいと思います。
 今回から、HSP2.6β6以上でないと動作しません。注意してください。
 (* HSP2.5で動作させるには、gmode命令でgmode 4となっている箇所をgmode 2やgmode 1としてください。)

製作に入る前にどういった流れになればよいか、概要だけでもまとめてみることにします。


     ・定数の定義
     ・マップをロードする
     ・初期化処理(Playerの初期化、モンスターの初期化、画面の初期化)
     ・メイン処理
       ・キー入力処理
       ・Playerの処理(移動処理、スクロール処理、描画処理)
       ・モンスターの処理(行動決定、移動処理、描画処理)
     ・終了処理
大きくまとめるとプログラムの流れはこれで行けそうです。
このように製作に入る前に目的の動作を実現さるための「プログラムの流れを整理する」ことは大変重要なのです。

それでは流れを把握したうえで早速1stepずつ解説していきます。
定数定義とマップロードについてはすでに解説済みなので、モンスターを出現させるための前準備からいきましょう。
前準備としては、モンスター用の定数定義、画像読み込み、モンスター情報の配列変数確保、モンスターの初期配置 となります。定数定義と画像読み込み、変数確保を一気に作ってしまいましょう。

#define MON_MAX 15		;モンスターの最大出現数
#define MON_SHU 8		;モンスターの種類数

	;/////	モンスター画像をバッファへ読み込む	/////
	buffer MON_CHIP_PLANE,,,C_MODE:picload "monster.bmp"

	;<<<<<<<<<<	MonsterCharacterのイニシャライズ	>>>>>>>>>>
	;-----	Monster情報の構造体	-----
	dim mon_data,MON_MAX,64
		i=0
		dup tx,mon_data.i.0
		dup ty,mon_data.i.1
		dup tph,mon_data.i.2
		dup tpa,mon_data.i.3
		dup tpc,mon_data.i.4
		dup tmf,mon_data.i.5
		dup tmc,mon_data.i.6
		dup txv,mon_data.i.7
		dup tyv,mon_data.i.8
		dup tps,mon_data.i.9
		dup tf,mon_data.i.10
	;-----				-----
	; fix 2001.4.24
Player情報でも採用した構造体チックな手法をここでも同様に使います。あとはもはや説明するまでもないですね?

次は、モンスターをフィールドに配置してみることにします。
配置する前にルールを決めなければなりません。なぜなら溶岩の中や岩の中にモンスターが出現してしまっては、 そのモンスターは身動きがとれなくなります。そう、今回出現するモンスターはすべて歩行タイプであり、Playerと 同様のルールに沿って行動できるのです。


  1)配置場所は、MAP_SIZE_XとMAP_SIZE_Yの範囲、つまりマップ内である
  2)配置場所は、0=通過可能のフィールドである
  3)1と2の条件を満たしつつその範囲内でランダム配置を行う
  4)ルールに適さない場合1モンスターあたり10回までランダム配置を試みる

以上の条件通りに配置できるようScriptを組んでみます。


	randomize;乱数の初期化
	;<<<<<<<<<<	Monsterをランダム配置する	>>>>>>>>>>
	repeat MON_MAX
		;/////	マップ属性=0ならそこに配置	/////
		flg=0
		repeat
			await:stick ky:if ky==128:goto *owari
			rnd x,MAP_SIZE_X:rnd y,MAP_SIZE_Y	;配置場所を乱数で
			if map.x.y==0:break	;MAP属性が0なら配置場所決定
			if cnt>9:flg=1:break	;10回tryしても確定しなければ断念
		loop
		if flg:continue
		;/////	モンスター初期値決定	/////
		tx.cnt=x:ty.cnt=y:tph.cnt=2:tpa.cnt=1:tf.cnt=1
		rnd tps.cnt,MON_SHU
	loop

次にモンスターの行動アルゴリズムを考えてみましょう。
今回組むアルゴリズムは至ってシンプルなもので、Playerに関係なく現在地からランダムで4方向へ 進むというものです。もう少し複雑なよりそれっぽいアルゴリズムにしても良いのですが、ARPGの面白味 の一つであるモンスターの行動は、みなさんの手(アイデア)によって創り出されるほうが良いでしょう。
それでも、どうすればそれっぽい行動をモンスターにさせることができるかわからない、というのであれば、 「鷹月ぐみな氏CreationCollege」に あるプログラミング製作講座:戦術SLG製作をお読み下さい。

では、アルゴリズムの概要をまとめてみます。


  1)出現中のモンスターのみ行動が許される
  2)行動中ではないモンスターに対し行動を指示する
  3)一定確率に当てはまったときのみ行動を指示する
  4)移動方向は、ランダムで4方向とする
  5)移動方向が1>通過不可能の場合今回の行動はあきらめる
  6)移動方向が0=通過可能ならばその方向に指示する


*MON_THINKING
	repeat MON_MAX
		if tf.cnt==0:continue	;///	出現フラグ=0なら次のループへ
		if tmf.cnt:continue	;///	移動中なら次のループへ
		rnd r1,1024:if r1>32:continue	;///	確率が外れなら次のループへ

		;/////	移動方向をランダムで仮決定する
		rnd r1,4:txv.cnt=hox.r1:tyv.cnt=hoy.r1
		tmpx=tx.cnt+txv.cnt:tmpy=ty.cnt+tyv.cnt

		;/////	移動したい方向がマップ範囲外なら次のループへ
		if tmpx>(MAP_SIZE_X-1):continue
		if tmpy>(MAP_SIZE_Y-1):continue
		if tmpx<0:continue
		if tmpy<0:continue

		;/////	移動したい方向が障害物なら次のループへ
		if map.tmpx.tmpy>0:continue

		;/////	問題なければその方向で決定
		tph.cnt=r1:tmf.cnt=1:tmc.cnt=0:tpa.cnt=0:tpc.cnt=0
	loop
return
この段階でむつかしく考える必要なありません。上記でアルゴリズムをまとめた通りにプログラムすれば 良いわけですね。ポイントとしては、移動指示の場合、変数tmfに1を入れ、その移動方向は変数txvとtyvに それぞれ-1〜1までが入っているわけです。ん?0〜3までの乱数を発生させ、hox.r1という配列変数をtxvなどに代入している? そうです、実はあらかじめ方向に対応した増分値を配列変数で設定しておくことでアルゴリズムを少し簡単にしていたのです。
以下をご覧ください。

*ETC_INIT
	;<<<<<<<<<	その他のイニシャライズ	>>>>>>>>>>
	dim hox,4:dim hoy,4
	hox.0=0:hoy.0=-1
	hox.1=1:hoy.1=0
	hox.2=0:hoy.2=1
	hox.3=-1:hoy.3=0
このように、上方向:x増分=0/y増分=-1、右方向:x増分=1/y増分=0・・・としておくことで指示された進行方向に 進むための値をすぐに引っ張り出すことが出来るのです。また、hox.0〜hox.3に設定された方向はキャラクタ画像と も対になっているところも注目してください。これにより、乱数→移動方向→表示すべきアニメーションパターンと いう関係を簡単に作り上げることができるのです。

次にモンスターの移動処理ですが、tmfという変数に1が入っているモンスターにのみ移動処理を行っており、 Playerキャラと同様に16stepで1フィールドを歩く計算になってます。

*MON_MOVE
	repeat MON_MAX
		if tmf.cnt==0:continue
		tmc.cnt++:if tmc.cnt>15:tx.cnt+=txv.cnt:ty.cnt+=tyv.cnt:tmf.cnt=0
	loop
return

次にモンスターの描画処理ですが、ポイントとしては「Playerキャラから見える範囲のモンスターを描画 すれば良い」です。それを知るためにPlayerキャラのxy座標との比較を絶対値として算出してます。
ここで扱う座標には、マップでいう座標と画面上でいう座標の二つのスケールがありますので、混同しないように 気を付けて下さい。基本的に私のプログラムではマップのスケールで考え、描画時に画面スケールへ変換してます。

*MON_DRAW
	;<<<<<<<<<<	Monsterキャラの描画処理	>>>>>>>>>>
	repeat MON_MAX
		if tf.cnt==0:continue	;出現フラグのチェック

		;/////	MAP系xy座標の絶対値
		tmpx=tx.cnt-mx:x=tmpx:if tmpx<0:tmpx=-tmpx
		tmpy=ty.cnt-my:y=tmpy:if tmpy<0:tmpy=-tmpy

		;/////	見えない範囲は描画しない
		if tmpx>9:continue
		if tmpy>9:continue

		;/////	描画系xy座標の算出
		tmpx=x*32+P_CHR_DRAWX:tmpy=y*32+P_CHR_DRAWY

		;/////	画面がスクロール中なら補正
		if mf:tmpx-=xv*2:tmpy-=yv*2

		;/////	移動途中の算出
		if tmf.cnt:tmpx+=tmc.cnt*txv.cnt*2:tmpy+=tmc.cnt*tyv.cnt*2

		;/////	アニメーション計算
		tpc.cnt++:if tpc.cnt>5:tpc.cnt=0:tpa.cnt++
		if tpa.cnt>2:tpa.cnt=0

		;/////	画面へ描画
		pos tmpx,tmpy:color 32,156,0:gmode 4,,,256
		tmpx=tpa.cnt*CHR_SIZE_X:tmpy=tps.cnt*4*CHR_SIZE_Y:tmpy+=tph.cnt*CHR_SIZE_Y
		gcopy MON_CHIP_PLANE,tmpx,tmpy,CHR_SIZE_X,CHR_SIZE_Y
	loop
return

 で、これらのモンスター処理は次のようにターンをまとめることにします。

*MON_TURN
	gosub *MON_THINKING
	gosub *MON_MOVE
	gosub *MON_DRAW
return

次に、前回ではPlayerの移動(スクロール処理)をサブルーチン内で完結するように組みましたが、 これをメインから毎ターン呼び出しモンスターと平等なタイミングで処理されるようにします。
分かりやすいようにサブルーチン名を変更しました。

*MY_MOVE
	;/////	進行方向からキャラクタアニメーションレーン番号を算出
	if xvv=1:ph=1
	if xvv=-1:ph=3
	if yvv=1:ph=2
	if yvv=-1:ph=0

	;/////	進行方向が通過可能か調べる
	tmpx=mx+xvv:tmpy=my+yvv
	;/////	マップ範囲外は移動不可
	if tmpx>(MAP_SIZE_X-1):mf=0:return
	if tmpy>(MAP_SIZE_Y-1):mf=0:return
	if tmpx<0:mf=0:return
	if tmpy<0:mf=0:return
	if map.tmpx.tmpy>0:mf=0:return	;障害物ありなら不可

	;/////	1ステップ分のスクロールを行う	/////
	kyy=ky&15	;カーソルキーが押され続けているか
	mc++:xv+=xvv*mp:yv+=yvv*mp	;1step分の計算
	pc++:if pc>5:pc=0:pa++		;キャラアニメーションさせる為の計算
	if pa>2:pa=0			;キャラアニメーションは繰り返し
	if mc>15:if kyy==0:pa=1		;16step終了時にキーが押されてなければ停止アニメーション

	;/////	16stepなら移動後処理(mx,myに進行方向分を足す)
	if mc>15:mx+=xvv:my+=yvv:xv=0:yv=0:mf=0:mc=0:pc=0
return

次にPlayer処理もターンとしてまとめることにします。

*MY_TURN
	;<<<<<<<<<<	Player処理	>>>>>>>>>>
	if mf:gosub *MY_MOVE	;移動要求があったら(mf=0:停止中/mf=1:移動中)
	gosub *MAP_DRAW		;MAPを毎回描画
	gosub *MY_CHR_DRAW	;Playerを毎回描画
return

最後にメイン処理を次のように変えます。これにより、前回までと大きく違うモノがあります。 それは、前回ではPlayerが移動したときのみMAPの再描画をしてましたが、今回からは毎回全てを 再描画することになります。一見、極端に処理に負荷がかかり効率が悪く思われそうですが、Player が動こうがモンスターが動こうが関係なしに毎回描画を行うことで、ゲームスピードが安定していきます。
ちなみにDirectDraw(DIrectX)では毎回描画が当たり前です。ん?なにか感づかれましたか?

*MAIN	;<<<<<<<<<<	メインループ	>>>>>>>>>>
	await WAIT_TIME	;waitをとる
	gosub *KEY_IN	;キー入力処理へ

	;/////	描画OFF	/////
	redraw 2

	gosub *MY_TURN		;Player処理
	gosub *MON_TURN		;モンスター処理

	;/////	描画ON(一気にメイン画面へ描かれる)
	redraw 1,MAP_DRAW_X,MAP_DRAW_Y,MAP_DRAW_AREA_SIZE,MAP_DRAW_AREA_SIZE
goto *MAIN

今回、Scriptは以下の構成で分割されてます。これは拡張を考慮してのことです。

  ・メインScript --- main325.as
  ・モンスターScript --- monster.as
  ・モンスター初期化Script --- mon_init.as
  ・マップローダー --- map_load.as

さあ、これでかなりそれっぽくなってきました。次回は、いよいよここまでのプログラムをベースに クライアント/サーバ型による通信の基礎を構築してみたいと思います。

なお、今回のHSP-TECH第8弾でご説明した2D Filed Walk Scriptのソーススクリプト全部とリソース画像をアーカイブで用意しておきました。 ただし、リソース画像に関しては必ずReadmeをお読み下さい。 リソース画像は素材として配布する訳ではありません、あくまでもこのHSP-TECHで解説した教本の一部、教材としてお使い下さい。

▽ここまでのスクリプトとサンプルデータのセットをDLする(69kb)▽(4.24non-fix)

HSP-TECH第9弾へ

INDEXへ戻る