はじめに
前回は、次のようなことを学びました。
- Pythonの文法:関数
- Pythonの文法:モジュールとパッケージ
- Pygameのさまざまな機能
今回からは実践編として、非常に小さなレトロ風RPGを作っていきます。
ゲームは非常にシンプルなものです。主人公の勇者はマップの左上から旅を始めて、右下の魔王城に住む魔王を倒すとゲームが終了します。
マップ移動中に遭遇する敵と戦闘することで、経験値が集まりキャラクターが成長します。街に着くとHPとMPが回復します。数分程度でクリアできるゲームとなっています。
今回は、このレトロ風RPGの前半部分として、タイトル画面とマップ画面を作成します。コードを書く際のプログラムは、なるべく短くなるように心掛けます。

補助的モジュール
まずは、ゲームの処理を補助するモジュールを3つ作ります。ゲームの基本データをまとめたモジュールと、画像の処理、音声の処理をおこなうモジュールです。
| モジュール | 内容 |
|---|---|
| data.py | 各種のデータ |
| img.py | 画像の読み込みと他 |
| audio.py | 音声の読み込みと他 |
ファイル構成は次のとおりです。新しいモジュールを作るときは、ここにファイルを足していきます。
data.py
img.py
audio.py
image/
chara.png
land.png
font/
PixelMplus12-Regular.ttf
audio/
bgm/
maou_bgm_8bit01.mp3
(他略)
se/
maou_se_8bit22.wav
data.py
まず解説するのは、各種のデータをまとめたdata.pyモジュールです。ゲーム共通で使うデータをここで定義します。
import pygame
# システム
U = 16 * 3 # 描画単位
W = 20 * U # 横幅
H = 15 * U # 高さ
screen = pygame.Surface((0, 0)) # スクリーン
key = {"down": None, "keep": {}} # キー
# シーン title, map, battle
scene = "" # シーン
scene_next = "title" # 次回更新
# 色
COL_B = (0, 0, 0) # 黒
COL_BT = (0, 0, 0, 128) # 黒半透明
COL_W = (255, 255, 255) # 白
COL_G = (64, 64, 64) # 灰
それぞれの変数について解説します。
Uはマップ1マスや、キャラクター1体の描画サイズです。この単位で描画をおこないます。Wはウィンドウの描画領域の横幅、Hは高さです。このサイズでウィンドウを作成します。
screenは、ウィンドウの描画領域のSurfaceオブジェクトを保持します。まだウィンドウは作成していないので、ダミーでサイズ0のSurfaceを代入します。のちほどこの変数には、初期化したウィンドウの描画領域を代入します。
keyは、キーの状態を保持する辞書です。この変数には、描画ループごとにキーの状態を代入します。
sceneとscene_nextは、画面(タイトル、マップ、バトル)の移動を管理する変数です。sceneは現在の画面の名前です。scene_nextは移動を予約するための名前です。のちほど作る処理で、scene_nextの値が書き換わると、表示する画面を変更します。
COL_~は色のタプルです。黒、黒半透明、白、灰を用意しています。黒半透明だけ、要素が4つあります。4つ目の要素はアルファ値(透明度)です。
img.py
画像の読み込みと分割、保持をおこなうimg.pyモジュールです。フォントの読み込みもおこないます。
import pygame, pygame.freetype, data
# 画像分割
def load(p):
image = pygame.image.load(p) # 画像読み込み
w, h = image.get_size() # 横幅、高さ取得
unit = 16 # 画像ピクセル単位
images = [] # 画像リスト
for y in range(0, h, unit):
for x in range(0, w, unit):
# 1枚分のSurfaceを生成して貼り付ける
piece = pygame.Surface((unit, unit), pygame.SRCALPHA)
piece.blit(image, (0, 0), (x, y, unit, unit))
# 拡大して画像リストに追加
piece = pygame.transform.scale(piece, (data.U, data.U))
images.append(piece)
return images
chara = load("image/chara.png") # キャラクター
land = load("image/land.png") # 土地
fsz = 36 # フォント デフォルト サイズ
font = pygame.freetype.Font("font/PixelMplus12-Regular.ttf", fsz) # フォント
変数charaとlandに、キャラクターの画像
と土地の画像
をリスト化したものを代入します。リスト内の画像は、16ピクセルずつに分割して、data.Uのサイズ(48ピクセル)に拡大したものです。
それぞれの画像は、次のようなリストになります。
分割と拡大とリスト化をおこなうのはload()関数です。「1枚分のSurfaceを生成して貼り付ける」処理、「拡大する」処理は、前回のPygameの画像描画で紹介した処理の応用です。
いくつか、新しい内容が出てきているので解説します。
image.get_size()関数は、Surfaceオブジェクトの横幅と高さのタプルを得ます。w, h = image.get_size()とすることで、戻り値のタプルを分割して、変数wとhに代入します。
for y in range(0, h, unit):とfor x in range(0, w, unit):の入れ子の繰り返し処理は、初心者には少し難しい処理です。
まず外側のrange(0, h, unit)について解説します。0から始まり、unitずつ値を増やして、h未満のあいだ処理をおこないます。こうすることで、変数yは、0から16ずつ大きくなり、画像の高さ未満のあいだ処理を繰り返します。
同じように内側のrange(0, w, unit)は、0から始まり、unitずつ値を増やして、w未満のあいだ処理をおこないます。こうすることで、変数xは、0から16ずつ大きくなり、画像の横幅未満のあいだ処理を繰り返します。
その結果、たとえばchara.pngの画像なら、次の順番で画像を取り出していきます。
そしてpygame.transform.scale()関数で拡大して、images.append(scaled)でリストの末尾に追加していきます。
このモジュールでは、フォントも読み込みます。
フォントのデフォルトサイズfszは36にします。そして、パスを"font/PixelMplus12-Regular.ttf"、デフォルトサイズをfszで読み込み、変数fontに代入します。
audio.py
音声のパスや読み込み、再生をおこなうaudio.pyモジュールです。音声は「魔王魂」さんの音声ファイルを利用します。

プログラムを以下に示します。
from pygame.mixer import Sound, music
FIELD = "audio/bgm/maou_bgm_8bit01.mp3" # 野原
BATTLE = "audio/bgm/maou_bgm_8bit18.mp3" # 戦闘
LOSE = "audio/bgm/maou_bgm_8bit20.mp3" # 敗北
ENDING = "audio/bgm/maou_bgm_8bit22.mp3" # エンディング
WIN = "audio/bgm/maou_bgm_8bit24.mp3" # 勝利
BATTLE_LAST = "audio/bgm/maou_bgm_8bit25.mp3" # 最終戦闘
# BGM再生
def play(p):
music.load(p) # ロード
music.play(-1) # 繰り返し(-1)で再生
damage = Sound("audio/se/maou_se_8bit22.wav") # ダメージ
インポート部分では、pygame.mixerからSound, musicを読み込みます。続く前半は、BGM各音声ファイルのパスです。後半は、読み込みや再生処理です。
BGMはパスだけ用意しておき、モジュール内で定義したplay()関数で読み込みと再生をおこないます。外部からはaudio.play(audio.FIELD)のように実行することで、指定のBGMを再生します。
SEは事前にSound()で読み込んでおきます。外部からはaudio.damage.play()と実行することでSEを再生します。

