見出し画像

6-4 保存するデータ3 マップ

同人誌について

 この連載は、同人誌『PythonとPygameで作る レトロ風RPG 全コード』を一部抜粋して編集したものです。

 同人誌本編には、ゲーム本体のソースコードや、各種のサンプルコード、Windowsで実行できるEXEファイルが付属しています。PDFで288ページの本になります。ぜひ、こちらもご購入ください(2024-03-10:ver1.0.3 に更新)。


説明と全体コード

 「src/mymod/data/map.py」の説明です。マップのデータ用のモジュールです。

from random import seed, randrange
from dataclasses import dataclass

SEED = None     # ランダムのシード値(None or int)
PLAIN    = 0
FOLEST   = 1
MOUNTAIN = 2
WATER    = 3
TOWN     = 4
CASTLE   = 5

COLS = [    # ミニ マップ用色配列
    (152, 232,   0),    # 平地
    ( 56, 104,   0),    # 森
    (192, 112,   0),    # 山
    ( 80, 128, 255),    # 水
    (232, 208,  32),    # 街
    (144,  64, 240)     # 魔王城
]

@dataclass
class Map:
    w: int
    h: int
    map:list[int]
    towns: list[list[int]]

    # 初期化
    @classmethod
    def from_wh(cls, w: int, h: int) -> "Map":
        res = Map(w, h, [], [])
        res.map = [PLAIN] * h * w
        return res

    # マップ生成
    def gen(self, start_x: int, start_y: int):
        seed(SEED)
        w, h, m = self.w, self.h, self.map

        # 水
        for i in range(int(w * 1.5)):
            x = randrange(w)
            y = randrange(h)
            for j in range(3):
                r = (j % 2) + 1
                x2 = x + randrange(-r, r, 1)
                y2 = y + randrange(-r, r, 1)
                x2 = (x2 + w) % w
                y2 = (y2 + h) % h
                m[x2 + y2 * w] = WATER

        # 開始位置(周囲の水を消す)
        for i in range(9):
            x = start_x - 1 + i % 3
            y = start_y - 1 + i // 3
            m[x + y * w] = PLAIN

        # 山と森
        for i in range(w):
            x = randrange(w)
            y = randrange(h)
            for j in range(24):
                r = (j % 3) + 1
                x2 = x + randrange(-r, r, 1)
                y2 = y + randrange(-r, r, 1)
                x2 = (x2 + w) % w
                y2 = (y2 + h) % h
                if j < 16:
                    m[x2 + y2 * w] = FOLEST
                else:
                    m[x2 + y2 * w] = MOUNTAIN

        # 街と城
        step = 5
        area_w = w // (step * 3)
        area_h = h // (step * 3)
        for i in range(9):
            x = i % 3
            y = i // 3
            x2 = (x * step + 1) * area_w + randrange(area_w * 3)
            y2 = (y * step + 1) * area_h + randrange(area_h * 3)
            if i == 4:
                m[x2 + y2 * w] = CASTLE
            else:
                m[x2 + y2 * w] = TOWN
                self.towns.append([x2, y2])

インポート部分

 インポート部分を示します。

from random import seed, randrange
from dataclasses import dataclass

 マップ生成にランダムを使うので`random`モジュールの`seed`と`randrange`を読み込みます。また、`dataclass`を使うので読み込みます。

定数

 冒頭では、ゲームで利用する定数を用意します。

SEED = None     # ランダムのシード値(None or int)
PLAIN    = 0
FOLEST   = 1
MOUNTAIN = 2
WATER    = 3
TOWN     = 4
CASTLE   = 5

 `SEED`の値を数値にすると、マップの配置を固定化できます。通常は`None`にして、毎回異なるマップにしています。

 次は、ミニマップの色の配列です。ミニマップは、マップ画面の右上に表示する、小さなマップです。このミニマップの色を用意します。

COLS = [    # ミニ マップ用色配列
    (152, 232,   0),    # 平地
    ( 56, 104,   0),    # 森
    (192, 112,   0),    # 山
    ( 80, 128, 255),    # 水
    (232, 208,  32),    # 街
    (144,  64, 240)     # 魔王城
]

Mapクラス1

 マップを表す`Map`クラスです。クラス メソッド`from_wh()`を持ちます。このメソッドは、横幅と高さの引数から、自身のオブジェクトを作成します。

@dataclass
class Map:
    w: int
    h: int
    map:list[int]
    towns: list[list[int]]

    # 初期化
    @classmethod
    def from_wh(cls, w: int, h: int) -> "Map":
        res = Map(w, h, [], [])
        res.map = [PLAIN] * h * w
        return res

Mapクラス2 マップ生成

 マップ生成のアルゴリズムです。`random`の`randrange()`関数を利用して、一定の範囲に地形を散布していきます。

    # マップ生成
    def gen(self, start_x: int, start_y: int):
        seed(SEED)
        w, h, m = self.w, self.h, self.map

        # 水
        for i in range(int(w * 1.5)):
            x = randrange(w)
            y = randrange(h)
            for j in range(3):
                r = (j % 2) + 1
                x2 = x + randrange(-r, r, 1)
                y2 = y + randrange(-r, r, 1)
                x2 = (x2 + w) % w
                y2 = (y2 + h) % h
                m[x2 + y2 * w] = WATER

        # 開始位置(周囲の水を消す)
        for i in range(9):
            x = start_x - 1 + i % 3
            y = start_y - 1 + i // 3
            m[x + y * w] = PLAIN

        # 山と森
        for i in range(w):
            x = randrange(w)
            y = randrange(h)
            for j in range(24):
                r = (j % 3) + 1
                x2 = x + randrange(-r, r, 1)
                y2 = y + randrange(-r, r, 1)
                x2 = (x2 + w) % w
                y2 = (y2 + h) % h
                if j < 16:
                    m[x2 + y2 * w] = FOLEST
                else:
                    m[x2 + y2 * w] = MOUNTAIN

        # 街と城
        step = 5
        area_w = w // (step * 3)
        area_h = h // (step * 3)
        for i in range(9):
            x = i % 3
            y = i // 3
            x2 = (x * step + 1) * area_w + randrange(area_w * 3)
            y2 = (y * step + 1) * area_h + randrange(area_h * 3)
            if i == 4:
                m[x2 + y2 * w] = CASTLE
            else:
                m[x2 + y2 * w] = TOWN
                self.towns.append([x2, y2])

 広大なマップを作ることを想定していないので、簡単なランダムで済ませています。ある程度以上大きなマップを作るなら、もっと構造化された作り方をした方がよいです。

 また、きちんとマップを作るのなら、移動不能な場所を排除する必要があります。今回のゲームでは、そうしたマップが生成される可能性は低いので割愛しています。

 `random`モジュールの`randrange()`関数の説明を書きます。いくつかの引数を指定できます。

randrange()


 次の内容については省略します。こちらは同人誌をご覧ください。

  • 6-5 保存するデータ4 マップ イベント


同人誌について

 この連載は、同人誌『PythonとPygameで作る レトロ風RPG 全コード』を一部抜粋して編集したものです。

 同人誌本編には、ゲーム本体のソースコードや、各種のサンプルコード、Windowsで実行できるEXEファイルが付属しています。PDFで288ページの本になります。ぜひ、こちらもご購入ください(2024-03-10:ver1.0.3 に更新)。

 このnoteの記事と、Webページに一部抜粋版を掲載しています。

 技術系同人誌など まとめページ


この記事が気に入ったらサポートをしてみませんか?