見出し画像

【疑似3DダンジョンRPG開発日誌 07】 マップ管理システムをリファクタリング


やったこと

疑似3DダンジョンRPG「グラヴェンの傷跡」の開発中、マップデータの管理方法を見直して整理した。

きっかけ:階段のグラフィックをどう描くか考えていた

階段をどこに描画するか調べていて、「床に描くか、壁に描くか」という問題にぶつかった。

オリジナルのWizardryは床面に台形の穴として描いていたが、今の自分のコードだと床は全体を一色で塗りつぶしているだけなので、特定のマスだけ穴を描くには台形の計算を新たに書く必要がある。

一方、壁面なら今ある扉の仕組みがそのまま使えるので、そっちのほうが無難という結論に。

本題:扉と階段の管理方法を見直した

階段の話をしていて、今の管理方法に問題があることに気づいた。

変更前の状態

扉・階段・固定エンカウントがそれぞれ別の辞書で管理されていた。

python

DOORS = {
    0: {(2, 1): False, (4, 7): False},  # False=閉, True=開
    ...
}

STAIRS = {
    0: {(1, 8): "up", (8, 8): "down"},
    ...
}

FIXED_ENCOUNTERS = {
    (3, 6): "グラキエス = ドラゴス",
}

将来、罠や他の要素を追加するたびに辞書が増えていく構造で、管理がどんどん大変になっていた。

ウィザードリィライクの定番設計をClaudeに聞いてみた

一般的なWizardryライクのゲームでは壁属性方式が主流だとわかった。

各マスが4面の壁を持ち、それぞれの壁面に属性(壁/扉/階段/隠し扉…)を持たせる設計。

        北の壁
         ↑
西の壁 ←[マス]→ 東の壁
         ↓
        南の壁

ただしフルに実装すると100マス×4方向=400面分のデータが必要になる。実際にはほとんどの壁面は「壁」か「通路」しかないので、特殊な属性だけ辞書で持つ「差分方式」が現実的。

実際にやった変更

ベースマップ(0/1の2次元リスト)はそのまま活かして、特殊属性だけを1つの辞書に統合した。

python

# 変更後:CELL_ATTRSに一本化
INITIAL_CELL_ATTRS = {
    0: {  # 1階
        (2, 1): {"type": "door",        "open": False},
        (4, 7): {"type": "door",        "open": False},
        (1, 8): {"type": "stairs_up"},
        (8, 8): {"type": "stairs_down"},
    },
    1: {  # 2階
        ...
    },
}
CELL_ATTRS = copy.deepcopy(INITIAL_CELL_ATTRS)

変更したファイルは3つ。

  • data.py … DOORS / STAIRS を削除して CELL_ATTRS に統合

  • utils.py … is_wall() と get_view_data() を CELL_ATTRS 参照に更新

  • dungeon_rpg.py … DOORS / STAIRS を参照していた箇所を修正

変更後の感想

データ量はほとんど増えず、むしろ辞書の数が減ってスッキリした。

今後の拡張もこの形で追加するだけでよくなった。

python

# 将来の追加例
(4, 2): {"type": "door", "open": False, "locked": True},  # 鍵付き扉
(6, 3): {"type": "relief"},   # レリーフ(壁の飾り)
(7, 4): {"type": "trap"},     # 罠

小さなリファクタリングだけど、設計の考え方をいろいろ考えたので多少は身になったかも…

次にやること

  • 階段のグラフィック素材を作る(壁面に重ねる方式で)

  • _draw_3d() に stairs_down / stairs_up の描画分岐を追加する

いいなと思ったら応援しよう!

しん もし記事が参考になったり、開発を応援してもいいと思ってくれた方がいたら、noteのサポートをいただけると次の記事を書く原動力になります。もちろん読んでくれるだけで十分嬉しいです。