これまでは一つの面だけを表示しましたが、複数の面から選択して表示できるようにしたいですね。この場合、クラス化した方があとあと楽になりますので、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面のデータが表示され、そのデータに対応する下の画像が表示されます。しかし面番号をキーボードから入力しなければならないのは不便です。次は、面番号をマウスで選択できるようにします。
