前章の最後で自分中心の背景が回転するスクリプトのダウンロードが出来ました。
自分で作ってみた迷路スクリプトはアレと似たようなものが出来たかな?
出来てなくても理解できていれば進んでよいでしょう。
理解が出来ていないのなら進んでもあまり意味がありません。
もう一度前章を読まれることを強くお勧めします。
…で最後のスクリプトは自分中心の迷路ゲームでは問題ないのですが、
自分視点の迷路ゲーム、いわゆるこの講座の主旨である3D迷路には不向きです。
何がダメだったかを考えていただければすぐに分かるかと思いますが、
答えを申しますと、自分の視界に入るはずのないものが映っていることです。
自分付近の9マスを確保する時、前章のように自分マス+周り8マスを確保しても、
斜め後ろ、真後ろは視界の外なので確保しても意味がないですね。

最もオーソドックスな9マス確保の仕方としては下記のようになるでしょう。

…ということで、前章ダウンロードできるスクリプトを自分視点用に改造しましょう。
そのスクリプトの前方の視界は1マスだけでしたが今度は2マスとなります。
それくらいの変更なんて簡単、と思ってたら大間違いです。
2マス先のマスは今まで視界外だったマスとなるので、簡単にはいかないわけです。
![]() |
─ 右回転 → | ![]() |
|---|
とりあえずスクリプトを書いてみましょ。
使用しているDATファイルは前章のものとは別です。
コチラから新しくダウンロードし直してください。
dim forwardx,4 dim forwardy,4 dim direction,1 sdim tip,2,25 forwardx=0,-1,0,1 forwardy=-1,0,1,0 exist "maze2.dat" if strsize<0 : dialog "マップデータがありません" : end sdim map,strsize+1 bload "maze2.dat",map notesel map ichix=10 : ichiy=10 gosub *draw repeat stick key,15 if key&15 : gosub *move wait 1 loop *move x=forwardx.direction : y=forwardy.direction if key=2&(tip.7="0") : ichix+=x : ichiy+=y : gosub *draw if key=1 : direction=direction+1\4 if key=4 : direction=3-(direction*-1)\-4 if key&5 : gosub *draw wait 10 return *getdata x=0 repeat 5,-2 noteget tmp,ichiy+cnt strmid tmp,tmp,ichix-2,5 repeat 5 if direction=0 : y=x*5+cnt if direction=1 : y=4-x+(cnt*5) if direction=2 : y=24-(x*5+cnt) if direction=3 : y=4-cnt*5+x strmid tip.y,tmp,cnt,1 loop x+ loop return *draw gosub *getdata redraw 0 repeat 9 x=cnt\3+1+(cnt/3*5) if tip.x="1" : color 100,150,150 : else : color 255,255,255 boxf cnt\3*(winx/3),cnt/3*(winy/3),cnt\3+1*(winx/3),cnt/3+1*(winy/3) loop color ,100 tmp=3-direction+1\4 pos forwardx.tmp*30+winx-50,forwardy.tmp*30+winy-50 : mes "北" pos winx-50,winy-50 : mes"▲" color 200 : boxf winx/3+10,winy-(winy/3)+10,winx*2/3-10,winy-10 redraw return
はい、こんな感じです。
DATファイルの変更について、から入りましょう。
DATファイルをメモ帳等のテキストエディターで開くと、
一番外側の壁(=1)は、全て2マス分となっています。
もし外壁が1つ分だけしかない場合、
2マス先まで取得するのに自キャラが端に来た時2マス先が存在しない為、
エラーで落ちるなど問題が発生してしまうことが十分にありうるので
視界分の分厚さの外壁を用意してください。
それでは次にスクリプト内容に入りましょう。
パッと見て変わったな、と思う*getdataの説明をします。
今回のマップからデータの取得をする方法が違いますね。
前回は1行内の自分周りのデータを3行分取ってきて
描画時にチップ位置を回転させて配置していました。
今回は1行内の自分周りのデータを視界分(2マス先)取って来て、
チップ位置を回転させて配列変数に一旦代入し、
描画時にその配列変数内の描画するチップに対応した要素を取り出して配置してます。
| 前 | |||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 左 |
| 右 | |||||||||||||||||||||||||
| 後 |
配列要素[12]が自キャラの位置です
どの方角を向いてても回転させたチップ番号を各要素に代入するので
コレを取り出せばOKですね。
…で上のように取得データは25チップ分ですが、
*drawでは表示に使う9チップ分だけしか使っていません。
「x=cnt\3+1+(cnt/3*5)」の式がテーブルの色の付いたチップを取り出す式です。
自キャラの1マス分前の当たり判定も大分シンプルとなりましたね。
条件が「if key=2&(tip.7="0")」となっています。
キーが上を押されていて配列tipの要素7が通路であれば進む、ということですね。
要素番号[7]というのについては上の要素テーブルを参照してください。
それ以外については前ページのものと同じなので問題ないでしょう。
さて、遅くなりましたがコレでようやく(疑似)3D表示が出来るところまで来ました。
表示方法についての説明をしておきましょうか。
一番手っ取り早いのは前ページの完成図のような一枚絵を用意しておき、
その地形に応じて表示する画像を変えていくのが一番簡単で処理的にも一番早いでしょう。
しかし自分を除く8マス分がそれぞれ壁であるかないかを計算すると、
「2(壁か否か)の8(マス数)乗+1(天井)×?(壁の種類)」
で最低257枚画像を用意しなければなりません。
コレはちょっと無駄が多いにもほどがありますので、
これから説明する良くある手法でやっていきましょう。
まず、下記の使用する画像を用意しておきます。
![]() | ![]() | ![]() | ![]() | ![]() |
| 天井と地面 | 前壁 | 奥の前壁 | 横壁 | 奥の横壁 |
|---|
後は、地形に合わせてそれぞれの画像の一部を切り貼りしてやればいいわけですが、
適当にやってよいというわけではなく以下の手順でやる必要があります。

基本的に奥から手前へ、両端から中央へ描画していきます。
(1)の黄色部分の壁は先ほどの使用する画像の「奥の横壁」を使います。
(2)(4)(6)は「横壁」。緑色の(3)は「奥の前壁」。水色の(5)は「前壁」です。
同じ数字がありますが、コレはどちらを先にしてもよいものであることを示します。
この順番で壁を配置していくことでどんな地形でも表せられますね。
画像内の壁と通路の境界位置や全体の比率も大体決まっているようですが、
講座用に独自の比率でやりました(…単に細かい位置まで合わせるのが面倒だった)。
ちなみに下記の比率でやってみました。

通常良く利用される比率では(1)の位置の「奥の横壁」が必要となるのですが、
講座用の比率だと視界の角度的に(1)の位置まで入らないので、
使用するのは(2)(3)(4)(5)(6)だけとなります。
…というかカットしました。
少しでも楽になる様に。
比率についても微妙におかしいです。左右対称ではありません(ぉ
1〜2ドットずれていますが見た目的に問題がなかったので無視しました^^;
さて、疑似3D描画のスクリプトは下記になります。説明は後ほど。
#define WX 640
#define WY 480
dim gpx,9 ; 前壁・奥の前壁に使用する画像内の左端位置
dim gpy,9 ; 横壁に使用する画像内の左端位置
dim gsx,9 ; 前壁・奥の前壁の描画に必要な横幅
dim gsy,9 ; 横壁の描画に必要な横幅
sdim tip,2,9
gpx=0,196,441,0,100,538 : gsx=196,245,199,100,438,102
gpy=196,0,320,100,0,441,0,0,538 : gsy=124,0,121,96,0,97,100,0,102
tip="0","1","0","0","0","0","1","0","1"
buffer 2
picload "back.bmp"
buffer 3,WX*4,WY
pos 0,0 : gzoom WX*4,WY*4,2,0,0,WX*2,WY*2
gsel 0
gmode 2
*main
redraw 0
color : boxf
pos 0,0 : gcopy 3,0,0,WX,WY
gosub *draw
gosub *wall
redraw
stop
*draw
repeat 9
if tip.cnt="1" {
pos gpy.cnt,0 : gcopy 3,WX*1+gpy.cnt,0,gsy.cnt,WY
pos gpx.cnt,0 : gcopy 3,cnt/3+2*WX+gpx.cnt,0,gsx.cnt,WY
}
loop
return
*wall
font "",20,1
color 255
repeat 9
pos cnt\3*25+WX-80,cnt/3*25+WY-80 : mes tip.cnt
loop
return
今回は描画する壁に全てが当てはまる規則性がなかったので、
パラメータの数値を壁数分入れておいたテーブルから参照する形にしました。
こういった「処理自体は同じだがパラメータに規則性がない」場合には
こういった手法も利用すると良いでしょう。
スクリプト初めの方でマップ内の壁情報を入れているtipの
0と1を変えてみて他の地形も表示させてみてください。
試していただくと、まだコレは完全ではないことに気づかれることでしょう。
3列内の右側が壁(=1)の場合だと表示がおかしくなってしまいます。
また、先ほどの手順を書いた画像通りに壁を配置していくと無駄が出来てしまいます。
例えば、(5)の手前の前壁が全てある時、奥側の壁情報なんて描画する必要ありません。
後で(5)を描画するわけですから消えてしまいます。
わかりにくそうなので無駄一覧を書いてみましょうか?
・(2)を描画する時、(3)の真ん中、又は(5)の真ん中がある場合
・(3)の端側を描画する時、(4)、又は(5)の端側、又は(6)がある場合
・(3)の真ん中を描画する時、(5)の真ん中がある場合
・(4)を描画する時、(5)がある場合
・(5)の端側を描画する時、(6)がある場合
これらの条件に当てはまる場合は描画しないようにすれば、
先ほどの表示がおかしくなるバグが直ったり、
処理速度を軽減することが出来ます。
時間の都合でこの修正についてはスキップします。
17章の初めから読んできて理解されている方にとってはすぐに解決できる問題でしょう。
遅くなりましたが、最後の迷路自動生成について説明しましょう。
ランダムに壁を配置するわけですが、
完全なランダムではダメだということは想像に難くないと思います。
壁になっておかなくてはいけない条件は、
・上下左右の外枠となるチップ(今回の場合、2マス分以上)
・通路にするマスに隣接する8マスの内、4つ以上
2つ目の条件は、3つ以下だと2×2以上の塊(部屋)が
出来てしまう可能性がでてくるので防ぐために必要となります。
しかし、コレを1マスずつ調べていたのでは広いマップを作成する時、
時間が掛かってしまいかねませんし、
何より判定処理を考えて取り入れるのが面倒です。
下図の感じで、まずは外枠を除く内側部分を1つ飛ばしで壁を配置しておきます。
後は残った通路部分からランダムで壁を配置してやれば出来そうですね。
2分の1の確率で壁にすると設定すると壁だらけになってしまいそうなので、
3分の1の確率で壁になるようにしてみたものが下記スクリプトです。
#define MX 79 ; X方向のマス数
#define MY 59 ; Y方向のマス数 randomize sdim map,2,MX*MY*2 repeat MY repeat MX tmp=row*MX+cnt ; 外枠の場合通過する if row<2|(cnt<2)|(MX-2<=cnt)|(MY-2<=row) { map.tmp="1" ; 1つ飛びで壁を配置し,通路になるマスのうつ3/1を壁とする } else { if row\2=1&(cnt\2=1) { map.tmp="1" } else { rnd r,3 : r=r/2 : map.tmp=""+r } } loop row+ loop title "迷路自動生成" sx=640/MX : sy=480/MY ; 1マスの大きさを計算 redraw 0 repeat MY*MX if map.cnt="1" : boxf cnt\MX*sx,cnt/MX*sy,cnt\MX*sx+sx,cnt/MX*sy+sy loop redraw stop
コレを実行してみてどう感じますか?
一部分の通路が全て壁で囲まれてしまっていて
行くことのできない意味のない通路が出来てしまっていたり、
1つ飛びのままで壁が生成されていない部分があったりと
結構迷路に偏りがあるように感じられると思います。
つまり、結論として完全なランダムの壁配置はあまり使えないということですね。
完全なランダムではないとしたらどのような規則を作っておけばよいかと言うと、
「棒倒し法」「穴掘り(道伸ばし)法」「壁伸ばし法」
と呼ばれているアルゴリズムを利用すると良いでしょう。
棒倒し法は作成が一番簡単な手法ですが、見栄えが悪いものとなってしまいます。
この先行き止まりであるというのがすぐにわかってしまうので、
2Dの迷路としては使えたものじゃありません。
今回のような3D迷路としてはコレで十分なので後ほど説明していきます。
穴掘り法は、既に道であるポイントから道を伸ばす手法で、
迷路を解く時誤った道を選ぶとタイムロスが大きく
パッと見て間違いかどうかわかりにくい迷路が作成できます。
道から壁がない方向に道を伸ばすというアルゴリズムの性質上、
1つの直線が長くなってしまうことが問題?でしょうか。
壁伸ばし法は穴掘り法と同じ道から伸ばすアルゴリズムですが、
伸ばす先のチェック方法が異なり、
壁を伸ばすことをランダムで選択するということから
穴掘り法のような長い直線が出来にくくなります。
さて、それでは上で書いたようにココでは棒倒し法を説明します。
検索エンジンで検索してもらえればココと同じやり方が出てくると思います。
ココで分かりにくければ他を当たってみてくださいね。
それでは、作り方の方を説明していきたいと思います。
棒倒しというくらいですので壁から通路にめがけて壁を作って通路を塞ぐものです。
まずは完成図をご覧になっていただきましょう。

コレくらい細かいものだと少し難しそうに感じるかもしれませんが、
実際に解いてみるとショボショボです。
よく見ると、こんな行き止まり間違えていく奴がいるのか?
と思える行き止まりがたくさんあるのがわかります。
アルゴリズム上こんなものがたくさん出来てしまいますので
2D迷路には不向きなのですが、
3Dにすると自分の周りしか見えないので問題はありません。
では、この迷路はどのようにして作成されているかを説明していきます。
予め上図の状態を作っておきます。
別にこれからでなくても出来ますが説明しやすいので。
赤色が壁と仮定すると、外壁の中に1つ飛びで壁が9個あります。
外壁の中にあるそれぞれの壁から、棒を倒すように通路にめがけて壁を作るわけです。
この壁の作り方を説明しますね。
まず、外壁の内側にある左端に並んだ縦4つの壁から好きな方向に壁を作ります。
新しく作成した壁は緑色で示しています。
この後も中の壁から通路に壁を伸ばしていくわけですが、
次からは、伸ばせる方向が左以外の上右下の3方向だけにします。
壁にしようとした場所が既に壁の場合、どうするかで大きな変化が現れます。
1つは、既に作られてたらもう一度作ることができる方向を探す。
こうするとゴールまで行ける経路は1本だけの迷路が出来、
分かれ道が来たら必ず右に進むことで必ずゴールできる右手法が使えるようになります。
もう1つは、諦めるパターン。
こうするとゴールまでの経路が複数できることとなります。
3D迷路の場合、既に来たことのある場所かわかりにくいので
諦めるパターンの方が彷徨う形になって面白くなるのではないでしょうか?
この章のダウンロードできるスクリプトは、
17,18章の総合スクリプトではなくて棒倒し方アルゴリズムのスクリプトです。
コチラからどうぞ。
2章に渡ってお届けした迷路もコレにて終了したいと思います。
コレにイベントとかアイテムを付けたりしてゲームを完成させてみてください。
それでは。