読者です 読者をやめる 読者になる 読者になる

人工知能に関する断創録

人工知能、認知科学、心理学、ロボティクス、生物学などに興味を持っています。このブログでは人工知能のさまざまな分野について調査したことをまとめています。最近は、機械学習・Deep Learningに関する記事が多いです。



マップチップパレット

Pygame

前回は草地マップでしか描画できませんでしたが、今回はいろんなマップチップが使えるように拡張します。スペースキーを押すと下図のように画面がマップチップパレットに切り替わり、マウスクリックで使いたいマップチップを選択できるようにします。またマップ上で右クリックするとマウスの位置にあるマップチップを選択できます。使えるマップチップはキャラクターチップとマップチップにまとめてあります。

pymap03.zip
f:id:aidiary:20100807174238p:plain

マップチップデータ

これからマップエディタを使っていろんなマップを作っていくわけですがそのためにはどんなマップチップが使えるかきちんと定義しておく必要があります。このRPG編で使うマップチップはキャラクターチップとマップチップ(2008/6/24)にまとめましたが、その情報をファイルに格納したのがmapchip.datです。マップエディタはこのファイルをロードして使えるマップチップやマップチップの上に移動可能かなどをセットします。このファイルはRPG編のバイナリマップのロード(2008/7/5)以降でも共通して使います。mapchip.datは下のようなデータです。1行が1つのマップチップを表しています。左からマップチップID、マップチップ名、移動可能か?、背景を透明色にするか?を表しています。

 0,none,0,0
 1,darkness,1,0
 2,sky,1,0
 3,grass,1,0
 4,flat,1,0
 5,water,0,0
 6,forest,1,0
 7,hill,1,0
 8,mountain,0,0
 9,snow,1,0
 10,desert,1,0

マップチップIDはバイナリマップの各マスに入る値です。後で詳しく説明しますが、バイナリマップは1マス8ビット(1バイト)で表すため0から255までの256種類のマップチップが使えます。今回は55種類なのでまだまだ余裕があります。マップチップ名はファイル名と同じです。マップチップ名に.pngをつけたものがファイル名になります。移動可能か?はプレイヤーが徒歩でそのマップチップに乗れるかを表します。0は移動不可、1は移動可能です。最後の背景を透明色にするか?ですが0のとき透明色を使わない、1のとき使うです。たとえば、草地

f:id:aidiary:20100807173639p:plain

は全部草地の色で塗りつぶされているので背景色を透明にする必要はありません。一方、町

f:id:aidiary:20100807174240p:plain

は背景の茶色がむき出しになっているのでそこは透明にする必要があります。一般的に背景がむき出しになっているマップチップはマップエディタでベタ描きしません。ベタ描きした他のマップチップの上に重ねてイベントとして配置します。草地の上に町を配置すれば草原の町

f:id:aidiary:20100807174241p:plain

に砂漠の上に町を配置すれば砂漠の町

f:id:aidiary:20100807174242p:plain

に見えます。イベントとして配置する方法はRPG編のマップ間移動を参照してください。マップチップのロードはload_mapchips()です。マップチップ名から該当するファイル名を求めて画像をロードし、Map.imagesに格納してます。これはRPG編でも使います。

def load_mapchips(file):
    """マップチップをロードしてMap.imagesに格納"""
    fp = open(file, "r")
    for line in fp:
        line = line.rstrip()  # 改行除去
        data = line.split(",")  # カンマで分解
        id = int(data[0])  # マップチップID
        name = data[1]  # マップチップ名
        movable = int(data[2])  # 移動可能か?(エディタでは未使用)
        Map.images.append(load_image("%s.png" % name))
    fp.close()

マップチップパレット

次にマップチップパレット画面を実装したMapchipPaletteクラスを見ていきます。

class MapchipPalette:
    """マップチップパレット"""
    ROW = 20  # パレットの行数
    COL = 25  # パレットの列数
    COLOR = (0,255,0)  # 緑
    WIDTH = 3  # カーソルの太さ
    def __init__(self):
        self.display_flag = False  # Trueのときパレット表示
        self.selected_mapchip = 3  # 選択しているマップチップ番号
    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)
    def draw(self, screen):
        # パレットを描画
        for i in range(self.ROW * self.COL):
            x = (i % self.COL) * GS
            y = (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 = mouse_pos[0] / GS
        y = mouse_pos[1] / GS
        pygame.draw.rect(screen, self.COLOR,
                         (x*GS,y*GS,GS,GS), self.WIDTH)

ROWとCOLですが、マップチップパレットに配置できるマップチップの数です。この計算でいくと20x25で500個のマップチップが使えることになりますが、実際は1マス8ビットしか使わないので2の8乗で256個までしか使えません。もし256個よりたくさん使いたかったら1マスを16ビットで表現する必要があります。16ビットなら2の16乗で65535種類(多すぎ!)使えます。ここらへんはマップの保存(2008/7/5)で詳しく説明します。

コンストラクタのdisplay_flagは、マップチップパレットの表示、非表示を切り替えるフラグです。これがTrueのときマップチップパレット画面でFalseのときマップ画面になります。selected_mapchipは現在選択中のマップチップIDです。ここで選択されたマップチップでお絵描きできます。

update()はマウスでクリックしたマップチップを選択してselected_mapchipに番号をセットする処理です。セットしたら display_flagをFalseにしてマップチップパレットを閉じています。その後のpygame.time.wait(500)はパレットを閉じた後にそのマップチップですぐ描いてしまうのを防いでいます。コメントアウトしてみるとどうして必要かわかります。ただこれはあまりよい方法ではないと思います・・・

draw()はマップチップを順番に描画しているだけです。画像はMapクラスのimagesにすべて登録してあるのでそれを流用しています。もし登録されていなかったらIndexErrorの例外が起きるのでその場合は

f:id:aidiary:20100807173318p:plain

で描画してます。実際55個しか登録してないので大部分が

f:id:aidiary:20100807173318p:plain

になってますね。

マップとマップチップパレットの切り替え

最後にマップ画面とマップチップパレット画面を切り替えるmain()の処理です。

        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
            elif event.type == KEYDOWN and event.key == K_ESCAPE:
                pygame.quit()
                sys.exit()
            elif event.type == KEYDOWN and event.key == K_SPACE:
                # パレットの表示/非表示を切り替え
                palette.display_flag = not palette.display_flag

スペースキーを押したときpaletteのdisplay_flagを切り替えています。ウィンドウモードとフルスクリーンモードの切り替えでも紹介しましたが

 palette.display_flag = not palette.display_flag

はフラグを切り替えるときの定石です。palette.display_flagがTrueのときはFalseに切り替わり、FalseのときはTrueに切り替わります。

右クリックによるマップチップ抽出

今回は1つ便利な機能を追加しています。マップ画面上で右クリックするとマウス位置にあるマップチップが選択されます。わざわざマップチップパレットで選択する必要がないので非常に便利です。この処理は、Mapクラスのupdate()です。

        elif mouse_pressed[2]:  # 右クリック(マップチップ抽出)
            px, py = pygame.mouse.get_pos()
            selectx = (px + offsetx) / GS
            selecty = (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]

マウスの位置のマップチップ(self.map[selecty][selectx])をpaletteのselected_mapchipに格納してるだけですね。