見出し画像

【Pyxel】Pyxelで学ぶゲームプログラム 〜マップデータの読み込み


今回は、レトロゲームエンジン「Pyxel」を使って、マップデータを読み込む方法について紹介します。

■最小限のコード

まずはPyxelで動作させるための最小限のコードを書きます。

import pyxel

class App:
    def __init__(self):
        pyxel.init(160, 120, fps=60)
        pyxel.run(self.update, self.draw)

    def update(self):
        pass

    def draw(self):
        pass

App()

実行して何もない真っ黒な画面が表示されることを確認します。

■マップチップ画像のダウンロード

以下のURLからマップチップ画像をダウンロードします。
http://syun777.sakura.ne.jp/tmp/pyxel/tileset.png

そしてダウンロードした画像を実行するスクリプトと同じフォルダに配置します。

■マップチップ画像の読み込みと描画

マップチップ画像を読み込んで表示します。

import pyxel

class App:
    def __init__(self):
        pyxel.init(160, 120, fps=60)
        pyxel.image(0).load(0, 0, "tileset.png") # マップチップ画像の読み込み
        pyxel.run(self.update, self.draw)

    def update(self):
        pass

    def draw(self):
        pyxel.cls(0)
        # 画像を描画する
        pyxel.blt(0, 0, 0, 0, 0, 40, 32, 2)

App()

ひとまず以下のように表示されます。

■マップチップ画像の描画

先ほどは画像を描画しただけですので、今度は自由にマップとなるデータを用意して描画できるようにします。
マップ画像は以下のように番号が割り振られているとします。

その場合、以下のデータをマップデータとした場合、

        map = [
            [0,  0,  0, 0,  0,  0],
            [0,  0,  0, 0,  0,  0],
            [0,  5,  7, 0,  0,  0],
            [0, 15, 17, 0,  0,  0],
            [0,  0,  0, 0,  5,  7],
            [0,  0,  0, 0, 15, 17],
        ]

マップは以下のように描画されることになります。

では、マップチップ画像を読み込んで描画するために、専用のクラスを作ります。

import pyxel
import math # math.floorを使うので必要

class Map:
    SIZE = 8 # チップサイズ
    CHIP_WIDTH = 5 # 1列に5つ並んでいる
    CHIP_HEIGHT = 5 # 5行並んでいる
    
    # マップチップ座標をスクリーン座標に変換
    @classmethod
    def to_screen(cls, i, j):
        return (i * cls.SIZE, j * cls.SIZE)
    
    # マップチップの描画
    @classmethod
    def draw_chip(cls, i, j, val):
        # スクリーン座標に変換
        x, y = cls.to_screen(i, j)
        # チップ画像の座標を計算
        u = (val % cls.CHIP_WIDTH) * cls.SIZE
        v = (math.floor(val / cls.CHIP_WIDTH)) * cls.SIZE
        pyxel.blt(x, y, 0, u, v, cls.SIZE, cls.SIZE, 2)

これを使ってマップを描画します。

class App:
    def __init__(self):
        pyxel.init(160, 120, fps=60)
        # マップデータの定義
        self.map = [
            [0,  0,  0, 0,  0,  0],
            [0,  0,  0, 0,  0,  0],
            [0,  5,  7, 0,  0,  0],
            [0, 15, 17, 0,  0,  0],
            [0,  0,  0, 0,  5,  7],
            [0,  0,  0, 0, 15, 17],
        ]
        pyxel.image(0).load(0, 0, "tileset.png")
        pyxel.run(self.update, self.draw)

    def update(self):
        pass

    def draw(self):
        pyxel.cls(0)

        # マップの描画
        self.draw_map()

    def draw_map(self):
        # 外枠の描画
        pyxel.rectb(0, 0, Map.SIZE*6, Map.SIZE*6, 5)
        
        # 各チップの描画
        for j, arr in enumerate(self.map):
            for i, d in enumerate(arr):
                Map.draw_chip(i, j, d)

実行すると以下のように表示されます。

■外部テキストから読み込みしてマップを表示する

プログラム内でマップデータを定義しましたが、これを外部テキストに用意して簡単に編集できるようにします。
まずば、"map.txt" というテキストファイルを作成して、以下のように入力します。

0,  0,  0, 0,  0,  0
0,  0,  0, 0,  0,  0
0,  5,  7, 0,  0,  0
0, 15, 17, 0,  0,  0
0,  0,  0, 0,  5,  7
0,  0,  0, 0, 15, 17

このテキストのマップデータを読み込んで単体で動かすコードは以下の通りです。

# ①マップデータ読み込み
map_file = open("map.txt")

map = []
for line in map_file:
    # ②一行ずつ読み込み
    # ③"," で文字を区切る
    arr = line.split(",")
    mapdata = []
    for data in arr:
        # ④空白文字を削除
        v = data.strip()
        if v == "":
            # 無効なデータがあればおしまい
            break
        # ⑤マップデータに追加
        mapdata.append(int(v))
    # ⑥1行分を登録
    map.append(mapdata)

print(map)

これを元にマップデータのロード処理を書きます。
まずは、load_map() を実装します。

    def load_map(self, txt):
        # マップ読み込み
        map = []
        map_file = open(txt)
        for line in map_file:
            # 1行ずつ読み込み
            array = []
            data = line.split(",")
            for d in data:
                # 余分な文字を削除
                s = d.strip()
                if s == "":
                    break
                v = int(d.strip())
                array.append(v)
            map.append(array)

        return map

__init__() に読み込み処理を追加します。

    def __init__(self):

        pyxel.init(160, 120, fps=60)
        # マップデータ読み込み
        self.map = self.load_map("map.txt")
        pyxel.image(0).load(0, 0, "tileset.png")
        pyxel.run(self.update, self.draw)

実行して前に表示した状態と変わっていないことを確認します。

■プレイヤー表示

プレイヤー(ニャンコ)を表示します。
まずは、"map.txt" を編集して一番右上に "8" を記入します。

0,  0,  0, 0,  0,  8
0,  0,  0, 0,  0,  0
0,  5,  7, 0,  0,  0
0, 15, 17, 0,  0,  0
0,  0,  0, 0,  5,  7
0,  0,  0, 0, 15, 17

プレイヤーとなるニャンコはマップチップ画像上、 "8" 番目にいるので、 "8" をマップデータに指定することとなります。

8をプレイヤーの開始地点とするために、指定の番号を探す、search_map() を実装します。

    def search_map(self, val):
        # 指定の値が存在する座標を返す
        for j, arr in enumerate(self.map):
            for i, v in enumerate(arr):
                if v == val:
                    # 見つかった
                    return i, j
        
        # 見つからなかったら (-1, -1) を返す
        return -1, -1

さらに、マップに直接値を設定できる、set_map() を実装します。

    def set_map(self, i, j, val):
        # 指定の位置に値を設定する
        self.map[j][i] = val

__init__() でプレイヤーの初期座標を設定する

    def __init__(self):
        pyxel.init(160, 120, fps=60)
        # マップデータ読み込み
        self.map = self.load_map("map.txt")
        # プレイヤーの位置を取得
        self.x, self.y = self.search_map(8)
        # マップデータからプレイヤーを削除
        self.set_map(self.x, self.y, 0)
        pyxel.image(0).load(0, 0, "tileset.png")
        pyxel.run(self.update, self.draw)

draw_player() を実装してプレイヤーを描画します。

    # プレイヤーの描画
    def draw_player(self):
        Map.draw_chip(self.x, self.y, 8)        

そして、draw() でプレイヤー描画関数を呼び出すようにします。

    def draw(self):
        pyxel.cls(0)

        # マップの描画
        self.draw_map()
        # プレイヤーの描画
        self.draw_player()

実行するとプレイヤー(ニャンコ)が描画されるようになりました。

■全てのソースコード

ここまでのコードは以下の通りとなります。

import pyxel
import math # math.floorを使うので必要

class Map:
    SIZE = 8 # チップサイズ
    CHIP_WIDTH = 5 # 1列に5つ並んでいる
    CHIP_HEIGHT = 5 # 5行並んでいる
    
    # マップチップ座標をスクリーン座標に変換
    @classmethod
    def to_screen(cls, i, j):
        return (i * cls.SIZE, j * cls.SIZE)
    
    # マップチップの描画
    @classmethod
    def draw_chip(cls, i, j, val):
        # スクリーン座標に変換
        x, y = cls.to_screen(i, j)
        # チップ画像の座標を計算
        u = (val % cls.CHIP_WIDTH) * cls.SIZE
        v = (math.floor(val / cls.CHIP_WIDTH)) * cls.SIZE
        pyxel.blt(x, y, 0, u, v, cls.SIZE, cls.SIZE, 2)

class App:
    def __init__(self):        
        pyxel.init(160, 120, fps=60)
        # マップデータ読み込み
        self.map = self.load_map("map.txt")
        # プレイヤーの位置を取得
        self.x, self.y = self.search_map(8)
        # マップデータからプレイヤーを削除
        self.set_map(self.x, self.y, 0)
        pyxel.image(0).load(0, 0, "tileset.png")
        pyxel.run(self.update, self.draw)

    def load_map(self, txt):
        # マップ読み込み
        map = []
        map_file = open(txt)
        for line in map_file:
            # 1行ずつ読み込み
            array = []
            data = line.split(",")
            for d in data:
                # 余分な文字を削除
                s = d.strip()
                if s == "":
                    break
                v = int(d.strip())
                array.append(v)
            map.append(array)

        return map

    def search_map(self, val):
        # 指定の値が存在する座標を返す
        for j, arr in enumerate(self.map):
            for i, v in enumerate(arr):
                if v == val:
                    # 見つかった
                    return i, j
    def set_map(self, i, j, val):
        # 指定の位置に値を設定する
        self.map[j][i] = val

    def update(self):
        pass

    def draw(self):
        pyxel.cls(0)

        # マップの描画
        self.draw_map()
        # プレイヤーの描画
        self.draw_player()

    # プレイヤーの描画
    def draw_player(self):
        Map.draw_chip(self.x, self.y, 8)        

    def draw_map(self):
        # 外枠の描画
        pyxel.rectb(0, 0, Map.SIZE*6, Map.SIZE*6, 5)
        
        # 各チップの描画
        for j, arr in enumerate(self.map):
            for i, d in enumerate(arr):
                Map.draw_chip(i, j, d)

App()

■全てのデータのダウンロード

どうしても動作しない方は、以下から今回使用したデータをダウンロードして動作を確認してみてください。
http://syun777.sakura.ne.jp/tmp/pyxel/tilemap.zip

今回はここまでとします。
次回で、ニャンコの移動とゲームクリアを実装します。

続きを書きました。アイテムの回収とゲームクリア処理の実装についてとなります。

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