これまでは一つの面だけを表示しましたが、複数の面から選択して表示できるようにしたいですね。この場合、クラス化した方があとあと楽になりますので、Mapクラスとして作成します。複数の面データを読み込んで配列に格納し、その配列から目的の面データを読み込んでキャラクタ配列に格納することになります。そこで二次元の配列 mapdata[][]を用意します。第1面の第1行目の文字列がmapdata[0][0]に格納されることになり、len(mapdata)が面の数を、len(mapdata[n])がn番目の面の行数を表すことになります。
キャラクタ画像はmapdata[][]にヒモ付けられるので、Mapクラスで読み込むのが自然です。なのでモジュールloadImage.pyはMapクラスでimportすることにします。また各キャラクタの座標配列もmapdata[][]に関連していますので、Mapクラスのインスタンス変数にしましょう。なのでMapクラスのコンストラクタまでは以下のようなコードになるでしょう。このコードをtemp.pyとしてsoukoフォルダに保存します。
import pygame from pygame.locals import * import sys sys.path.append("C:\myPython\module") import loadimage as LI SCR_RECT = Rect(0, 0, 800, 480) GS = 32 class Map: pygame.init() screen = pygame.display.set_mode(SCR_RECT.size) blockImg = LI.load_image("images/block.png", -1) bagImg = LI.load_image("images/bag.png", -1) placeImg = LI.load_image("images/place.png", -1) storedImg = LI.load_image("images/stored.png", -1) playerImg = LI.load_image("images/player.png", -1) def __init__(self): self.blocks = [] # 壁座標(tuple)の List self.places = [] # 収納場所座標(tuple)の List self.bags = [] # 荷物座標(tuple)の List self.stored = [] # 格納座標(tuple)の List self.player = [] # プレーヤー座標(tuple)の List、要素数は1
ネットにはたくさんの倉庫番用マップデータが公開されています。しかし著作権の問題がありますので、どれを利用してもよいわけではありません。そこでDavid W. Skinnerさん作のここからダウンロ-ドした「Microban」というマップデータを使います。このマップデータは初心者向けのもので155面までありますが、154面と155面を除いてあります。このファイルもsoukoフォルダに保存してください。
マップデータは、テキストファイルとしてread & writeができます。しかしmapdata[][]には面番号など不要のデータも含まれ、単純に全部の行を読むだけでは、mapdata[][]を作成することはできません。そこでmapdata[][]に必要な行には必ず「#」(壁)があることに着目し、以下のメソッドgetData()を作成しました。
001: def getData(self): # self.mapdataの作成 002: flag = [] # '#'がある行は'0' mapdataに不要な行は'1' 003: f = open(self.filename, 'r') 004: while(True): # 一行ずつ読んで listに格納 005: line = f.readline() 006: if '#' in line: 007: flag.append('0') 008: else: 009: flag.append('1') 010: if not line: 011: break 012: f.close() 013: max = 0 014: # 面数を取得して初期化 015: for i in range(len(flag)): 016: if flag[i] == '0' and flag[i+1] == '1': # 不要行の次に'#'を含む行があれば 017: max += 1 018: self.mapdata = [[] for _ in range(max)] # mapdata[][]の初期化 019: 020: f = open(self.filename, 'r') 021: lines = f.readlines() # 全行の list 022: f.close 023: n = 0 024: for i in range(len(lines)): # 全行を調べる 025: if '#' in lines[i]: 026: self.mapdata[n].append(lines[i].rstrip('\n')) # 改行を削除して配列に追加 027: if flag[i] == '0' and flag[i+1] == '1': # 不要行の次に'#'を含む行があれば 028: n += 1 # mapdata[n++]
mapdata[][]を作成するに当たり、18行目を書かなかったらIndexError: list index out of rangeと怒られました。そのためまず面数を取得する必要がありました。そのコードが2-17行です。18行で面数を含めて初期化しています。
マップデータによって不要行の数が異なる場合があります。しかし通常は、「#」を含む必要行がある行数だけ連続した後に不要行があり、その後に次の面の必要行が続きます。このようなデータから必要行だけを抜き出さなければなりません。色んな方法を試しましたが、なかなか上手く処理できませんでした。そこで必要行と不要行を区別する配列flag[]を作成しました。4-11行でflag[]を作成し、15-17行で不要行の次に「#」を含む行がある場合に面数を+1しています。
flag[]の作成にはreadline()を使って一行ずつ読んで処理しましたが、20-28行ではreadlines()を使って全行をリストにして処理しています。ここでもflag[]を利用することができました。これで[n]を面番号とし、[m]を行番号とするインデックスをもち、「### $ ###」などのstr型の行データを格納したmapdata[n][m]を作成できます。もっと良い方法があると思いますが、Gの頭ではこの方法しか思いつきませんでした。temp.pyにこのgetData()メソッドを実装したものをmapStage.pyと名前を付け、soukoフォルダに保存します。
ファイル名:mapStage.py 001:import pygame 002:from pygame.locals import * 003:import sys 004:sys.path.append("C:\myPython\module") 005:import loadimage as LI 006: 007:SCR_RECT = Rect(0, 0, 800, 480) 008:GS = 32 009: 010:class Map: 011: pygame.init() 012: screen = pygame.display.set_mode(SCR_RECT.size) 013: blockImg = LI.load_image("../images/block.png", -1) 014: bagImg = LI.load_image("../images/bag.png", -1) 015: placeImg = LI.load_image("../images/place.png", -1) 016: storedImg = LI.load_image("../images/stored.png", -1) 017: playerImg = LI.load_image("../images/player.png", -1) 018: 019: def __init__(self): 020: self.mapdata = [] # mapdataを作成 021: self.filename = 'microban.txt' 022: self.blocks = [] # 壁座標(tuple)の List 023: self.places = [] # 収納場所座標(tuple)の List 024: self.bags = [] # 荷物座標(tuple)の List 025: self.stored = [] # 格納座標(tuple)の List 026: self.player = [] # プレーヤー座標(tuple)の List、要素数は1 027: 028: def getData(self): # self.mapdataの作成 029: flag = [] # '#'がある行は'0' mapdataに不要な行は'1' 030: f = open(self.filename, 'r') 031: while(True): # 一行ずつ読んで listに格納 032: line = f.readline() 033: if '#' in line: 034: flag.append('0') 035: else: 036: flag.append('1') 037: if not line: 038: break 039: f.close() 040: max = 0 041: # 面数を取得して初期化 042: for i in range(len(flag)): 043: if flag[i] == '0' and flag[i+1] == '1': # 不要行の次に'#'を含む行があれば 044: max += 1 045: self.mapdata = [[] for _ in range(max)] # mapdata[][]の初期化 046: 047: f = open(self.filename, 'r') 048: lines = f.readlines() # 全行の list 049: f.close 050: n = 0 051: for i in range(len(lines)): # 全行を調べる 052: if '#' in lines[i]: 053: self.mapdata[n].append(lines[i].rstrip('\n')) # 改行を削除して配列に追加 054: if flag[i] == '0' and flag[i+1] == '1': # 不要行の次に'#'を含む行があれば 055: n += 1 # mapdata[n++]
Mapクラスが正しいかどうかを確認するには、上のコードのgetData()メソッドの一番最後にprint(self.mapdata[152])を追加し、さらに以下の3行を追加して、python mapStage.pyで起動して下さい。[152]の数字を適当に変えて起動すれば、その数に対応する面のデータがCMDに表示されます。正しく表示されたましたか?mapStage.pyに以下のコードを追加して上書き保存すれば、temp.pyはもう不要です。
if __name__ == "__main__": map = Map() map.getData()
続いて画像データを面のデータに紐づけるメソッドsetChars()をMapクラスに作成します。このメソッドは面番号を引数に取ります。
def setChars(self, stage): self.blocks.clear() self.places.clear() self.bags.clear() self.stored.clear() self.player.clear() for i in range(len(stage)): # 行数 for j in range(len(stage[i])): # 文字数 if stage[i][j] =='#': self.blocks.append((j, i)) # 壁(x,y) if stage[i][j] =='.': self.places.append((j, i)) # 置き場(x,y) if stage[i][j] =='$': self.bags.append((j, i)) # 荷物(x,y) if stage[i][j] =='@': self.player.append((j, i)) # 荷物(x,y) if stage[i][j] =='*': self.stored.append((j, i)) # 収納済(x,y) self.bags.append((j, i)) # 壁(x,y) self.places.append((j, i)) # 置き場(x,y)
このmapStage.pyを起動しても、画像は表示できません、そこでmapStage.pyからMapクラスを呼び出し、microban.txtから選択した面を表示するプログラムは以下のようになります。souko.pyとしてsoukoフォルダに保存しましょう。
ファイル名:souko.py 001:import pygame 002:from pygame.locals import* 003:import sys 004:sys.path.append("C:\myPython\module") 005:import loadImage as LI # moduleの利用 006:from mapStage import Map # Mapクラスのインポート 007: 008:SCR_RECT = Rect(0, 0, 800, 480) 009:GS = 32 010: 011:class Souko: 012: pygame.init() 013: screen = pygame.display.set_mode(SCR_RECT.size) 014: 015: def __init__(self): 016: pass 017: 018: def drawChar(self, map, screen): # mapを渡す 019: for i in map.blocks: 020: screen.blit(map.blockImg, (i[0]*GS+200, i[1]*GS)) # Mapクラスの変数の利用 021: for i in map.bags: 022: screen.blit(map.bagImg, (i[0]*GS+200, i[1]*GS)) 023: for i in map.places: 024: screen.blit(map.placeImg, (i[0]*GS+200, i[1]*GS)) 025: for i in map.stored: 026: screen.blit(map.storedImg, (i[0]*GS+200, i[1]*GS)) 027: for i in map.player: 028: screen.blit(map.playerImg, (i[0]*GS+200, i[1]*GS)) 029: pygame.display.update() 030: 031: def main(self): 032: map = Map() # Mapクラスのインスタンス作成 033: map.getData() # Mapクラスのメソッドを使う 034: map.setChars(map.mapdata[152]) # Mapクラスのインスタンス変数の利用 035: while (1): 036: self.drawChar(map, self.screen) 037: # イベント処理 038: for event in pygame.event.get(): 039: # 終了用のイベント処理 040: if event.type == QUIT: # 閉じるボタンが押されたとき 041: pygame.quit() 042: sys.exit() 043: if event.type == KEYDOWN: # キーを押したとき 044: if event.key == K_ESCAPE: # Escキーが押されたとき 045: pygame.quit() 046: sys.exit() 047: 048:if __name__ == "__main__": 049: souko = Souko() 050: souko.main()
4-5行でモジュールをimportし、6行目でmapStage.pyのMapクラスをimportしています。15-16行で形だけのコンストラクタを書いています。main以外のメソッドは、描画を担当するdrawChar()だけです。mainメソッドでは、32行でMapクラスのインスタンスを生成し、33-34行でMapクラスのメソッドを使い、Mapクラスのインスタンス変数である配列変数mapdata[][]を渡しています。34行目の[]に0-152の任意の数を書けば、対応する面データを36行目で描画することができます。
mapStage.pyにprint(self.mapdata[152])が記述された状態でsouko.pyを起動すると、CMDには第152面のデータが表示され、そのデータに対応する下の画像が表示されます。しかし面番号をキーボードから入力しなければならないのは不便です。次は、面番号をマウスで選択できるようにします。