動きを伴うゲームは、画面上で動かすことが前提ですから、まずは pygame用の画面を作成しましょう。
import pygame # これと from pygame.locals import * # これをインポートし SCR_RECT = Rect(0, 0, 800, 480) # 画面の sizeを指定し pygame.init() # pygameを初期化して screen = pygame.display.set_mode(SCR_RECT.size) # 画面を作成
このようにコーディングし、souko.pyという名前でsoukoフォルダに保存します。コマンドプロンプトを起動し、cd \mypython\soukoと入力してsoukoへ移動します。カレントディレクトリがsoukoとなります。dirコマンドでsouko.pyがあることを確認してくださいね。そこで python souko.pyと入力すると、真っ黒な画面が表示されますがすぐに消えてしまいます。これは、まだメインループを作っていないからで、エラーではありません。
プログラミングの醍醐味は、自分が考えたとおりに処理が進行して結果が得られることの喜びにあると思います。ゲームの場合は、自分が考えたとおりに画面のキャラクタが動くと、感動すら覚えます。そこで、まずは面ファイルを画像として描画してみましょう。そのためには、各キャラクタの画像が必要です。師匠のサイトから画像を拝借しました。荷物と収納場所の画像は自作です。
先に作成したmyPythonフォルダにimagesフォルダを作成し、これらの画像をダウンロードして保存してください。上の画像を右クリックして「名前を付けて画像を保存」を選び、取り敢えず現れたファイル名でimagesフォルダに保存します。左から、block.png、player.png、bag.png、stored.png、place.pngとなります。CMDのdirやwindowsのエクスプローラーで、imagesフォルダに5つの画像ファイルが保存されたことを確認してくださいね。
souko.pyを開き、メインループと画面を閉じるイベント処理を追加します。画面を閉じたときに怒られないように、import sysも追加して上書き保存しましょう。python souko.pyで起動すると真っ黒な画面が現れますが、今度は消えません。Escキーを押すか、Xをクリックすれば終了します。
import pygame from pygame.locals import* import sys SCR_RECT = Rect(0, 0, 800, 480) pygame.init() screen = pygame.display.set_mode(SCR_RECT.size) def main(): while (1): # イベント処理 for event in pygame.event.get(): # 終了用のイベント処理 if event.type == QUIT: # 閉じるボタンが押されたとき pygame.quit() sys.exit() if event.type == KEYDOWN: # キーを押したとき if event.key == K_ESCAPE: # Escキーが押されたとき pygame.quit() sys.exit() if __name__ == "__main__": main()
pythonの前はjavaで遊んでいましたので、クラスが無くても動く上のようなスクリプトは、何だかお尻がムズムズします。そこでクラス化してMainクラスを作成することにしました。簡単です。変えた行の文字色を緑色にしています。これをsouko.pyとして上書き保存します。
import pygame from pygame.locals import* import sysclass Main: SCR_RECT = Rect(0, 0, 800, 480) pygame.init() screen = pygame.display.set_mode(SCR_RECT.size) while (1): # イベント処理 for event in pygame.event.get(): # 終了用のイベント処理 if event.type == QUIT: pygame.quit() sys.exit() if event.type == KEYDOWN: if event.key == K_ESCAPE: pygame.quit() sys.exit() if __name__ == "__main__":main = Main()
それでは<1.準備>でご紹介したSokoban:levelsのTutorialマップデータの面データを表示してみましょう。面データの各行をそれぞれ文字列に代入し、以下のように配列stage[]に格納します。配列の各要素の文字を順に調べ、各文字に対応する画像を画面の対応する座標に表示することになります。
str1 = '##### '
str2 = '#@ # '
str3 = '# #$###'
str4 = '# $ ..#'
str5 = '#######'
stage = [str1, str2, str3, str4, str5]
ここで各文字に対応する画像を画面の座標にどのように対応させるか、が問題になります。各文字を読んで表示する処理を二重 forループとするのが一般的ですね。しかし倉庫番ゲームの場合は、座標が変化するのはプレーヤーと荷物だけであり、壁と収納場所は固定ですので、全部をループに含める必要はありません。
そこでGは、各キャラクタ毎に座標(x,y)の配列を作成することにしました。上の面であれば荷物「$」は2個あり、荷物の配列は[(2,3),(3,2)]となります。このようにtupleのlistとすれば、4行目にある荷物を右へ移動させたなら、荷物の配列で2番目の要素(3,2)を(3,3)に変更すればすみます。
壁と収納場所は固定ですから、座標を配列にすることはあまり意味がありませんが、描画処理のコードを分かり易くするために配列にすることにしました。文字「*」は収納された荷物を表し、収納場所と荷物の両方があることを意味しています。また文字「+」は収納場所にプレーヤがいることを表し、収納場所とプレーヤーの両方があることを意味しています。なのでこれらの座標も配列にします。このようにすれば、荷物配列と収納された荷物の配列が同一となったときに、その面をクリアしたことが判定できるので、クリア処理が簡単になります。
これらの配列は以下のように作成します。なおplayerは一人ですから、配列にする必要はないのですが、後に「一手戻る」処理などを実装する時に、配列にしておいた方が楽なことがあります。なので、あとでplayerの座標も配列にします。
str1 = '##### ' str2 = '#@ # ' str3 = '# #$###' str4 = '# $ ..#' str5 = '#######' stage = [str1, str2, str3, str4, str5] blocks = [] # 壁座標(tuple)の List places = [] # 収納場所座標(tuple)の List bags = [] # 荷物座標(tuple)の List stored = [] # 格納座標(tuple)の List for i in range(len(stage)): # 行数 for j in range(len(stage[i])): # 文字数 if stage[i][j] =='#': blocks.append((j, i)) # 壁(x,y) if stage[i][j] =='.': places.append((j, i)) # 収納場所(x,y) if stage[i][j] =='$': bags.append((j, i)) # 荷物(x,y) if stage[i][j] =='*': stored.append((j, i)) # 収納済(x,y) bags.append((j, i)) # 壁(x,y) places.append((j, i)) # 収納場所(x,y)
次は画像データのロードです。画像データをロードするメソッドも師匠のサイトに最適なものがありましたので、それをそのまま拝借します。以下のメソッドですが、倉庫番ゲーム以外でも使える汎用的なものですので、モジュールとしてimportできるようにします。
先ず、myPythonフォルダにmoduleフォルダを作成しましょう。
次にコードの先頭にimport pygame文とfrom pygame.locals import *文を追加し、適当な名前を付けてmoduleフォルダに保存しておきます。GはloadImage.pyという名前で保存しました。
import pygame from pygame.locals import * def load_image(filename, colorkey=None): """画像をロードして画像と矩形を返すモジュール""" try: image = pygame.image.load(filename).convert_alpha() except pygame.error as message: print("Cannot load image:", filename) raise SystemExit(message) if colorkey != None: if colorkey == -1: colorkey = image.get_at((0,0)) image.set_colorkey(colorkey, RLEACCEL) return image
このモジュールを利用するには、import sysの下にsys.path.append("C:\myPython\module")と書いてpathを追加し、その下にimport loadImageと書くだけです。このモジュールを利用して画像を読み込み、配列に格納して表示するまでのコードは以下のようになります。
ファイル名: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: 007:SCR_RECT = Rect(0, 0, 800, 480) # 画面の size 008:GS = 32 # 1マスの大きさ 009: 010:class Map: 011: pygame.init() # pygameを初期化して 012: screen = pygame.display.set_mode(SCR_RECT.size) # 画面を作成 013: str1 = '##### ' 014: str2 = '#@ # ' 015: str3 = '# #$###' 016: str4 = '# $ ..#' 017: str5 = '#######' 018: stage = [str1, str2, str3, str4, str5] 019: playerImg = LI.load_image("../images/player.png", -1) 020: blockImg = LI.load_image("../images/block.png", -1) 021: bagImg = LI.load_image("../images/bag.png", -1) 022: placeImg = LI.load_image("../images/place.png", -1) 023: storedImg = LI.load_image("../images/stored.png", -1) 024: 025: def __init__(self): 026: self.blocks = [] 027: self.places = [] 028: self.bags = [] 029: self.stored = [] 030: self.player = [] 031: self.setChars(self.stage) 032: 033: def setChars(self, stage): 034: self.blocks.clear() 035: self.places.clear() 036: self.bags.clear() 037: self.stored.clear() 038: self.player.clear() 039: for i in range(len(stage)): # 行数 040: for j in range(len(stage[i])): # 文字数 041: if stage[i][j] =='#': 042: self.blocks.append((j, i)) # 壁(x,y) 043: if stage[i][j] =='.': 044: self.places.append((j, i)) # 置き場(x,y) 045: if stage[i][j] =='$': 046: self.bags.append((j, i)) # 荷物(x,y) 047: if stage[i][j] =='@': 048: self.player.append((j, i)) # 荷物(x,y) 049: if stage[i][j] =='*': 050: self.stored.append((j, i)) # 収納済(x,y) 051: self.bags.append((j, i)) # 壁(x,y) 052: self.places.append((j, i)) # 置き場(x,y) 053: if stage[i][j] =='+': 054: self.player.append((j, i)) # 壁(x,y) 055: self.places.append((j, i)) # 置き場(x,y) 056: 057: def drawChar(self, screen): 058: for i in self.blocks: 059: screen.blit(self.blockImg, (i[0]*GS+200, i[1]*GS)) 060: for i in self.bags: 061: screen.blit(self.bagImg, (i[0]*GS+200, i[1]*GS)) 062: for i in self.places: 063: screen.blit(self.placeImg, (i[0]*GS+200, i[1]*GS)) 064: for i in self.stored: 065: screen.blit(self.storedImg, (i[0]*GS+200, i[1]*GS)) 066: for i in self.player: 067: screen.blit(self.playerImg, (i[0]*GS+200, i[1]*GS)) 068: pygame.display.update() 069: 070: def main(self): 071: while (1): 072: self.drawChar(self.screen) # player以外を描画 073: # イベント処理 074: for event in pygame.event.get(): 075: # 終了用のイベント処理 076: if event.type == QUIT: # 閉じるボタンが押されたとき 077: pygame.quit() 078: sys.exit() 079: if event.type == KEYDOWN: # キーを押したとき 080: if event.key == K_ESCAPE: # Escキーが押されたとき 081: pygame.quit() 082: sys.exit() 083: 084:if __name__ == "__main__": 085: souko = Souko() 086: souko.main()
4-5行で、loadImageモジュールをimportしています。このように保存したファイル名でimportすれば、そのファイルに含まれるメソッドを使えます。今回は、ファイル名とメソッド名が似ていたので、間違えないようにimport loadImage as LIとしてimportし、LI.load_image(...)でそのメソッドを使っています。30行でplayerにも座標の配列を作成しています。
59行以下にある200という数値は画面の左端からの距離で、単に見やすくするだけのものです。いずれは、画面の左端から画像までの間のスペースに、いくつかの情報を表示します。あとは、コメントを見れば理解して頂けると思います。
CMDでカレントディレクトリをsoukoとし、python souko.pyで起動すると以下の画面になります。