はじめに
前回は、次のようなことを学びました。
- 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を再生します。

