適当のごった煮

Pythonと境界標とQGISを中心にいろいろと

Pygameの基礎を学ぶ

スポンサードリンク

Pygameを学ぼうと思ってネットを検索していたら、Invent with Pythonというサイトにたどり着きました。英語ですが、書籍がフリーで公開されており、Making Games with Python & Pygameの2章に、知りたかったPygameの基礎がまとまっていたので辞書を片手に実験しながらまとめました。

目次

何もしない画面の表示

画面作成メソッドによって得られるSurfaceオブジェクトが基本画面になる。ここに画像やキャラクターなどを乗せていくことでゲーム画面を作り上げていく。

操作内容 コード
インポート import pygame as pg
初期化 pg.init()
画面作成 GAMEN = pg.display.set_mode(幅と高さのタプル)
タイトルバー pg.display.set_caption(文字列)
イベント取得 pg.event.get()
初期化処理を解除 pg.quit()
# 画面表示
import pygame as pg
import sys

pg.init()
GAMEN = pg.display.set_mode((400, 300))
pg.display.set_caption('Hello World!')
while True:
    for event in pg.event.get():
        if event.type == pg.QUIT:
            pg.quit()
            sys.exit()

f:id:tekito-gottani:20190205150128j:plain

画面の表示

「pg.quit()」はPygameを終了するのではなく、初期化処理を解除するだけなので、「sys.exit()」を使わないとプログラムの実行が続き、Pygame関連の処理部分で「pygame.error: video system not initialized」が出る。

色の表現

Pygameの色は、3つか4つの要素を持つタプルで表される。3つの要素の場合は、赤、緑、青をそれぞれ整数(0~255)で表したタプルで表現する。例えば、黒は(0, 0, 0)で、白は(255, 255, 255)といった感じである。

4つの要素を持つ場合は、上記の3つに加えて不透明度(α値)を整数(0~255)で入力する。α値が0なら完全に透けて、255なら透けない(=指定しない場合と同じ)。この値を使用する場合は、大元の画面(Surfaceオブジェクト)に対して「convert_alfpha()」メソッドを使い、新たに不透明度対応のSurfaceオブジェクトを取得する必要がある。

図形の描画

描画対象画面と色、座標を指定することで様々な図形を描くことができる。ピクセル指定を使用すると、指定したピクセル単位で操作することができるが、ピクセルオブジェクトが作成されると画面がロックされてしまい、オブジェクトを削除しない限り画像の描画などができなくなってしまうので注意する。

図形 コード
画面塗りつぶし Surface.fill(RGBのタプル)
四角形 pg.draw.rect(画面, 色, (左上x, 左上y, 幅, 高さ))
多角形 pg.draw.polygon(画面, 色, ((x1, y1), (x2, y2)... ), 線幅)
pg.draw.line(画面, 色, (始点x, 始点y), (終点x, 終点y), 線幅)
pg.draw.circle(画面, 色, (中心x, 中心y), 半径, 線幅)
楕円 pg.draw.ellipse(画面, 色, (内接四角形左上x, 左上y, 幅, 高さ), 線幅)
ピクセル指定開始 pixObj = pg.PixelArray(画面)
ピクセル指定色変更 pixObj[x][y] = (R, G, B)
ピクセル指定終了 del pixObj
# 図形の描画
import pygame as pg
import sys, time

pg.init()
GAMEN = pg.display.set_mode((400, 300))
pg.display.set_caption('Hello World!')
GAMEN.fill((0, 120, 120))

pg.draw.rect(GAMEN, (255, 0, 0), (150, 50, 100, 50))
pg.draw.polygon(GAMEN, (255, 255, 0), ((0, 0), (100, 20), (30, 30), (40, 20)))
pg.draw.line(GAMEN, (255, 0, 255), (0, 50), (100, 100), 5)
pg.draw.circle(GAMEN, (0, 255, 0), (200, 200), 50, 5)
pg.draw.ellipse(GAMEN, (0, 255, 255), (300, 100, 50, 150), 3)
pixObj = pg.PixelArray(GAMEN)
for i in range(0, 100, 4):
    pixObj[100][150+i] = (255, 255, 255)
del pixObj

pg.display.update()
time.sleep(5)
pg.quit()
sys.exit()

f:id:tekito-gottani:20190205150843j:plain

色々な図形

図形を動かす

図形を描き、その位置をずらした図形を再度描画することを繰り返せば、図形が動いているように見える。このとき、繰り返しごとに大元画面の塗りつぶし(Surface.fill(色指定))を行わないと、前回描画した画像が残ってしまうので注意する。

# 図形の移動
import pygame as pg
import sys, time

pg.init()
GAMEN = pg.display.set_mode((400, 300))
pg.display.set_caption('Hello World!')
pg.draw.rect(GAMEN, (255, 0, 0), (150, 50, 100, 50))

x = 50
dx = 10
while True:
    GAMEN.fill((0, 120, 120))
    pg.draw.rect(GAMEN, (255, 120, 120), (x, 150, 100, 50))
    pg.display.update()

    if x >= 300 or x <= 0:
        dx = -dx
    x += dx
    time.sleep(0.05)

    for event in pg.event.get():
        if event.type == pg.QUIT:
            pg.quit()
            sys.exit()

f:id:tekito-gottani:20190205151051j:plain

図形の移動

また、Rectオブジェクトのmoveメソッドに移動量を引数にして渡せば、移動した状態のRectオブジェクトが返ってくるので、それを描画すれば同様の移動処理が可能になる。move_ipメソッドの場合はオブジェクトそのものの位置を変更する。これは、オブジェクトのxとyを直接変更していることと同じになる。

操作 コード
Rectオブジェクト作成 robj = pg.Rect(左上x, 左上y, 幅, 高さ)
移動したRectオブジェクト作成 robj = robj.move(x移動量, y移動量)
Rectオブジェクトの(x, y)を変更 robj.move_ip(x移動量, y移動量)
Rectオブジェクトの(x, y)を変更 robj.x += dx; robj.y += dy
# 図形の移動2
robj = pg.Rect(150, 120, 100, 50) # ループ前にオブジェクト作成
dx = 10

while True:
    GAMEN.fill((0, 120, 120))
    pg.draw.rect(GAMEN, (255, 120, 120), robj)
    pg.display.update()

    if robj.x >= 300 or robj.x <= 0:
        dx = -dx

    # 下記3行はすべて同じ意味になる
    robj = robj.move(dx, 0)
    robj.move_ip(dx, 0)
    robj.x += dx

FPSの調整と画像を動かす

画面の更新頻度を調整するためには、FPS(frames per second)設定する。Clockオブジェクトのtickメソッドによって、前回実行されたtickから指定されたFPSの時間を調整してくれる(最初に実行されるtickは時間調整はしない)。

Pygameで画像を表示するときは、最初に画像を読み込んで、その画像の表示位置を指定して大元の画面にblit(コピー)すればよい。画像を動かす場合は、表示位置を変更して再度blitしてupdateすることを繰り返せば動いているように見える。

操作 コード
Clockオブジェクト作成 fpsClock = pg.time.Clock()
FPS指定 FPS = 30
時間の調整 fpsClock.tick(FPS)
画像読み込み img = pg.image.load('画像ファイル') # PNG, JPG, GIF, BMP
画像の画面への描画 GAMEN.blit(img, (左上x, 左上y))

画像ソフトで30×30のボール画像を作成し、バウンドするボールアニメーションを作成すると以下のようになる。画像の代わりに図形の円を使って同じことができる。

# バウンドするボール
import pygame as pg
import sys

pg.init()
FPS = 30
fpsClock = pg.time.Clock()

GAMEN = pg.display.set_mode((400, 300))
pg.display.set_caption('ball')

ball = pg.image.load('03.jpg')
ballx = 100 # ボールの初期位置は必ず0以外にする
bally = 100 # ボールの初期位置は必ず0以外にする
vx = 10
vy = 10

while True:
    GAMEN.fill((255, 255, 255))
    GAMEN.blit(ball, (ballx, bally))

    if ballx <= 0 or ballx >= 400-30:
        vx = -vx
    ballx += vx

    if bally <= 0 or bally >= 300-30:
        vy = -vy
    bally += vy

    for event in pg.event.get():
        if event.type == pg.QUIT:
            pg.quit()
            sys.exit()

    pg.display.update()
    fpsClock.tick(FPS)

f:id:tekito-gottani:20190205151440j:plain

バウンドするボール

文字を表示する

Pygameで文字を表示するためには、フォントオブジェクトを作成し、レンダリングして表示範囲を求め、表示位置を決めてオブジェクトを大元の画面にblitする。

操作 コード
フォントオブジェクト作成 fobj = pg.font.Font(フォント, フォントサイズ)
テキストSurface作成 textSurface = fobj.render(テキスト, True/False, テキスト色, 背景色)
テキスト領域取得 textRect = textSurface.get_rect()
表示位置中心指定 textRect.center = (x, y)
テキスト描画 GAMEN.blit(textSurface, textRect)

テキストSurfaceを作成する際の2番目の引数は、アンチエイリアシングを行うかどうかを真偽値で指定する。下記サンプルで指定しているフォントは、Pygameが持っているフォントで、Pygameがインストールされているフォルダに入っている。

# 文字の表示(カウントアップ)
import pygame as pg
import sys

pg.init()
FPS = 5
fpsClock = pg.time.Clock()
tcolor = (0, 0, 0)
bgcolor = (0, 200, 100)

GAMEN = pg.display.set_mode((400, 300))
pg.display.set_caption('font')

i = 0
while True:
    GAMEN.fill(bgcolor)
    fobj = pg.font.Font('freesansbold.ttf', 50)
    textSurface = fobj.render('{}'.format(i), True, tcolor, bgcolor)
    textRect = textSurface.get_rect()
    textRect.center = (200, 150)
    GAMEN.blit(textSurface, textRect)
    i += 1

    for event in pg.event.get():
        if event.type == pg.QUIT:
            pg.quit()
            sys.exit()

    pg.display.update()
    fpsClock.tick(FPS)

f:id:tekito-gottani:20190205152524j:plain

時間とともに数値が変化

音を鳴らす

Pygameで音(音楽)を再生するためには、サウンドオブジェクトを作成し、playメソッドを使用するだけで良い。音楽を止めるには、stopメソッドを使用する。WAV、OGGファイルを読み込むことができる。

操作 コード
サウンドオブジェクト作成 sobj = pg.mixer.Sound(音楽ファイル)
音を再生 sobj.play()
音を停止 sobj.stop()

playメソッドは、音楽が終わるまで待つことなく次の行へ処理が進むので、効果音などを鳴らすにはちょうど良い。PygameにはBGMを鳴らす関数も準備されていて、手軽にBGMを流すことができる。こちらの関数は、WAV、MP3、MIDIを読み込むことができる。

操作 コード
BGM読み込み pg.mixer.music.load(音楽ファイル)
BGMを再生 pg.mixer.music.play(ループ回数(「-1」でずっと), 再生開始位置)
BGMを停止 pg.mixer.music.stop()

イベント

Pygameでは画面上で発生する様々なイベント(ユーザーによるマウス・キーボード操作など)をイベントオブジェクトとして待ち行列に保存している。何か特定のイベントによってスクリプトの動作を変えたいときは、待ち行列からイベントオブジェクトを取り出して、特定の動作を行ったかどうかを判断する。

イベントオブジェクトは、イベント番号と種類によって表され、辞書型のデータに各種の値を保持している。イベントの種類によって取得できる値は異なっている。

操作 コード
イベントオブジェクト取得 eobj = pg.event.get()
イベントタイプ確認 eobj.type
取得できる値の確認 eobj.dict

イベントタイプは整数値で、Pygameで定義されている大文字のイベント種類(Pygame.QUITなど)と同じ数値である。代表的なイベントオブジェクトと取得できる値は下表の通り。

イベントタイプ 種類 イベント内容 取得できる値
2 KeyDown キーボードを押した mod, unicode, scancode, key
3 KeyUp キーボードを離した mod, scancode, key
4 MouseMotion 画面上をマウスポインタが動いた rel, pos, buttons
5 MouseButtonDown マウスボタンを押した pos, button
6 MouseButtonUp マウスボタンを離した pos, button
12 Quit ウィンドウを閉じた カラの辞書

キーボード系に関しては、「key」の数値がPygameで定義されている「K_xxx」と同じ値なので、これを利用するのが手っ取り早く分かりやすい。「mod」はシフトキーを押しながら別のキーを押したときに数値が変化するようだが詳細は不明。

マウスについては、押したボタンや位置が分かる。ボタンはMouseMotionなら「(左, 中, 右)」のタプル(押すと「1」押さないと「0」)、その他の場合は「左:1、中:2、右:3」の数値で表される。

# ブロックの移動
import pygame as pg
import sys, time

pg.init()
FPS = 5
fpsClock = pg.time.Clock()
bgcolor = (150, 200, 100)

GAMEN = pg.display.set_mode((400, 300))
pg.display.set_caption('event')

x = 0
y = 0
while True:
    GAMEN.fill(bgcolor)

    for event in pg.event.get():
        if event.type == pg.KEYDOWN:
            if event.key == pg.K_UP:
                if y >= 10:
                    y -= 10
            elif event.key == pg.K_DOWN:
                if y <= 280:
                    y += 10
            elif event.key == pg.K_RIGHT:
                if x <= 380:
                    x += 10
            elif event.key == pg.K_LEFT:
                if x >= 10:
                    x -= 10
        elif event.type == pg.QUIT:
            pg.quit()
            sys.exit()

    pg.draw.rect(GAMEN, (255, 120, 120), (x, y, 10, 10))

    pg.display.update()
    fpsClock.tick(FPS)

f:id:tekito-gottani:20190205152347j:plain

矢印キーで四角形が移動

参考