概要
この記事はフューチャー Advent Calendar 2019 14日目の記事です。
昨日の記事は@RuyPKGさんによる新人でも、楽がしたい! ~議事録の準備~となってます。
先日社内勉強会にて、@shibukawaさんがゲームエンジンの紹介をしていたのがこの記事を書いたきっかけになります。
Pythonで実装したボードゲームのUIが欲しく、HTMLによる表現だと動きを表現するのが難しかったのでPythonのゲームエンジンであるpygameを触ってみることにしました。
pygameとは
Pythonで動かすことの出来る、クロスプラットフォームのゲームエンジンです。
ゲームのグラフィック部分をPythonで書くことができるならば、Pythonで書かれたゲームロジックをそのまま埋め込むことが出来るのでとても便利です。
このpygameですが、2009年に一旦開発が停止してしばらく動いていなかったのでpygameをご存知の方は乗り換えした人も多いのではないでしょうか。
しかし、pygame2リリースに向けた開発が2019年になって活発になり、来年中にpygame2の本バージョンがリリースされそうです。
2.0.0.dev6からはpython3.8もサポートされていて先が楽しみです。
今後注目を浴びてくる気がします。
環境
- pygame: 2.0.0.dev6
- python: 3.8.0
環境構築
pygame公式のGetting Startedを参考に環境構築を進めます。
pip経由で、pygameをインストールします。
この際、バージョンを指定しないと最新安定版である1.9.6が入ってしまうので注意しましょう。(1.9.6はpython3.6までしか対応していません)
pip install pygame==2.0.0.dev6
サンプルを動かしてみる
多数のサンプルゲームがpygameには含まれています。
試しに一つ実行してみましょう。
python -m pygame.examples.aliens
インベーダーゲームのようなものが実行されました!
サンプルの一覧とそれぞれどんなゲームかはgitのexampleフォルダのREADMEにあるので色々実行してみると面白いです。
1からサンプルを作る
サンプルを解読しながら、自分でコードを描き上げていきます。
今回は、背景に文字を書くだけのサンプルを目指します。
処理の流れ
静的な画面とは違い、ゲームエンジンを用いて描画する場合は高速で画面を更新する必要があります。
そのため、以下のような流れで処理を行います。
- 画像読み込み等の初期化
- 画面をクリアし、背景画像を描画
- 画面に表示するオブジェクトの位置や値を更新して描画
- 画面の更新の反映
- 2に戻る
ゲームエンジンでは、画面に表示するオブジェクトはスプライトを使って描画されているので、スプライトクラスを継承したクラスを使って実装時は描画します。
スプライトが何か気になった方は、調べてみてください。
描画を高速化するためのハードウェア実装の話等、歴史的経緯があって面白いですが知らなくても特に問題はないと思います。
import os
import pygame as pg
# game constants
SCREENRECT = pg.Rect(0, 0, 640, 480)
SCORE = 0
main_dir = os.path.split(os.path.abspath(__file__))[0]
def load_image(file):
""" loads an image, prepares it for play
"""
file = os.path.join(main_dir, "data", file)
try:
surface = pg.image.load(file)
except pg.error:
raise SystemExit('Could not load image "%s" %s' % (file, pg.get_error()))
return surface.convert()
class Score(pg.sprite.Sprite):
""" to keep track of the score.
"""
def __init__(self):
pg.sprite.Sprite.__init__(self)
self.font = pg.font.Font(None, 40)
self.font.set_italic(1)
self.color = pg.Color("white")
self.lastscore = -1
self.update()
self.rect = self.image.get_rect().move(10, 450)
def update(self):
""" We only update the score in update() when it has changed.
"""
if SCORE != self.lastscore:
self.lastscore = SCORE
msg = "Score: %d" % SCORE
self.image = self.font.render(msg, 0, self.color)
def main(winstyle=0):
pg.init()
# Set the display mode
winstyle = 0 # |FULLSCREEN
bestdepth = pg.display.mode_ok(SCREENRECT.size, winstyle, 32)
screen = pg.display.set_mode(SCREENRECT.size, winstyle, bestdepth)
# create the background, tile the bgd image
bgdtile = load_image("background.jpg")
background = pg.Surface(SCREENRECT.size)
background.blit(bgdtile, (0, 0))
screen.blit(bgdtile, (0, 0))
pg.display.flip()
# Initialize Game Groups
all = pg.sprite.RenderUpdates()
# Create Some Starting Values
clock = pg.time.Clock()
global SCORE
if pg.font:
all.add(Score())
# Run our main loop whilst the player is alive.
while True:
all.clear(screen, background)
SCORE += 123456789
all.update()
# draw the scene
dirty = all.draw(screen)
pg.display.update(dirty)
# cap the framerate at 40fps. Also called 40HZ or 40 times per second.
clock.tick(40)
if __name__ == "__main__":
main()
data以下に背景画像(background.jpg)を準備しておく必要があります。
ちょっと長いですが、上記サンプルを実行すると、以下のように背景の描画と文字の表示ができます。
以下ゲームエンジン独特の部分を説明します。
Spriteのグループ化
all = pg.sprite.RenderUpdates() # Spriteグループの作成
all.add(Score()) # Spriteグループへの追加
all.update() # Spriteグループの一括更新
all.draw(screen) # Spriteグループの描画
pygameにはスプライトをグループ化できる機能が備わっていて、画面の全ての要素や、特定のグループ(例えば敵キャラクターのみ等)の要素を一斉に更新したり描画するのが簡単にできるようなっています。
Spriteのグループには順序付きグループや単体スプライト用のグループや様々な種類があり、用途に応じて使い分けると良いでしょう。
公式ドキュメントで一覧は確認できます。
画面のリフレッシュレートの指定
clock = pg.time.Clock() # Clockの生成
clock.tick(40) # Clockを用いて1/40秒経過するまで処理を待つ
オブジェクトを動かす際には、処理の重さに関わらず同じ速さで動かすために処理が早く終わりすぎた場合は待つ必要があります。
これを実現するため、pygameではClockオブジェクトを使用します。
Clockオブジェクトを作ってclock.tick()を呼ぶだけで、前回の呼び出しからフレームレートに応じた時間sleepしてくれるのでとても便利です。
最後に
今回紹介した描画の機能以外にも、pygameには
- 入力の受けつけ
- 音の再生
- Android, iOS対応
等様々な機能があります。
リアルタイムに更新されるUIをPythonで書けるのは非常に便利なので、pygame2の正式リリースが待ちきれないですね!