pygameでRPGをつくってみる５　マップ作成


[記事全体の目次へ移動](https://qiita.com/hijpa/items/056acaa13e935590fdc2)

# お知らせ
　[1.マップエディターが使えるようになりました。](#変更箇所の追加)
　[2.トリミングのエラーを修正しました。](#mapchipをトリミング)
　[3.おおきめmapchipの新規作成を修正しました。](#おおきめmapchip)
　[4.おおきめmapchipのキーハンドラーを修正しました。](#おおきめmapchip)
 
 # 記事の修正
  どうやらオフセットについて書いてるところに誤りがるようです。
  mapだけ偶数なら画面は奇数でもよいのではないかと、考え直したのですが、検証したところやっぱり画面も偶数にしないと隙間ができてしまいます。なぜなのかさっぱりわかりません。

# GIF

<details><summary>ここをクリックしてください</summary>

![無題の動画 ‐ Clipchampで作成 (2).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3874121/5b82c272-4c0e-211a-d7c7-f8cdfc1b8e07.gif)


</details>

# 変更箇所の追加

マップエディターが使えるようになりました。
そのままでは動かないので変更を加えてあります。

<details><summary>ここをクリックしてください</summary>

aidiaryさんのコードを紹介します。
[マップエディター　ここからダウンロード](https://aidiary.hatenablog.com/entry/20080705/1281171879)


　ln76 ln89
 print()なので、()をつけます。
 以下は変更したコードです。
 
 ```python
            elif event.type == KEYDOWN and event.key == K_n:
                # 新規マップ
                try:
                    name = input_wnd.ask(screen, "NAME?")
                    row = int(input_wnd.ask(screen, "ROW?"))
                    col = int(input_wnd.ask(screen, "COL?"))
                    default = int(input_wnd.ask(screen, "DEFAULT?"))
                except ValueError:
                    print ("Cannot create map")#ここ
                    continue
                map = Map(name, row, col, default, palette)
            elif event.type == KEYDOWN and event.key == K_s:
                # マップをセーブ
                name = input_wnd.ask(screen, "SAVE?")
                map.save(name)
            elif event.type == KEYDOWN and event.key == K_l:
                # マップをロード
                try:
                    name = input_wnd.ask(screen, "LOAD?")
                    map.load(name)
                except IOError:
                    print ("Cannot load: %s") % file#ここ
                    continue
 
```

ln136 ln137 ln145 ln146 ln153 ln154 ln155 ln156 に
int()をつけます。

```python
    def update(self, offset):
        offsetx, offsety = offset
        mouse_pressed = pygame.mouse.get_pressed()
        if mouse_pressed[0]:  # 左クリック（マップチップ描画）
            # マウスが返すのはローカル座標
            px, py = pygame.mouse.get_pos()
            # 全体マップ上での座標はoffsetを足せばよい
            # GSで割るのはピクセルをマスに直すため
            selectx = int((px + offsetx) / GS)#ここ
            selecty = int((py + offsety) / GS)#ここ
            # マップ範囲外だったら無視
            if selectx < 0 or selecty < 0 or selectx > self.col-1 or selecty > self.row-1:
                 return
            # パレットで選択中のマップチップでマップを更新
            self.map[selecty][selectx] = self.palette.selected_mapchip
        elif mouse_pressed[2]:  # 右クリック（マップチップ抽出）
            px, py = pygame.mouse.get_pos()
            selectx = int((px + offsetx) / GS)#ここ
            selecty = int((py + offsety) / GS)#ここ
            if selectx < 0 or selecty < 0 or selectx > self.col-1 or selecty > self.row-1:
                 return
            self.palette.selected_mapchip = self.map[selecty][selectx]
    def draw(self, screen, offset):
        offsetx, offsety = offset
        # マップの描画範囲を計算
        startx = int(offsetx / GS)#ここ
        endx = int(startx + SCR_RECT.width/GS + 2)#ここ
        starty = int(offsety / GS)#ここ
        endy = int(starty + SCR_RECT.height/GS + 2)#ここ
        # マップの描画
        for y in range(starty, endy):
            for x in range(startx, endx):
                # マップの範囲外はマップチップ番号0で描画
                if x < 0 or y < 0 or x > self.col-1 or y > self.row-1:
                    screen.blit(self.images[0], (x*GS-offsetx,y*GS-offsety))
                else:
                    screen.blit(self.images[self.map[y][x]], (x*GS-offsetx,y*GS-offsety))
                    if show_grid:
                        pygame.draw.rect(screen, (0,0,0), (x*GS-offsetx,y*GS-offsety,GS,GS), 1)

```

ln213 ln214
/ を // にします。

```python
    def update(self):
        # マップチップパレットの選択
        mouse_pressed = pygame.mouse.get_pressed()
        if mouse_pressed[0]:  # 左クリック
            # マウス座標を取得
            mouse_pos = pygame.mouse.get_pos()
            # マス座標に変換
            x = mouse_pos[0] // GS#ここ
            y = mouse_pos[1] // GS#ここ
            # マップチップ番号に変換
            n = y * self.COL + x
            if n < len(Map.images) and Map.images[n] != None:
                self.selected_mapchip = n
                self.display_flag = False  # パレットを消す
                # パレットが消えた直後にマップチップを描画してしまうのを防ぐ
                pygame.time.wait(500)

```

ln244 ln245 ln246 ln249
()をつけたり、isを==に変えたりします。

```python
def load_image(dir, file, colorkey=None):
    file = os.path.join(dir, file)
    try:
        image = pygame.image.load(file)
    except (pygame.error, message):#ここ
        print ("Cannot load image:"), file#ここ
        raise (SystemExit, message)#ここ
    image = image.convert()
    if colorkey is not None:
        if colorkey == -1:#ここ
            colorkey = image.get_at((0,0))
        image.set_colorkey(colorkey, RLEACCEL)
    return image

```

ln295
()をつけます。

```python
    def draw_character(self, screen, pos, ch):
        """1文字だけ描画する"""
        x, y = pos
        try:
            rect = self.kana2rect[ch]
            screen.blit(self.image, (x,y), (rect.x+self.color,rect.y,rect.width,rect.height))
        except KeyError:
            print ("描画できない文字があります:%s") % ch#ここ
            return

```
</details>


# マップ作成  
 
 マップ作成について書いていこうと思います。

 その前に、いつものことですがaidiaryさんのコードを紹介させていただきます。aidiaryさんがマップエディターを作ってくださっているのでこれを利用するのが一番手っ取り早いです。

 [マップエディター　ここからダウンロード](https://aidiary.hatenablog.com/entry/20080705/1281171879)

 ちょっと気になる点があるので変更したいと思います。

# 変更個所

<details><summary>ここをクリックしてください</summary>
ln 226

class MapchipPalette:　にある　def draw(self,screen)で
y = int((i / self.COL)* GS)の / を // にします。

mapchipが右下にずれていくのが変更されます。

![スクリーンショット (668).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3874121/4ce2d58e-915b-551a-6fdd-75e3bcc170ec.png)


```python:
    def draw(self, screen):
        # パレットを描画
        for i in range(self.ROW * self.COL):
            x = int((i % self.COL) * GS)
            y = int((i //self.COL) * GS)#ここ
            image = Map.images[0]
            try:
                if Map.images[i] != None:
                    image = Map.images[i]
            except IndexError:  # イメージが登録されてないとき
                image = Map.images[0]
            screen.blit(image, (x,y))
        # マウスの位置にカーソルを描画
        mouse_pos = pygame.mouse.get_pos()
        x = int(mouse_pos[0] / GS)
        y = int(mouse_pos[1] / GS)
        pygame.draw.rect(screen, self.COLOR, (x*GS,y*GS,GS,GS), self.WIDTH)


```
左側にある黒い隙間を埋めたいと思います。

![スクリーンショット (669).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3874121/b25390d3-3b34-1297-1ef7-defa10913e9a.png)

この隙間は何でしょうか？
オフセットという考え方と関係があります。
オフセットはちょっとややこしいので、いまは画面の真ん中にキャラクターを描画するということだけで考えましょう。(ここでは緑色の枠です)

キャラクターを真ん中に描画するにはどうすればよいでしょうか。
画面を２で割ると中心が分かりそうです。
128x128の画面で試してみましょう。
図にすると以下の通りです。

![white.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3874121/2fd4223d-d75b-f571-9b83-daac1e904982.png)

おや？真ん中にいませんね。
そもそも中心となるマスが存在しません。
96x96の画面ではどうでしょう。

![white - コピー - コピー (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3874121/b7016a4e-f067-6332-5035-17e6ef191e19.png)

中心となるマスは存在しますが、マスからずれてますね。
何を隠そうこの16ピクセルのずれこそが、あの黒い隙間なのです。

結局のところ、どうやったら黒い隙間を消せるでしょうか。
やり方は３つあります。

ひとつめは、画面のサイズとマップのサイズを偶数にします。
たとえば、マップエディターは800x640でよこが奇数なので、832x640。25x20のマップを26x16。(800x640は偶数でありますが、32x32のマスに直すと25x20でよこが奇数になります)

ふたつめは、class Map: の def draw　を変更します。
たとえば、マップエディターは800x640でよこが奇数なので、startxとendxを変更。(-1 と +1 を追加)
    def draw(self, screen, offset):
        """マップを描画する"""
        offsetx, offsety = offset
        # マップの描画範囲を計算
        startx = int(offsetx / GS) - 1
        endx = int(startx + SCR_RECT.width/GS + 1) + 1
        starty = int(offsety / GS)
        endy = int(starty + SCR_RECT.height/GS + 1)

みっつめは、オフセットを変更します。
たとえば、マップエディターは800x640でよこが奇数なので、offsetxを変更。(奇数の側に　-16)
def calc_offset(cursor):
    """cursorを中心としてオフセットを計算する"""
    offsetx = cursor.rect.topleft[0] - SCR_RECT.width/2
    offsetx = offsetx - 16
    offsety = cursor.rect.topleft[1] - SCR_RECT.height/2
    return offsetx, offsety

根本的に変えるなら、画面とマップを奇数にしてオフセットを変更します。

ぼくはオフセットを全然わかってないので、aidiaryさんの説明を読んでください。

[オフセットの説明　ここから移動](https://aidiary.hatenablog.com/entry/20080607/1275790963)

</details>

# 8bitから16bitへ

<details><summary>ここをクリックしてください</summary>

マップにこだわるとmapchipがたくさん必要になります。
しかし、8bitだと256種類しか使えません。ぴぽや倉庫さんのmapchipはかるく2000を超えます。たくさんのmapchipを使うには16bitにする必要があります。def save と def load の"B"を"H"に変更します。save側に2か所、load側には4か所あります。
　以下のコードは "B" を "H" にしたコードです。
```python:
    def save(self, name):
        """マップをバイナリ形式でfileに保存"""
        file = "%s.map" % (name.lower())
        fp = open(file, "wb")  # バイナリモードで開く
        # 行数、列数はi(int)で保存
        fp.write(struct.pack("i", self.row))
        fp.write(struct.pack("i", self.col))
        # マップチップはB(unsigned char)で保存
        # 8bitなのでマップチップは256種類まで
        fp.write(struct.pack("H", self.default))
        for r in range(self.row):
            for c in range(self.col):
                fp.write(struct.pack("H", self.map[r][c]))
        fp.close()
    def load(self, name):
        """バイナリ形式のマップを読み込む"""
        file = "%s.map" % (name.lower())
        fp = open(file, "rb")
        self.name = name
        # unpack()はタプルが返されるので[0]だけ抽出
        self.row = struct.unpack("i", fp.read(struct.calcsize("i")))[0]
        self.col = struct.unpack("i", fp.read(struct.calcsize("i")))[0]
        self.default = struct.unpack("H", fp.read(struct.calcsize("H")))[0]
        self.map = [[self.default for c in range(self.col)] for r in range(self.row)]
        for r in range(self.row):
            for c in range(self.col):
                self.map[r][c] = struct.unpack("H", fp.read(struct.calcsize("H")))[0]
        fp.close()

```

8bitを16bitに変えただけでは使用できません。画面の範囲しか使用できないからです。
オフセットを追加しましょう。
元のコードのdef main():から while True: のなかにある   palette.draw(screen)までを消して、以下のコードを貼り付けましょう。

```python
def main():
    pygame.init()
    screen = pygame.display.set_mode(SCR_RECT.size)
    pygame.display.set_caption(u"PyMap 07 マップの読み込み")
    
    # マップチップをロード
    load_mapchips("data", "mapchip.dat")
    
    palette = MapchipPalette(25,50)
    map = Map("NEW", 20, 25, 5, palette)
    cursor = Cursor(5, 5)
    msg_engine = MessageEngine()
    input_wnd = InputWindow(INPUT_RECT, msg_engine)
    
    clock = pygame.time.Clock()
    while True:
        clock.tick(60)
        if palette.display_flag:  # パレットが表示中なら
            offset = calc_offset(cursor)
            palette.update(offset)
            cursor.update()
            palette.draw(screen,offset)
            cursor.draw(screen, offset)

```

元のコードのclass MapchipPalette:から        pygame.draw.rect(screen, self.COLOR, (x*GS,y*GS,GS,GS), self.WIDTH)
までを消して、以下のコードを貼り付けます。

```python
class MapchipPalette:
    """マップチップパレット"""
    COL = 25  # パレットの列数
    ROW = 50  # パレットの行数
    COLOR = (0,255,0)  # 緑
    WIDTH = 3  # カーソルの太さ
    def __init__(self,col,row):
        self.display_flag = False  # Trueのときパレット表示
        self.selected_mapchip = 3  # 選択しているマップチップ番号
        self.col = col
        self.row = row
    def update(self,offset):
        offsetx, offsety = offset
        # マップチップパレットの選択
        mouse_pressed = pygame.mouse.get_pressed()
        if mouse_pressed[0]:  # 左クリック
            px, py = pygame.mouse.get_pos()
            # 全体マップ上での座標はoffsetを足せばよい
            # GSで割るのはピクセルをマスに直すため
            selectx = int((px + offsetx) / GS)
            selecty = int((py + offsety) / GS)
            # マップチップ番号に変換
            n = selecty * self.COL + selectx
            if n < len(Map.images) and Map.images[n] != None:
                self.selected_mapchip = n
                self.display_flag = False  # パレットを消す
                # パレットが消えた直後にマップチップを描画してしまうのを防ぐ
                pygame.time.wait(500)
    def draw(self, screen,offset):
        offsetx, offsety = offset
        # マップの描画範囲を計算
        startx = int(offsetx / GS) - 1
        endx = int(startx + SCR_RECT.width/GS + 1) +1
        starty = int(offsety / GS) -1
        endy = int(starty + SCR_RECT.height/GS + 1) +1
        sky = load_image("mapchip","sky.png")
        # マップの描画
        for y in range(starty, endy):
            for x in range(startx, endx):
                # マップの範囲外はマップチップ番号0で描画
                if x < 0 or y < 0 or x > self.col-1 or y > self.row-1:
                    screen.blit(sky, (x*GS-offsetx,y*GS-offsety))
        # パレットを描画
        for i in range(self.ROW * self.COL):
            x = int((i % self.COL) * GS) - offsetx
            y = int((i // self.COL) * GS) - offsety
            image = Map.images[0]
            try:
                if Map.images[i] != None:
                    image = Map.images[i]
            except IndexError:  # イメージが登録されてないとき
                image = Map.images[0]
            screen.blit(image, (x,y))

```

変更が終わったら、パレットのサイズを大きくしましょう。mapchipの数より、パレットが小さいと、すべてのmapchipが表示されません。とりあえず、たてを５０にすると
よいでしょう。

palette = MapchipPalette(50,25)
(たて、よこ)
よくある順番とは違うので気をつけましょう。

class MapchipPalette:
    """マップチップパレット"""
    ROW = 50  # パレットの行数
    COL = 25  # パレットの列数

MapchipPaletteの値と違うと変になるので気をつけましょう。
サイズが合ってないと、下の画像のようになります。

![スクリーンショット (709).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3874121/0ecf0ffb-5996-c0f8-449b-721fd6a23629.png)

上の画像は、５０と２０です。(下は間違ったコードです)

palette = MapchipPalette(50,25)
class MapchipPalette:
    """マップチップパレット"""
    ROW = 20  # パレットの行数
    COL = 25  # パレットの列数

</details>

# 作業中の座標を記録する

<details><summary>ここをクリックしてください</summary>

広いマップで作業していると、マップパレットの座標とマップの座標が遠く離れることがあります。この距離を行ったり来たりするのは面倒ですね。作業中の座標を記録しましょう。

このコードを貼り付けるには、今までの変更をしていることが条件です。


import の下に
以下のコードを貼り付けます。
```python:
pos_list = [0,0]

```


whileから
cursor.draw(screen, offset)
を消して以下のコードを貼り付けます。
```python:
    while True:
        clock.tick(60)
        if palette.display_flag:  # パレットが表示中なら
            offset = calc_offset(cursor)
            palette.update(offset,cursor)
            cursor.update()
            palette.draw(screen,offset)
            cursor.draw(screen, offset)

```

elif event.type == KEYDOWN and event.key == K_SPACE:
#パレットの表示/非表示を切り替え
palette.display_flag = not palette.display_flag
の部分を消して、以下のコードを貼り付けます。

```python:
                elif event.type == KEYDOWN and event.key == K_SPACE:
                    # パレットの表示/非表示を切り替え
                    palette.display_flag = not palette.display_flag
                    if palette.display_flag:
                        pos_list[0] = cursor.x
                        pos_list[1] = cursor.y
                        cursor.x = 10
                        cursor.y = 10
                    if not palette.display_flag:
                        cursor.x = pos_list[0]
                        cursor.y = pos_list[1]

```

class MapchipPalette:にある
def update(self,offset):をすべて消して
以下のコードを貼り付けます。


```python:
    def update(self,offset,cursor):
        offsetx, offsety = offset
        # マップチップパレットの選択
        mouse_pressed = pygame.mouse.get_pressed()
        if mouse_pressed[0]:  # 左クリック
            px, py = pygame.mouse.get_pos()
            # 全体マップ上での座標はoffsetを足せばよい
            # GSで割るのはピクセルをマスに直すため
            selectx = int((px + offsetx) / GS)
            selecty = int((py + offsety) / GS)
            # マップチップ番号に変換
            n = selecty * self.col + selectx
            if n < len(Map.images) and Map.images[n] != None:
                self.selected_mapchip = n
                self.display_flag = False  # パレットを消す
                if not self.display_flag:
                    cursor.x = pos_list[0]
                    cursor.y = pos_list[1]

```

</details>

# mapchipをトリミング

　背景が透明の画像をトリミングしようとすると、
 IndexError: index 3 is out of bounds for axis 2 with size 3
というエラーがでていました。
　分岐をつけて、背景が透明でも、透明でなくても対応できるように修正してあります。

<details><summary>ここをクリックしてください</summary>
 
　ぴぽや倉庫さんのマップチップで大きいのは、32*32にすると2000くらいあります。
これを手作業でトリミングするのはたいへんです。
pythonのコードでトリミングしましょう。

# トリミングのコード

:::note alert
　このコードを実行すると入力画面が出ますが、ファイルのパスに""(ダブルクォーテーション)は必要ありません。面倒ですが消してください。
　もちろんこのコード内の""(ダブルクォーテーション)を消してはいけません。
:::

以下はコードを実行した際の入力画面です。
すべて表示されてますが、実際は順に表示されていきます。

![スクリーンショット (670).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3874121/17b8381d-526b-1052-517d-665c83c43873.png)


```python:

import cv2
import numpy as np
file_path =  input("file_path: ")
file_name = input("file_name: ")
num = input("num: ")
# 元画像のパスを指定(ex)E:\makemap\test2),引用符なし
input_img = cv2.imread(f"{file_path}", cv2.IMREAD_UNCHANGED)

# タイルのサイズ
GS = 32

# 元画像の高さ、幅を取得
height, width = input_img.shape[:2]

# タイルの数を計算
num_tiles_y = height // GS
num_tiles_x = width // GS

# 背景色を指定（透明）
background_color = (0, 0, 0, 0)

for y in range(num_tiles_y):
    for x in range(num_tiles_x):
        # トリミングする範囲を指定
        output_img = input_img[y*GS:(y+1)*GS, x*GS:(x+1)*GS]

        # 画像にアルファチャンネルがあるか確認
        if output_img.shape[2] == 4:
            # 背景色を設定
            bg_img = np.full((GS, GS, 4), background_color, dtype=np.uint8)
            bg_img[0:output_img.shape[0], 0:output_img.shape[1], :3] = output_img[:, :, :3]
            bg_img[0:output_img.shape[0], 0:output_img.shape[1], 3] = output_img[:, :, 3]
        else:
            bg_img = output_img

        # ファイル名
        filename = f'{file_name}{num}.png'

        # トリミングした画像を新しいファイルに保存
        cv2.imwrite(filename, bg_img)
        num = int(num)
        num += 1
        num = str(num)
print("完了しました")


```
</details>

# mapchipの拡大

<details><summary>ここをクリックしてください</summary>

　ぴぽや倉庫さんのmapchipは32x32と16x16では微妙に種類が違ったりします。32x32には雪山とかがありません。
　使えないのはもったいないので拡大して使えるようにします。

以下のコードはフォルダ内のすべての画像ファイルを２倍にして、トリミングします。

![スクリーンショット (673).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3874121/385ee11b-8147-5a63-2cc3-c823e5ef58f6.png)

フォルダのアイコンをクリックして、パスをショートカットキーでコピーすると""(ダブルクォーテーション)がつかないです。
以下のコードでは""(ダブルクォーテーション)が必要ないので、この方法を使った方が良いです。
(ファイルのあるところでフォルダのアイコンをクリックしなきゃダメです。パスをコピーのアイコンだとファイルがあるのより上の階層ですよね)


![スクリーンショット (674).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3874121/456f5ae8-d192-d3ca-cf7d-37e798d2e781.png)

パスのコピーをクリックすると、""(ダブルクォーテーション)がつきます。

```python:
import os
import cv2
import numpy as np

# フォルダのパスを指定
folder_path = input("directory: ")
file_name = input("file_name: ")
num = input("num: ")

# フォルダ内のファイル一覧を取得
file_list = os.listdir(folder_path)
# ファイル名を変更
for i, old_file_name in enumerate(file_list):
    if old_file_name.endswith('.png'):
        # フルパスを作成
        img_path = os.path.join(folder_path, old_file_name)

        img = cv2.imread(img_path, cv2.IMREAD_UNCHANGED)

        # 拡大するサイズを指定（幅, 高さ）
        size = (32, 160)

        # 画像を拡大
        img_resized = cv2.resize(img, size, interpolation=cv2.INTER_NEAREST)


        # タイルのサイズ
        GS = 32

        # 元画像の高さ、幅を取得
        height, width = img_resized.shape[:2]

        # タイルの数を計算
        num_tiles_y = height // GS
        num_tiles_x = width // GS

        # 背景色を指定（透明）
        background_color = (0, 0, 0, 0)

        for y in range(num_tiles_y):
            for x in range(num_tiles_x):
                # トリミングする範囲を指定
                output_img = img_resized[y*GS:(y+1)*GS, x*GS:(x+1)*GS]


                # 背景色を設定
                bg_img = np.full((GS, GS, 4), background_color, dtype=np.uint8)
                bg_img[0:output_img.shape[0], 0:output_img.shape[1], :3] = output_img[:, :, :3]
                bg_img[0:output_img.shape[0], 0:output_img.shape[1], 3] = output_img[:, :, 3]

                # ファイル名
                filename = f'{file_name}{num}.png'

                # トリミングした画像を新しいファイルに保存
                cv2.imwrite(filename, bg_img)
                num = int(num)
                num += 1
                num = str(num)
        print("完了しました")
        


```
</details>


# マップ範囲外もdefaultで描画

<details><summary>ここをクリックしてください</summary>

マップ範囲外は黒に赤い✖で描画されていますが、これだとちょっと完成図が想像しづらいです。マップの範囲外もdefaultで描画するように変更しましょう。

class Map:にあるscreen.blit(self.images[0]をscreen.blit(self.images[self.default]に変更します。

![スクリーンショット (707) - コピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3874121/3e527f9b-73f1-2706-0996-582dc73b3c2d.png)

変更後のコードです。
```python:

    def draw(self, screen, offset):
        offsetx, offsety = offset
        # マップの描画範囲を計算
        startx = int(offsetx / GS)
        endx = int(startx + SCR_RECT.width/GS + 1)
        starty = int(offsety / GS)
        endy = int(starty + SCR_RECT.height/GS + 1)
        # マップの描画
        for y in range(starty, endy):
            for x in range(startx, endx):
                # マップの範囲外もdefaultで描画
                if x < 0 or y < 0 or x > self.col-1 or y > self.row-1:
                    screen.blit(self.images[self.default], (x*GS-offsetx,y*GS-offsety))
                else:
                    screen.blit(self.images[self.map[y][x]], (x*GS-offsetx,y*GS-offsety))
                    if show_grid:
                        pygame.draw.rect(screen, (0,0,0), (x*GS-offsetx,y*GS-offsety,GS,GS), 1)


```
</details>

# defaultを変更

<details><summary>ここをクリックしてください</summary>

新規マップ作成の時に設定したdefaultが何か違うなぁ、って思うときがあります。
設定した後でも変更できるようにしましょう。
キーハンドラーに付け加えます。
(事前に、マップ範囲外もdefaultで描画の変更をおこなっておいてください)

```python:

                elif event.type == KEYDOWN and event.key == K_z:
                    map.default = int(input_wnd.ask(screen, "DEFAULT?"))


```

event.type == KEYDOWNは一回で済ますこともできます。
(以下のコードは動かすことを想定してません。見るだけです)

```python:
if event.type == KEYDOWN:
    if event.key == K_z:
     pass
    elif event.key == K_x:
     pass
```

</details>

# MapchipPaletteでID番号を表示します。
オフセットの変更をおこなっていることが条件です。
(オフセットの変更は "8bitから16bitへ"に書いてあります)
[8bitから16bitへ](#8bitから16bitへ)

<details><summary>ここをクリックしてください</summary>

importの下に以下のコードを追加します。

```python:
pygame.init()
#ここにフォントのパスを貼り付ける
font = pygame.font.Font(パス, 30)
n = 0

```

class MapchipPalette:の def draw(self, screen,offset):に以下のコードを追加します。

![スクリーンショット (708) - コピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3874121/1ec7d9e4-03dc-8352-5b90-08d7a73125c7.png)


```python:
        px, py = pygame.mouse.get_pos()
        # 全体マップ上での座標はoffsetを足せばよい
        # GSで割るのはピクセルをマスに直すため
        selectx = int((px + offsetx) / GS)
        selecty = int((py + offsety) / GS)
        # マップチップ番号に変換
        n = selecty * self.col + selectx
        text = font.render(str(n),True,(255,255,255))
        screen.blit(text,(32,32))

```

</details>

# おおきめmapchip

* マップを新規作成しようとすると、引数が４つ足りないエラーが出ていました。
TypeError: Map.__init__() missing 4 required positional arguments: 'big_mapchip1', 'big_mapchip2', 'big_mapchip3', and 'big_mapchip4'
　キーハンドラーのマップを新規作成する箇所に変更を加えました。
 map = Map(name, row, col, default, palette,0,0,0,0)
 0を４つ足しています。

* 入力するところでエラーが出ていました。(キーハンドラー)


<details><summary>ここをクリックしてください</summary>

使い方
マップでBキーを押すと、４回、ID番号を聞かれるので順に入力しましょう。

数字は入力の順番を表しています。

![順番.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3874121/6ee1d47f-dc90-1230-83b3-0583ee5ce100.png)

元に戻すときは、もう一度Bキーを押します。

*****
ひとマスずつ埋めていくのは大変なので、大きめのmapchipを作ります。

def mainのところです。(big_mapchipはdef mainの上です。このコードをそのままdef mainの下に貼り付けたらうまくいかないです。気をつけましょう。)

```python:
mapchip_size = False
big_mapchip1 = 0
big_mapchip2 = 0
big_mapchip3 = 0
big_mapchip4 = 0
def main():
    global mapchip_size,big_mapchip1,big_mapchip2,big_mapchip3,big_mapchip4
    pygame.init()
    screen = pygame.display.set_mode(SCR_RECT.size,DOUBLEBUF|HWSURFACE)
    pygame.display.set_caption("大きめmapchip")
    
    # マップチップをロード
    load_mapchips("data", "mapchip.dat")
    
    palette = MapchipPalette(50,20)
    map = Map("NEW", 20, 25, 5, palette,0,0,0,0)
    cursor = Cursor(5, 5)
    msg_engine = MessageEngine()
    input_wnd = InputWindow(INPUT_RECT, msg_engine)
    
    clock = pygame.time.Clock()

```

キーハンドラーで新規マップ作成するところ変更します。
map = Map(name, row, col, default, palette,0,0,0,0)
0を４つ足しています。

```python:
                elif event.key == K_n:
                    # 新規マップ
                    try:
                        name = input_wnd.ask(screen, "NAME?")
                        row = int(input_wnd.ask(screen, "ROW?"))
                        col = int(input_wnd.ask(screen, "COL?"))
                        default = int(input_wnd.ask(screen, "DEFAULT?"))
                    except ValueError:
                        print ("Cannot create map")
                        continue
                    map = Map(name, row, col, default, palette,0,0,0,0)

```

キーハンドラーに付け加えます。
(AttributeError: 'pygame.event.Event' object has no attribute 'key'
というエラーが出たら、event.key == K_b　の前に　event.type == KEYDOWN andを
つけましょう。)
コードを変更しました。数値を入力するときに間違って存在するID番号以上を入力したり、アルファベットを入力したり、空白を入力するとエラーが出てました。(ぼくは存在するID番号以上の数値を入力したため、それまで描いていたマップのデータが消えちゃいました)

```python:
                elif event.key == K_b:
                    mapchip_size = not mapchip_size
                    if mapchip_size:
                        map.big_mapchip1 = input_wnd.ask(screen, "ID_NUMBER?")
                        try:
                            map.big_mapchip1 = int(map.big_mapchip1)
                        except ValueError:
                            map.big_mapchip1 = 0  # 型変換に失敗した場合、デフォルト値を設定
                        if not (0 <= map.big_mapchip1 < len(map.images)):
                            map.big_mapchip1 = 0  # 有効な範囲外の場合、デフォルト値を設定
                        map.big_mapchip2 = input_wnd.ask(screen, "ID_NUMBER?")
                        try:
                            map.big_mapchip2 = int(map.big_mapchip2)
                        except ValueError:
                            map.big_mapchip2 = 0  # 型変換に失敗した場合、デフォルト値を設定
                        if not (0 <= map.big_mapchip2 < len(map.images)):
                            map.big_mapchip2 = 0  # 有効な範囲外の場合、デフォルト値を設定
                        map.big_mapchip3 = input_wnd.ask(screen, "ID_NUMBER?")
                        try:
                            map.big_mapchip3 = int(map.big_mapchip3)
                        except ValueError:
                            map.big_mapchip3 = 0  # 型変換に失敗した場合、デフォルト値を設定
                        if not (0 <= map.big_mapchip3 < len(map.images)):
                            map.big_mapchip3 = 0  # 有効な範囲外の場合、デフォルト値を設定
                        map.big_mapchip4 = input_wnd.ask(screen, "ID_NUMBER?")
                        try:
                            map.big_mapchip4 = int(map.big_mapchip4)
                        except ValueError:
                            map.big_mapchip4 = 0  # 型変換に失敗した場合、デフォルト値を設定
                        if not (0 <= map.big_mapchip4 < len(map.images)):
                            map.big_mapchip4 = 0  # 有効な範囲外の場合、デフォルト値を設定
                    

```

class Mapのところです。

```python:
class Map:
    images = []
    def __init__(self, name, row, col, default, palette,big_mapchip1,big_mapchip2,big_mapchip3,big_mapchip4):
        self.name = name
        self.row = row
        self.col = col
        self.default = default  # デフォルトのマップチップ番号
        self.map = [[self.default for c in range(self.col)] for r in range(self.row)]
        self.palette = palette
        self.big_mapchip1 = big_mapchip1
        self.big_mapchip2 = big_mapchip2
        self.big_mapchip3 = big_mapchip3
        self.big_mapchip4 = big_mapchip4

```

ここもclass Mapです。def updateのところです。

```python:
            # パレットで選択中のマップチップでマップを更新
            if not mapchip_size:
                    self.map[selecty][selectx] = self.palette.selected_mapchip
            if mapchip_size:
                if selectx < 0 or selecty < 0 or selectx > self.col- 2 or selecty > self.row-2:
                    return
                self.map[selecty][selectx] = self.big_mapchip1
                self.map[selecty + 1][selectx] = self.big_mapchip2
                self.map[selecty][selectx +1] = self.big_mapchip3
                self.map[selecty +1][selectx +1] = self.big_mapchip4

```



</details>
#

<details><summary>ここをクリックしてください</summary>

```python:

```
</details>