aburi6800のブログ

コンピュータのプログラミング、ゲームに関するニッチな情報を書いていくブログです。

【Python】3D迷路の中を歩きたい①

 久しぶりの更新です。
 いきなりですが、今回はPythonで3D迷路をやってみたいと思います。

(2020/6/1追記)
 実装を進めたところ、マップの検索範囲について問題があったため、修正しました。

なぜ3D迷路?

 私にとって3D迷路は、古くはWizardryからの憧れでした。
 あの暗く無機質なダンジョンを黙々と歩くだけで、とてもスリルに満ちたものでした。
 あまりにも面白いので、自作RPGでも3Dダンジョンを探索するもの作りたかったのです。
 しかし、昔の自分は3D迷路を表示するアルゴリズムがどうしても思いつかず、実現できませんでした。

 そして今、改めて実現方法を考えていたところ、うまく行きそうな感触が得られました。
 今となっては不要な情報なのかも知れませんが、一応参考として作成過程を残しておこうと思います。

なぜPython

 これまでの記事を見た方は、せっかくMSXを始めたのに何故?と思われるかも知れませんが・・・。
 正直、アセンブラは処理の組み方が難しく、なかなか形にできませんでした。
 そこで、昔のBASICのような感覚で、コンパイルなしに簡単に動かすことができて、OSにあまり依存しない言語はないか?と探していたところ、Pythonに行きついた、という訳です。
 ゲームの分野ではまだマイナーですが、Tcl/TkやゲームエンジンPygame、Pyxel)を使うことで作ることも可能です。
 言語的にもC++C#など他の言語よりシンプルなので、Sandbox的に使うのもありかと思います。

まずは下準備

 さて、3D迷路でよくある操作系では、上を押すと前進、左右で方向を変える、というものがあります。
 これは、普通の見下ろし型マップと違い、明確に方向を意識する必要がある、ということですね。
 この方向の概念を処理しやすくするため、まずは以下の定数(といっても、Pythonには定数がないので形だけですが)を定義します。

# 方向
DIRECTION_NORTH = 0
DIRECTION_EAST = 1
DIRECTION_SOUTH = 2
DIRECTION_WEST = 3

 時計回りに北方向、東方向、南方向、西方向としています。
 こうすると、方向を表す変数を加減算することで、方向を変えられます。
 
 次に、それぞれの方向に対する、横座標、縦座標の移動量を定義します。
 マップデータは2次元のリストで作るのですが、その中の自分の位置を変えるときの、方向に対する移動量というわけです。
 ゲーム中は変更する必要がないのでタプル型にし、方向の値に対応したインデックスに横方向、縦方向の移動量を設定します。

# 方向に対する増分
vx = ( 0, 1, 0,-1)
vy = (-1, 0, 1, 0)

 例えば、上方向の場合は北方向を表す定数の値がゼロなので、vx[0]とvy[0]の値が参照され、X座標は変更がなく、Y座標は-1されます。
 左方向の場合は西方向を表す定数の値が3なので、vx[3]とvy[3]の値が参照され、X座標は-1され、Y座標は変更がありません。

画面の表示に必要なマップの情報を得る

 さて、ここまででコントローラーの左右を押して方向を変え、上を押して前進するための準備はできました。
 次に、迷路を表示するため、自分の正面方向に見えるマップの情報を取得するためのデータを準備をします。
 あまり遠くまで見通せるとダンジョンの暗い雰囲気が出ないので(実は処理が面倒なだけですが)、ここでは自分から見て2ブロック先まで見えるようにします。
 これを実現するため、以下の範囲でマップを見ることにします。

 |0|1|2|3|4|
   |5|6|7|
   |8|9|A|
   |B|C|D|

 この図は、自分が北方向を見ているとした場合に画面に見える範囲です。
 この順番でマップの2次元リストからデータを取得します。
 Cは自分の位置としており、B~A(A,B,C,Dは10,11,12,13と読み替えてください)は自分とその左右のブロック、8~Aは1ブロック先(目の前)とその左右、5~7が2ブロック先、0~4は3ブロック先となります。
 3ブロック先を5ブロック見るのは、例えば目の前に壁がないときなどで画面の情報量を増やすためです。
 遠くにあるものは多くのものが見える、というわけですね。
 
 取得先のマップの座標は、上の図の場合、以下のようになります。

 |(x-2,y-3)|(x-1,y-3)|(x,y-3)|(x+1,y-3)|(x+2,y-3)|
           |(x-1,y-2)|(x,y-2)|(x+1,y-2)|
           |(x-1,y-1)|(x,y-1)|(x+1,y-1)|
           |(x-1,y)  |(x,y)  |(x+1,y)  |

 この座標増分データを、4方向それぞれに対して用意してやれば良いわけです。
 具体的には、以下のように2次元のタプル型で定義します。

# マップの参照先の定義
# 参照順のイメージは以下(上向きの場合。自分の位置はCとする)
# |0|1|2|3|4|
#   |5|6|7|
#   |8|9|A|
#   |B|C|D|
pos_x = (
    (-2,-1, 2, 1, 0,-1, 1, 0,-1, 1, 0,-1, 1, 0),
    ( 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, 1, 0, 0, 0),
    ( 2, 1,-2,-1, 0, 1,-1, 0, 1,-1, 0, 1,-1, 0),
    (-3,-3,-3,-3,-3,-2,-2,-2,-1,-1,-1, 0, 0, 0)
)
pos_y = (
    (-3,-3,-3,-3,-3,-2,-2,-2,-1,-1,-1, 0, 0, 0),
    (-2,-1, 2, 1, 0,-1, 1, 0,-1, 1, 0,-1, 1, 0),
    ( 3, 3, 3, 3, 3, 2, 2, 2, 1, 1, 1, 0, 0, 0),
    ( 2, 1,-2,-1, 0, 1,-1, 0, 1,-1, 0, 1,-1, 0)
)

 ちょっとややこしいですが、pos_x、pos_yは自分の現在の座標に対する、横・縦座標の増減量で、それぞれ4方向に対して定義しています。
 インデックス0は先の図と合わせて見て頂けるとわかると思います。
 インデックス1は先の図を右回りに90度回転させたイメージで定義しています。
 インデックス2、3はインデックス0、1の値の正負を逆転すれば良いだけですね。

マップデータの取得をテストしてみる

 さて、ここまでで移動に関する準備と表示に関する準備ができました。
 画面表示の処理に取り掛かる前に、想定した通りにマップデータを取得できているか、確認してみます。
 
 まず、自分の今の位置と方向を定義します。
 マップの左上に、下向きに位置している設定とします。

# 自分の最初の座標と方向
x = 1
y = 1
direction = DIRECTION_SOUTH

 次に、適当なマップデータを作ります。
 外周は必ず壁で囲みます。  まだ歩き回れないので、小さなものにします。

# マップ
# 0 = 通路
# 1 = 壁
# 外周は必ず壁とする
map = [
    [ 1, 1, 1, 1, 1],
    [ 1, 0, 1, 0, 1],
    [ 1, 0, 1, 0, 1],
    [ 1, 0, 0, 0, 1],
    [ 1, 1, 1, 1, 1]
]

 最後に、マップから情報を取得する処理を書きます。
 pos_x、pos_yの要素数だけ繰り返しますが、2ブロック先はマップ外を参照する可能性があるため、マップ外を見ようとしたときは壁として処理するようにします。

for i in range(0, 14):
    map_x = x + pos_x[direction][i]
    map_y = y + pos_y[direction][i]
    if map_x < 0 or map_x > 4 or map_y < 0 or map_y > 4:
        data = "1"
    else:
        data = map[map_y][map_x]

    print(str(map_x) + ":" + str(map_y) + "=" + str(data))

実行すると、以下のようにコンソールに出力されました。

3:4=1
2:4=1
-1:4=1
0:4=1
1:4=1
2:3=0
0:3=1
1:3=0
2:2=1
0:2=1
1:2=0
2:1=1
0:1=1
1:1=0

 うまく取得できているようです!
 あとは、この順番に「1」の部分で距離に対応した壁の絵を書けば、3D迷路が表示できるはずです。