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