pygameことはじめ

Python
この記事は約22分で読めます。

PythonでGUIゲームを作るためのモジュール、pygameを使ってみました。

インストール

pygame は標準ではインストールされないので、例によってまずはモジュールのインストールです。

Windows環境では、例によって py -m pip install pygame でインストールできます。

C:\> py -m pip install pygame
Collecting pygame
  Downloading pygame-2.0.1-cp39-cp39-win_amd64.whl (5.2 MB)
     |████████████████████████████████| 5.2 MB 3.2 MB/s
Installing collected packages: pygame
Successfully installed pygame-2.0.1

C:\>

pygame プログラムの基本的なつくり

まずはひな形

とりあえず『ウィンドウを表示して、×ボタンで消える』だけの基本部分です。どんな内容のプログラムでも、最低限これだけは書かなくてはいけなないでしょう。

import pygame
import pygame.locals
import sys

pygame.init()
pygame.display.set_mode((640,480))                      # ウィンドウサイズ(width. height) のタプルで指定
pygame.display.set_caption("test")                      # ウィンドウタイトルを指定

surface = pygame.display.get_surface()                  # サーフェイス(描画のための土台)を求める
surface.fill((0,128,0))                                 # 背景色を (R, G, B) のタプルで指定
pygame.display.update()                                 # 表示の更新(これがないとクライアント領域が表示されない)

while True:                                             # メッセージループ
    for event in pygame.event.get():                    # メッセージを取得
        if event.type == pygame.locals.QUIT:            # ×ボタンが押されたとき
            pygame.quit()
            sys.exit()
    pygame.time.wait(100)

ウィンドウの表示

pygame.init()
pygame.display.set_mode((640,480))                      # ウィンドウサイズ(width. height) のタプルで指定
pygame.display.set_caption("test")                      # ウィンドウタイトルを指定

surface = pygame.display.get_surface()                  # サーフェイス(描画のための土台)を求める
surface.fill((0,128,0))                                 # 背景色を (R, G, B) のタプルで指定
pygame.display.update()                                 # 表示の更新(これがないとクライア

ウィンドウを表示するのに最小限必要なのはこの部分です。

pygame.init() はpygameモジュール全体を初期化します。pygame関係の処理に先立って行います。2回以上やっても問題ないらしいですが2回以上やる意味もないような気もします。

pygame.display.set_mode( (640, 480) ) はウィンドウの大きさを指定しています。set_mode() メソッドの必須引数は1つだけで、そこに (幅, 高さ) のタプルを渡すため、コード上は二重括弧になるのが他の言語ではあまり見ない書き方です。

pygame.display.set_caption(“test”) はウィンドウのタイトルを指定します。

pygame.display.get_surface() は、ディスプレイのサーフェイスを取得する…とドキュメントにはあるのですが、サーフェイスとはウィンドウのクライアント描画領域を表すオブジェクトのようです(JavaのGUIでのGraphicsオブジェクトみたいなものだと思っておけばいいのでしょうか)。後で出てくる描画メソッドはこの surface のメソッドだったり surface を引数に取ったりします。

surface.fill( (0,128,0) ) はサーフェイスを塗りつぶすメソッドです。必須の引数は1つで、色を (R, G, B) の型式のタプルで指定します。そのため set_mode() と同様、コード上は二重括弧になるのが特徴的です(pygame はタプルを引数に取るメソッドが多いようです)。R,G,Bはそれぞれ0~255の値で、この例では暗い緑(ゲーム用テーブルでよく見る色)を指定しています。

注意しなければならないのは、surface.fill()を実行しただけではウィンドウに反映されないことです。

pygame.display.update() を実行することで、そこまでの描画がウィンドウに反映されます。

(逆に言うと、Java の awtなどと違ってダブルバッファリングをしなくてもチラつかないとか?)

画面に何か描画を行うたびに update() する必要があるので、この例では説明の都合上 while の前に書いてしまいましたが本当は後のループの中に書いた方がいいです。

メッセージループ

while True:                                             # メッセージループ
    for event in pygame.event.get():                    # メッセージを取得
        if event.type == pygame.locals.QUIT:            # ×ボタンが押されたとき
            pygame.quit()
            sys.exit()
    pygame.time.wait(100)

while True で無限ループが作られていますが、ここにpygameのメインとなる処理を書きます。

Java や JavaScript では、『マウスをクリック』や『キーボードを押す』というユーザ操作イベントに対して自動的に特定のメソッドが呼び出されるようになっています。しかしPythonの場合は、イベントが発生しているかをプログラムが自分で調べなければなりません。そして発生したイベントの種類それに応じて処理を振り分ける部分も明記しなければなりません。

うん、これは昔懐かしいメッセージループじゃないか。Windows初期、VC++で作っていた頃を思い出すわ。しばらくJavaのイベントリスナーの便利さに慣れて忘れてたけど。

pygame.event.get() は発生したイベントをリストにして取り出します。
これを、for event in pygame.event.get(): でイベント1つずつにバラして順に処理するわけですね。

if event.type == pygame.locals.QUIT: というのはイベントの種類を調べています。pygame.locals.QUIT は、ウィンドウの右上の×ボタンをクリックしたときに発生するイベントです。

pygame.quit() は、ドキュメントには『pygameモジュールの初期化を解除する』と意味のよく判らないことが書かれているのですが、要するにpygameの終了処理だと思っていいでしょう。ただしプログラムそのものは終了しないので、sys.exit() でプログラムを終了します。

これで、×ボタンを押せばプログラムが終了する(ウィンドウが閉じる)ようになります。この処理がないと、強制終了しないとウィンドウが消せなくなります。

処理を増やすには、if event.type==〇〇 という部分を追加していけばいいわけです。

最後の pygame.time.wait(100) は、名前の通りプログラムの実行をちょっと止めて待つ処理です。引数は待ち時間で、単位はms(1000ms=1秒)です。ただし精度や最短実行時間が1msだとは保証されていません。

これはプログラム実行速度の調節であると同時に、『待ち時間中はPCの処理能力を解放し、他のプログラムの処理のために使えるようにする』という意味もあります。システムによっては、まったくwaitをしないと他のプログラムの処理速度に悪影響を与えてしまうかもしれません。

似たメソッドとして pygame.time.delay() があります。ドキュメントによるとdelayの方が待ち時間が正確ですが、停止中も処理能力を解放しないようです。よって、他のプログラムと同時に使用する場合は wait を使った方がいかもしれません。

画面の描画

次に、画面にいろいろ描いてみます。先ほどのプログラムの surface.fill(0,128,0) と pygame.display.update() の間にいろいろな図形を描くコードを追加しました。

import pygame
import pygame.locals
import sys
import pygame.font

pygame.init()
pygame.display.set_mode((640,480))                      # ウィンドウサイズ(width. height) のタプルで指定
pygame.display.set_caption("test")                      # ウィンドウタイトルを指定

surface = pygame.display.get_surface()                  # サーフェイス(描画のための土台)を求める
surface.fill((0,128,0))                                 # 背景色を (R, G, B) のタプルで指定

pygame.draw.line(surface, (255,255,255), (20,20), (620,60), 5)
pygame.draw.rect(surface, (255,128,128), (50,80,200,100), 0)
pygame.draw.circle(surface, (255,128,128), (100,230), 50, 2)
pygame.draw.circle(surface, (128,255,128), (200,230), 50, 0)
pygame.draw.ellipse(surface, (255,128,255), (50,280,200,80), 2)
pygame.draw.ellipse(surface, (255,255,128), (50,360,200,80), 0)

font = pygame.font.SysFont("msgothicmsuigothicmspgothic", 30)
text = font.render("ABC日本語表示", True, (255,255,255))
surface.blit(text, (360,100))

ygame.draw.rect(surface, (255,255,255), (320,250,300,100),2)
text2 = font.render("中央揃え", True, (255,255,255))
surface.blit(text2, ( int((300-text2.get_rect().width)/2+320), int((100-text2.get_rect().height)/2+250)) )

pygame.display.update()                             # 表示の更新(これがないとクライアント領域が表示されない)

while(True):                                            # メッセージループ
    for event in pygame.event.get():                    # メッセージを所得
        if event.type == pygame.locals.QUIT:            # ×ボタンが押されたとき
            pygame.quit()
            sys.exit()

    pygame.time.wait(100)

実行すると、このように表示されます。

それでは、描画メソッドを個別に見ていきましょう。

直線

直線を描画するには pygame.draw.line() メソッドを使用します。

pygame.draw.line(surface, color, start, end, width)

surface には描画対象のサーフェイス(例えば pygame.display.get_surface()メソッドで取得したもの)を指定します。

color には直線の描画色を指定します。(R, G, B) のタプル形式です。

startend にはそれぞれ直線の始点・終点の座標を指定します。ともに (x, y) のタプル形式です。

width には直線の太さを指定します。width は省略可能で、デフォルト値は1です。

以下の例では、(20,20) から (620,60) まで太さ5・白色(255,255,255) の直線を描画します。

pygame.draw.line(surface, (255,255,255), (20,20), (620,60), 5)

矩形

矩形(四角形)を描画するには、pygame.draw.rect() メソッドを使用します。

pygame.draw.rect(surface, color, rect, width)

surface には描画対象のサーフェイスを指定します。

color には描画色を指定します。(R, G, B) のタプル形式です。

rect には矩形領域を指定します。(x, y, w, h) のタプル形式です。x,y は矩形の左上隅の座標、w は矩形の横幅、h は矩形の高さです。

width は描画する矩形の枠線の太さを表します。width に0を指定するか、または省略すると、描画する矩形の内部を color で塗りつぶします。

以下の例では、
左上隅(50,80)・横幅200・高さ100・赤色(255,128,128)の塗りつぶし矩形と、
左上隅(300,200)・横幅300・高さ100・白色(255,255,255)、太さ2の矩形枠線
を描画します。

pygame.draw.rect(surface, (255,128,128), (50,80,200,100), 0)    # 塗りつぶし
pygame.draw.rect(surface, (255,255,255), (300,200,300,100), 2)  # 枠線のみ(枠線の太さ=2)

円を描画するには、pygame.draw.circle() メソッドを使用します。

pygame.draw.circle(surface, color, center, radius, width)

surface には描画対象のサーフェイスを指定します。

color には描画色を指定します。(R, G, B) のタプル形式です。

center には円の中心の座標を指定します。 (x, y) のタプル形式です。

radius には円の半径を指定します。

width は円周の太さを指定します。width に0を指定するか、または省略すると、描画する円の内部を color で塗りつぶします。

以下の例では、
中心(100,230)・半径50・赤色(255,128,128)・太さ2の円周
中心(200,230)・半径50・緑色(128,255,128)の塗りつぶし円
を描画します。

pygame.draw.circle(surface, (255,128,128), (100,230), 50, 2)    # 円周の太さ2
pygame.draw.circle(surface, (128,255,128), (200,230), 50, 0)    # 塗りつぶし

楕円

楕円の描画には、pygame.draw.ellipse() メソッドを使用します。

pygame.draw.ellipse(surface, color, rect, width)

円とは描画される形は似ていますが、メソッドの引数の指定方法はまったく違います。むしろ矩形の方が近いです。

surface には描画対象のサーフェイスを指定します。

color には描画色を指定します。(R, G, B) のタプル形式です。

rect には、矩形領域を指定します。この矩形に内接するように楕円が描画されます。(x, y, w, h) のタプル形式です。x,y は矩形の左上隅の座標、w は矩形の幅、h は矩形の高さです。
楕円の中心+長半径・短半径ではありません。

width は描画する矩形の枠線の太さを表します。width に0を指定するか、または省略すると、中までcolor で塗りつぶします。

以下の例では、
左上隅(50,280)・横幅200・高さ80の矩形に内接する、紫色(255,128,255)で太さ2の楕円周
左上隅(50,360)・横幅200・高さ80の矩形に内接する、黄色(255,255,128)の塗りつぶし楕円
を描画します。

pygame.draw.ellipse(surface, (255,128,255), (50,280,200,80), 2)    # 外周の太さは2
pygame.draw.ellipse(surface, (255,255,128), (50,360,200,80), 0)    # 塗りつぶし

矩形として正方形を指定すれば円になりますが、pygame.draw.circle() メソッドとは引数が大きく異なることに注意して下さい。たとえば 中心(100,100)・半径30の円を描画する場合、

# circleの場合
pygame.draw.circle(surface, (255, 255, 255), (100, 100), 30, 1)
# ellipseの場合
pygame.draw.ellipse(surface, (255, 255, 255), (70, 70, 60, 60), 1)

のように、指定方法がまったく違います。

文字列描画

pygameでGUIウィンドウに文字列を描画するのはちょっと面倒で、3段階の手順が必要です。

  1. フォントを用意する
  2. 文字列をレンダリングする
  3. レンダリングした文字列をウィンドウに貼りつける
フォントの用意

フォントを用意するには、pygame.font.SysFont() メソッド、または pygame.font.Font() メソッドを使用します。ここでは pygame.font.SysFont() メソッドについて説明します。

pygame.font.SysFont(fontname, size, bold, italic)

fontname にはフォント名の文字列、または複数のフォント名のリストを指定します。該当するフォントが存在しない場合、または None が指定された場合はデフォルトのフォントが使用されます。

size はフォントのサイズを指定します。

bolditalic は省略可能ですが、True を指定するとそれぞれボールドフォントイタリックフォントになります。

この pygame.font.SysFont() の返却値を、次の文字列のレンダリングで使用します。
一度用意したフォントは、何度もレンダリングで繰り返し利用できます。

font = pygame.font.SysFont("msgothicmsuigothicmspgothic", 30)

さて、困ったことが2つあります。まず1つ、デフォルトのフォントでは日本語が表示できず、日本語部分がすべて『□』になってしまいます(pygame 2.0.1、Python 3.9.6時点)。

もう1つ、フォント名がちょっと特殊です。たとえばWindows環境ではお馴染みの『MSゴシック』を指定したい場合、CSSで指定するときのような『MS ゴシック』ではなく、matplotlib では使えた『MS Gothic』でもなく、『msgothicmsuigothicmspgothic』という名前で指定しないと反映されないようです。何の呪文だこれは。というかこれだと『MSゴシック』か『MS UI Gothic』か『MS Pゴシック』か判らんぞ。まぁとりあえず日本語表示できたのでいいことにします。あとで暇ができたらちゃんと調べよう…。

自分のシステムでどのようなフォント名が使用可能かは、pygame.font.get_fonts() メソッドで取得できます。たとえば Python の対話モードで以下のようにすればいいでしょう。

>>> import pygame.font
pygame 2.0.1 (SDL 2.0.14, Python 3.9.6)
Hello from the pygame community. https://www.pygame.org/contribute.html
>>> pygame.font.get_fonts()
['arial', 'arialblack', 'bahnschrift', 'calibri', 'cambriacambriamath', 'cambria',(以下略)
>>>

うちの環境では400個以上のフォント名が出てきました(Wordと一太郎、オマケのフォントも含めて全部インストールしてるからかな…)。見慣れた名前と違う表示になっているので探すのが大変です。

文字列のレンダリング

pygameでは、文字列を直接ウィンドウに描画するのではなく、あらかじめ『レンダリング』という作業が必要です。レンダリングには Font.render() メソッドを使用します。

Font.render(text, antialias, color, background)

render() メソッドは、pygame.font.SysFont() メソッドや pygame.font.Font() メソッドで用意した Font に対して実行するメソッドです。

text は描画したい文字列です。フォントが対応していれば日本語も描画可能ですが、先述したとおりデフォルトのフォントは日本語対応していません。また、\t(タブ文字)や \n(改行文字)などが含まれている場合、特殊文字ではなく単に『フォントがない文字』と認識されるようで、画面には『□』が表示されてしまいます。

antialias はアンチエイリアス(文字を滑らかに表示する)処理を行うかどうかの指定です。True を指定するとアンチエイリアス処理を行い、False だと行いません。必須引数です。デフォルト True で省略可にしてくれればいいのに。

color は文字の描画色です。他の描画メソッドと同様、(R, G, B) のタプルで指定します。

background は背景色です。指定方法は文字色と同じです。省略可能で、省略すると背景が透明になります。

Font.render() メソッドは文字列がちょうど入る大きさのサーフェイスを作成して返します。pygameでは既存のサーフェイスに直接文字を描画するメソッドがありませんので、Font.render() で作成したサーフェイスを既存のサーフェイス(例えば最初にpygame.display.get_surface() で取得したもの)に貼りつけて文字描画を行います。

…テプラで印字したテープを作って(レンダリング)、それを貼りつけていく…みたいなイメージでしょうか?

下の例では、『ABC日本語表示』という文字列を、アンチエイリアスあり・白色(255,255,255)でレンダリングしています。

text = font.render("ABC日本語表示", True, (255,255,255))
ウィンドウに貼りつける

文字列をレンダリングしたサーフェイスをウィンドウに貼りつけるには、Surface.blit() メソッドを使用します。

Surface.blit(surface, position)

最初の Surface は貼付先のサーフェイス(例えば pygame.display.get_surface() で取得したもの)、引数の surface はフォントをレンダリングしたサーフェイス(テプラで印字したテープのようなもの)です。

position は貼付先の位置、フォントをレンダリングしたサーフェイスの左上隅の座標です。他と同じく (x, y) のタプルで指定します。

以下の例では、レンダリングした文字列のサーフェイスtextを、貼付先のサーフェイスsurfaceの (360,100) を左上隅となるように貼りつけています。

surface.blit(text, (360,100))

文字列のセンタリング

ちょっとした応用で、文字列を枠(矩形)の中央に描画する方法を考えてみます。

枠の横幅を WIDTH、表示する文字列の横幅を width とすると、文字列の左右には \(\displaystyle\frac{\mathrm{WIDTH} – \mathrm{width}}{2}\) ずつ余白を取れば水平方向の中央に配置することができます。描画する際は、貼付先(この場合はウィンドウ)の座標で指定するので、枠の水平方向の位置(左上隅のx座標に等しい)を LEFT とすると、文字列の水平方向の描画位置は、

\(\displaystyle\frac{\mathrm{WIDTH}-\mathrm{width}}{2}+\mathrm{LEFT}\)

で表されます。

鉛直方向も同様に考えます。枠の鉛直方向の位置(左上隅のy座標)をTOP、枠の高さをHEIGHT、表示する文字列の高さをheightとすると、文字列の描画位置は、

\(\displaystyle\frac{\mathrm{HEIGHT}-\mathrm{height}}{2}+\mathrm{TOP}\)

で表されます。

文字列(正確には文字列がちょうど入る大きさのサーフェイスの矩形)の幅と高さは、

Surface.get_rect().width
Sufface.get_rect().height

で取得できます。よって、

(枠の幅 – 文字列サーフェイス.get_rect().width) / 2 + 枠の左上隅のx座標
(枠の高さ – 文字列サーフェイス.get_rect().height) / 2 + 枠の左上隅のy座標

とすれば、文字列を貼りつける座標を求めることができます。

以下の例では、左上隅座標(320,250)・幅300・高さ100の枠(白線で描画)の中央に『中央揃え』という文字列を表示しています。割り算で小数がでるので int() で整数化しています。

ygame.draw.rect(surface, (255,255,255), (320,250,300,100),2)
text2 = font.render("中央揃え", True, (255,255,255))
surface.blit(text2, ( int((300-text2.get_rect().width)/2+320), int((100-text2.get_rect().height)/2+250)) )

というわけで。

まずはいろいろ画面に表示する方法をまとめてみました。他にも折れ線や多角形を描画するメソッドがありますが、またの機会に。

次はマウスやキーボードのイベントを処理する方法をまとめます。

コメント

タイトルとURLをコピーしました