17章 3Dダンジョン(前半)

この章では下記のような今流行(?)の擬似3Dダンジョンを作成しちゃおうという講座です。

迷路ゲームとは「今どこにいるか・どの方角を見ているかわからない」という焦燥感をゲーム化したものです。

おもしろそう、難しそう…人それぞれいろいろ思うでしょうね。

この講座を作るに当たって実際に試してみた感想を書きましょう。

私の場合、何も調べずに作成しましたので描画ルーチンに時間が掛かりました。

迷路自体は持っている知識だけですぐ(?)に出来たのですが…。

ホントこれだけで3,4日と試行錯誤し続けました。

…といっても1日の作業時間が1〜2時間程度なんですけど…。

画像のほうも比率等の問題で何度も作成しなおしましたので結構時間を割きましたし。

皆さんは同じ轍を踏まないようしっかり調べてから始めるようにしましょうね。

結局最後まで意地を張って何も見ませんでしたので経験者から見れば無駄・方法が悪い等あると思います。

講座だからコレがベストだ、ということではないことをあらかじめご了承ください。

 

まずは説明していく大まかな順序・流れを書いておきますね。

はじめに通路と壁を象ったテキスト形式の迷路データを読み込んで処理をします。

その次にメインとなる自分視点の擬似3Dを説明して、

最後に迷路の自動生成をしたいと思います。

 

ではさっそく迷路データを処理する所から入りましょう。

上の流れを見ていただいても分かるかと思いますがココではまだ3D化はされません。

一番初めにすべきことはルール決めではないでしょうか?

とりあえず0を通路、1を壁と決めておこうかと思います。

今まではまず直接データを埋め込んだスクリプトを書いて、

その後に外部ファイルとしてデータを書き出しソレを読み込ませるというものに分けていましたが、

ココでは都合上割愛して一回目から外部ファイルを読み込ませようと思います。

サンプルで使っている外部ファイルはコチラと同じものです。

ファイル1行が迷路1列分にしています。

自分の視界は何チップ分先まで見えるようにするかというのも決めなければなりません。

広くなればいろいろ面倒なことが増えますし、

全マスまたは広範囲のマスが見えてしまうと面白みも欠ける恐れがあります。

普通のものでよいのなら自キャラマスの周り8マス(計9マス)見えれば十分でしょう。

…ということでココでは3×3表示するようにします。

 

話が本線から外れてしまいますが一部だけを見せるという部分で相対的な考えが必要となります。

アクションゲームの講座でもコレがあったのですが当たり前の様に説明抜きで進めてしまいました。

っていうかコチラの方がアクションよりレベル的に低いと思われるので先に説明すべきでしたネ。

まぁ…後悔先に立たずってことで…(ぉ

アクションをまだ見てない人はコチラの絶対と相対についての知識を踏まえてから読んでください…。

ココで書いてもコレを読んでいる皆さんはもう既にアクションを終えられていることでしょう。

アクションが理解できなかった人にはチャンスです、説明が悪いですが理解してくださいね。

もう上で触れられていますが絶対・相対についてはもう完璧かな?

プログラムとは関係ないですけどパソコンを少々使っている人であれば、

ファイルの置いているフォルダを絶対・相対パスで指定する、といった言い方を聞いたことあるはず。

何が違うかと言うと、絶対の方はルートを基準とした考え方で、

相対はとある何か(カレントetc)を基準とした考えです。言い方が難しいですかね…。

習うより慣れろということでココでこれから使う絶対と相対座標について説明しましょう。

一番左上のマス(x,y)を(0,0)として自キャラを(6,10)座標に配置します。

この(6,10)というものが絶対座標です。

左上の(0,0)を基点(ルート)としていますので、例えば(8,13)であれば

左端から9つ目,上端から14であるということが分かります(当たり前ですが)。

それを中心に周り8マス分なので表示する座標は

左端から8
上端から13

(7,12)
左端から9
上端から13

(8,12)
左端から10
上端から13

(9,12)
左端から8
上端から14

(7,13)
左端から9
上端から14

(8,13)
左端から10
上端から14

(9,13)
左端から8
上端から15

(7,14)
左端から9
上端から15

(8,14)
左端から10
上端から15

(9,14)

となるわけです。

コレに対し相対座標は、同じ様に中心の座標は(8,13)とわかっていますが、

左端から上端からそれぞれ何マス目かは分からなかったとします。

普通は見れば分かるんですけどね…。

まぁ左端・上端が(0,0)じゃなくて何になっているか分からないと仮定しましょう。

それでも周りの8マスは求められますね。

中心Xから−1
中心Yから−1

(7,12)
中心Xから±0
中心Yから−1

(8,12)
中心Xから+1
中心Yから−1

(9,12)
中心Xから−1
中心Yから±0

(7,13)
コレが中心
中心X=8,中心Y=13

(8,13)
中心Xから+1
中心Yから±0

(9,13)
中心Xから−1
中心Yから+1

(7,14)
中心Xから±0
中心Yから+1

(8,14)
中心Xから+1
中心Yから+1

(9,14)

この様になるわけですね。

コレが絶対と相対の求め方となります。

それでコレは一見すると求め方の違いだけで同じ様に見えます。

…が絶対座標の方はいつ仕様が変更されて基準点が変わるかわかりません。

もし変わってしまうとこの座標を書いているところをすべて変更しなければならない、

ということが出るかもしれませんよね。

変わらないことがほとんどだと思いますが100%ではないので使用しないほうがよいでしょう。

相対のほうは仕様が変わっても影響を受けにくいです。

…ということでこの相対座標を使います。

 

ではスクリプトを書いて後から説明しますね。

	exist "maze1.dat"
	if strsize<0 : dialog "マップデータがありません" : end
	sdim map,strsize+1
	bload "maze1.dat",map
	notesel map
	ichix=6 : ichiy=10 ; 左上を(0,0)とした初期位置
	gosub *draw
	repeat
		stick key,15
		if key&15 : gosub *move
		wait 1
	loop

*move
	x=0 : y=0 ; 一時変数
	if key=1 : x-
	if key=2 : y-
	if key=4 : x+
	if key=8 : y+
	noteget tmp,ichiy+y ; 移動先y行
	strmid tmp,tmp,ichix+x,1 ; 移動先x列
	if tmp="0" : ichix+=x : ichiy+=y ; 移動先が通路なら移動させる
	gosub *draw ; コレも上のif文が真の時の中に入れてよい
	wait 5 ; 少しウェイトを掛ける(各自微調整してください)
	return

*draw
	redraw 0
	repeat 9
		noteget tmp,cnt/3+ichiy-1
		strmid tmp,tmp,cnt\3+ichix-1,1
		if tmp="1" : color 100,150,150 : else : color 255,255,255
		boxf cnt\3*(winx/3),cnt/3*(winy/3),cnt\3*(winx/3)+(winx/3),cnt/3*(winy/3)+(winy/3)
	loop
	color 200 : boxf winx/3+10,winy/3+10,winx*2/3-10,winy*2/3-10 ; 最後に自キャラを描画
	redraw
	return

赤色ボックスが自キャラで常に中心に表示されています。

白色部分が通路で方向キーで押した先のマスも白色であれば移動することが出来ます。

青色部分が壁で壁のある方向にキーを押しても移動しません。

メインループのif文はビット演算子(短絡演算子)を使ってますが、

moveラベル側のif文での再取得は=となっています。

もうそれぞれどのように違うかはご存知ですよね?

何度か説明したような気がするのですが記憶が曖昧なのでココで説明しておきましょう。

メインの「&」の方は押されたキーの中に指定するキーが含まれていた時に真となります。

=」の方は押されたキーと指定したキーが完全に一致する時だけ真になります。

初めの方向キーが押されたかをチェックする時から「●●キーは押されたか」とチェックしていたのでは、

それだけで4方向分も書かなくてはならなくてかさばってしまいます。

逆に2回目の「●●キーが押されたか」というチェックの時に「&」を使っていたのでは、

同時に押された時、例えば「上+右」だと「斜め右上」を取得してしまいます。

ゲームによっては斜め移動も出来るようにこの手を利用していただいて結構ですが、

ここでは上・右の両方とも壁である時に斜め右上が空いている時に

キーを上と右押せばいけてしまうというのはよくないでしょう。

壁チェック・描画の部分で初めにレクチャーした相対座標の求める処理が書かれていますね。

座標(ichix,ichiy)を中心にX座標を求めるためにichiy-1,ichiy,ichiy+1行目を全て順に取り出しています。

その取り出したものの中からichix-1,ichix,ichix+1を抜き出せばX,Y共に求めたことになるわけですね。

後はその取り出した値が0,1のどちらかを見て通路,壁をそれぞれ描画してやればOKです。

 

さぁ手順1はコレくらいで終了して手順の2に行きましょう。

ココではお待ちかねの疑似3Dについて進めて行こうと思っています。

やっと3D処理(疑似ですが)に入る!といいたいところなのですが、

手順3までこの章だけで行くことは難しいでしょう。

難しいというか長くなるだけで集中力が欠けちゃうよねってことで2章に分けます。

そしてお待ちかねの手順2の疑似3D化は残念ながら次章に持越しです…。

この章では3D化の元となる部分だけしますね。

 

先ほどのスクリプトは通路を移動する迷路、つまり「どこにいるか分からないゲーム」ではありましたが、

「どの方角を見ているか分からないゲーム」ではありませんでした。

北が上だと仮定すると、どれだけ歩いて角を曲がっても北は上のままです。

どの方角を向いているか分からないようにするにはどうすればよいでしょうか?

そう、角を曲がる処理をする時「背景を回転すればよい」のですね。

言うは易し、行なうは難し。一度考えてみましょう。

プログラムで書いてみる前にどういうことをするのか書いてみよう。

← 左回転 ─
─ 右回転 →

結果くらいはほぼ全員がパッと書けますよね。

少し混乱しそうな所ですが、

自キャラが左回転する(左を向く)と前にあったものは右に行くんですよね?

背景が左に回転するわけではありませんので間違えない様にご注意ください。

さて問題はどのようなプログラムを書けばこのように変えられるかというところです。

移動の流れを見てみましょう。

元々左上(-1,-1)にあった座標を左回転させると、右上(1,-1)に来るわけです。

自分の座標の上(0,-1)にあったマスは真右(1,0)に来ます。

右上(1,-1)にあったマスはと言うと右下(1,1)に来てますね?

─ 回転 →

パッと公式に出来なくても法則性みたいなものが見えてきませんか?

一番上の段一列にあったものは一番右の縦列になり、

真ん中段の一列は真ん中の縦一行に、下の一列は左の一行になります。

つまり、

(x,-1) → ( 1,y)
(x, 0) → ( 0,y)
(x, 1) → (-1,y)

ですね。また同時に、

左にあった縦一行は上の段一列に、真ん中の縦行は真ん中の横一列、右行は下の一列になります。

(-1,y) → (x,-1)
( 0,y) → (x, 0)
( 1,y) → (x, 1)

ココまで分かればプログラム化できるのではないでしょうか?

コレだけでもやり方はいくつもあります。

ココでは上のものをできる限りそのまま再現するプログラムをお届けしたいと思います。

	sdim map1,10
	sdim map2,10
	map1 ="101"
	map1+="000"
	map1+="011"
	;元の9コを見てみる
	repeat 9
		strmid tmp,map1,cnt,1
		pos cnt\3*50+270,cnt/3*50+50 : mes tmp
	loop
	;入れ替える
	tmp=0 ; 変数を数値化する(コレがないとバグります)
	repeat 9
		peek tmp,map1,cnt
		poke map2,1+(cnt\3)*3-(cnt/3)-1,tmp
	loop
	;結果を表示する
	pos winx/2,220 : mes "↓"
	repeat 9
		strmid tmp,map2,cnt,1
		pos cnt\3*50+270,cnt/3*50+300 : mes tmp
	loop
	stop

ちょい複雑そうな式が1つありますがこんな感じですかね…。

こんなのが1人でできる人は次の章見なくても1人だけで3D迷路なんて作られるはずです。

初めはたぶんこの結果が出来たとしてももっとifだらけの長いスクリプトとなることでしょう(笑

その気になればifなんて1つも使わなくても1つの式だけで表すこともできるので、

ダラダラと書くことを否定しませんがシンプルに行くこともできるというのを頭に入れておいてくださいね。

 

以上でこの章をおしまいにしたいと思います。

一番初めに書いたようにコレが最適な方法ではないと思われますが、

1つ目の迷路スクリプトと2つ目の回転スクリプトを混ぜ混ぜしちゃって、

どこにいてどこを向いているか分からない迷路ゲームを作ってみてください。

1つ今まででなかった点を付け加えなければならないのは、

回転して東西南北どこを向いているのか内部的には分かっておかなくてはいけないので

ソレ用の変数を設けておいて処理を分岐させるなりしてください。

1つの処理で出来ちゃうかもしれませんが、ね。

とりあえず中盤の感想…。

私も初めてやってみたものでホントに正しいの?というのがあります。

どこか間違っていたらおっしゃってくださいませ。

1つ目と2つ目の融合させたスクリプトはコチラから。