Pygame Zero⑤マップの作成
マップの作成
2Dゲームでは画像をタイル状に表示することで,ゲーム・マップを作成する方法が採用されています. タイル状の画像を進行方向とは逆にシフトさせることで,ゲーム画面のスクロールも表現できます. Pygame Zeroでは,マップを作成・表示するためのクラスは用意されていませんので,独自に Mapクラス を実装します. また,これまで取り上げてこなかった ディクショナリ と呼ばれるデータ構造も紹介します. ディクショナリは,リストと同様に,複数の値を記憶するために用います. if文で表現される条件分岐の代替として用いることが可能であり,状況に応じて,リストと使い分けると効果的です.
準備
Muエディタを起動したら,Pygame Zero モードを選択しましょう. 保存用のフォルダを「chapter12」という名前で作成し,ソースファイルを「game.py」という名前でフォルダ内に保存します. また,ゲームを表示するためのウィンドウを作成します. ウィンドウの幅は640px,高さは480px,背景色は白色に設定します.
WIDTH = 640
HEIGHT = 480
def draw():
screen.fill("white")
ディクショナリ
ここでは,Pythonのデータ構造の一つであるディクショナリ(辞書)について確認しましょう. リストとの違いを意識すると良いです.
ディクショナリの使い方
リストは,0,1,2などの参照番号で,記憶させた要素を参照する仕組みでした.
一方,ディクショナリは,任意のキーで,記憶させた要素を参照する仕組みです.
数値だけでなく,文字列もキーとして指定が可能です.
ここでは,“a”,“b”,“c”,をキーとして指定しています.
ディクショナリを作成するときは,キーとバリュー(記憶させる値)の組み合わせを,{}
で括って列挙します.
しかし,データを参照するときは,リストと同様に[]
を用いることに注意してください.
dic = {"a": "apple", "b": "banana", "c": "cherry"} # ディクショナリの作成
print(dic) # -> {'a': 'apple', 'b': 'banana', 'c': 'cherry'}
print(dic["a"]) # -> apple
print(dic["b"]) # -> banana
print(dic["c"]) # -> cherry
条件分岐としてのディクショナリ
ディクショナリの構造を利用して,条件分岐を表現することができます.
例えば,変数x
の値に応じて,3種類に分岐するif文は次のように記述できます.
x = "b" # xの値に応じて分岐
if x == "a":
print("apple")
elif x == "b":
print("banana") # -> banana
elif x == "c":
print("cherry")
これを,ディクショナリを用いて表現すると次のように記述できます. キーが条件として機能していることがわかります. if文よりもスッキリと表現できることが多いので活用しましょう.
x = "b"
print(dic[x]) # -> banana
マップ
2Dゲームにおいて,マップを表現する方法の一つが,画像をタイル状に並べる方法です. ここでは,自由にマップをデザインするために,独自にMapクラスを定義してみます.
Mapの仕組み
マップの素材として,KENNYのRGB Baseで提供されている次の4種類のタイル画像を用います. それぞれ,平地,海,道路,草を表しており,画像サイズは32ピクセル×32ピクセルです. 画像ファイルをダウンロードしたら,imagesフォルダにコピーしてください.
ディクショナリmap_dic
を定義し,特定の文字とファイル画像名の,キー・バリューを登録します.
例えば,f
という文字に対し,tile0
という画像ファイル名がペアとなっています.
これを,条件分岐の代替として用います.
map_dic = {
"f": "tile0", # 森
"s": "tile1", # 海
"r": "tile2", # 道路
"g": "tile3", # 草
}
タイル画像をスクリーンにタイル状に敷き詰めます.
ここでは,説明のため,4枚×3枚のタイルを敷き詰めることを考えます.
4×3の2重構造のリストを生成し,表示するタイル画像に対応する文字(f
,s
,r
,g
)を設定しておきます.
スクリーンに描画する際には,この文字に対応する画像ファイルを,ディクショナリmap_dic
を用いて選択します.
画像サイズが32ピクセル×32ピクセルであるのに対し,
実際のスクリーンのサイズは640ピクセル×480ピクセルです.
このため,20枚×15枚のタイル画像を並べることになります.
次に示すmap_data
が20×15の2重構造のリストです.
map_data = [
["s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s"],
["f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f"],
["f", "f", "g", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "g", "f", "f", "f", "f"],
["f", "f", "f", "f", "f", "g", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "g", "f"],
["g", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "g", "f", "f", "f", "f", "f", "f", "f", "f"],
["f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f"],
["r", "r", "r", "r", "r", "r", "r", "r", "r", "r", "r", "r", "r", "r", "r", "r", "r", "r", "r", "r"],
["r", "r", "r", "r", "r", "r", "r", "r", "r", "r", "r", "r", "r", "r", "r", "r", "r", "r", "r", "r"],
["r", "r", "r", "r", "r", "r", "r", "r", "r", "r", "r", "r", "r", "r", "r", "r", "r", "r", "r", "r"],
["f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f"],
["g", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "g", "f", "f"],
["f", "f", "g", "f", "f", "f", "f", "f", "g", "f", "f", "g", "f", "f", "g", "f", "f", "f", "f", "f"],
["f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "g", "f"],
["f", "f", "f", "f", "f", "g", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f", "f"],
["s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s"]
]
Mapクラス
上記のディクショナリmap_dic
と,2重構造のリストmap_data
を用いて,マップを表示する Mapクラス を実装します.
コンストラクタでは,map_dic
,map_data
と,タイル画像のサイズtile_size
を引数として渡します.
また,draw()
を新たに定義し,文字に対応する画像ファイルをscreen.blit()
で描画します.
class Map:
# コンストラクタ
def __init__(self, map_dic, map_data, tile_size):
self.map_dic = map_dic
self.map_data = map_data
self.tile_size = tile_size
def draw(self):
max_i = len(map_data[0]) # -> 20
max_j = len(map_data) # -> 15
for i in range(max_i):
for j in range(max_j):
number = self.map_data[j][i] # タイル画像に対応する文字を取得
image = self.map_dic[number] # タイル画像のファイル名を取得
screen.blit(image, (i * self.tile_size, j * self.tile_size))
Mapクラスを定義したら,オブジェクトmap
を生成し,draw()
を実行します.
タイルのサイズには$32$ピクセルを指定します.
# オブジェクトの生成
map = Map(map_dic, map_data, 32)
def draw():
screen.fill("white")
map.draw() # マップの描画
画面のスクロール
タイル画像を左にシフトさせることで,ゲーム画面をスクロールします. 最初に,自動車の画像をダウンロードして,imagesフォルダにコピーしてください. この自動車の画像をゲーム画面の中央に配置することで, スクロールで自動車が前進しているように錯覚させることができます.
Actorクラスで,自動車のスプライトを生成し,ゲーム画面の中央に配置します. このとき,自動車が右方向を向くように,時計周りに90°回転させておきます.
# 自動車のスプライト
car = Actor("car", center=(WIDTH/2, HEIGHT/2))
car.angle = -90 # 時計周りに90°回転
def draw():
screen.fill("white")
map.draw()
car.draw() # 自動車のスプライトを表示
Mapクラスに,スクロールのためのshift()
を定義します.
リストの先頭の要素を取り出し,リストの最後尾に移動させます.
これにより,マップが左に1タイル分だけ,移動することになります.
# マップを左にシフト(スクロール)
def shift(self):
for list in self.map_data:
head = list.pop(0) # リストの先頭要素を取り出し
list.append(head) # リストの差後尾に先頭要素を追加
clock.schedule_interval()
を利用して,上記のshift()
を0.2秒ごとに繰り返し実行します.
# 0.2秒ごとにmap.shift()を実行する
clock.schedule_interval(map.shift, 0.2)
拡大・縮小・反転(補足)
Pygame Zero Helperを利用すると キャラクター(Actorクラス)の拡大・縮小・反転が可能です. 上記のサイトからpgzhelper.pyをダウンロードし,プロジェクトのフォルダ内に配置してください. また,game.pyの冒頭に下記のコードを追加してください.
from pgzhelper import *
キャラクターを拡大・縮小するにはscale
を利用します.
car = Actor("car.png", center=(WIDTH/2, HEIGHT/2))
car.scale = 2.0 # 2倍
キャラクターを左右反転するにはflip_x
,上下反転するにはflip_y
を利用します.
car = Actor("car.png", center=(WIDTH/2, HEIGHT/2))
car.flip_x = -1.0 # 左右反転
キャラクターを前進するにはmove_forward()
,後退するにはmove_back()
を利用します.
car = Actor("car.png", center=(WIDTH/2, HEIGHT/2))
car.move_forward() # 前進
課題
次の課題に取組んでください.
- 任意のマップを作成(タイル画像を変更してもOK)
- 自動車を左向きに変更し,マップを右にスクロール
- カーソルキーで自動車を上下に移動
課題を完成させたらスクリプトを保存し,chapter12フォルダをZIPで圧縮してから,chapter12.zipという名前でファイルを提出してください.