Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

PyGame-CEによるインタラクティブ表現入門その2:PyGame-CEの基本とアニメーション

PyGame-CEプログラムの構成を学ぶ

前回の教材で以下のようなプログラムを作成した.このプログラムの内容を理解していこう.

import pygame

# 初期化
pygame.init()

# 400ピクセル×400ピクセルのウィンドウをつくる
WIDTH, HEIGHT = 400, 400
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("PyGame-CE Program 1: basic drawing")

clock = pygame.time.Clock()
running = True

# メインループ
while running:
    # 1. イベント処理
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False  # ウィンドウ右上の×で終了

    # 2. 背景を塗る
    screen.fill((220, 220, 220))  # R,G,B [0-255]

    # 3. 図形を描く
    # 青い円 (x=200, y=200, 半径30)
    pygame.draw.circle(screen, (0, 0, 255), (200, 200), 30)

    # 赤い四角形 (x=50, y=50, 幅80, 高さ80)
    pygame.draw.rect(screen, (255, 0, 0), (50, 50, 80, 80))

    # 4. 画面を更新
    pygame.display.flip()

    # 5. フレームレート制御 (1秒あたり60フレーム程度)
    clock.tick(60)

# 終了処理
pygame.quit()

メインループの中で円と四角形を描画しているので,つまりこのプログラムは「画面を灰色に塗りつぶしてから,円と四角形を描画する」という操作を何度も繰り返し行っている.しかし円と四角形を描画する位置が変わらないので,事実上,静止画を表示したのと同じことになっている.

座標系について

プログラム内で,円を(200, 200)の位置に,四角形を(50, 50)の位置に描いている.従ってそれぞれの図形は直線y=xy=xの上に乗っているはずである.しかし画面上では図形は左上から右下方向の対角線上に表示され,円は四角形の右上ではなく右に表示される.われわれの馴染んでいるxy座標系から考えると不自然である.これはなぜだろうか.

例に示したプログラムにより描画される図形

Figure 54:例に示したプログラムにより描画される図形

コンピューターグラフィックスでは,以下のような座標系を用いることが多い.

コンピューターグラフィックスで一般的な座標系

Figure 55:コンピューターグラフィックスで一般的な座標系

色の表現について

PyGameでは,色をRGB(アール・ジー・ビー)と呼ばれる方式で表現している。 RGBとは,光の三原色である(Red)・(Green)・(Blue) のそれぞれの明るさを数値で指定して色を作る方法である。PyGameの場合はそれぞれの明るさを0~255の256段階(8ビット)で指定する.

たとえばscreen.fill((220, 220, 220))は,赤・緑・青をすべて「220」という明るめの値にしている。 0〜255の範囲で数値を指定でき,3つの値がすべて同じだと「白から黒までの灰色」になる。 この場合はやや明るい灰色で画面全体を塗りつぶしている。

一方,pygame.draw.circle(screen, (0, 0, 255), (200, 200), 30)(0, 0, 255)は,赤と緑を0(まったく光らせない),青を255(最大の明るさ)にしている。 そのため,この命令は鮮やかな青の円を描くことになる。

このように,RGBの3つの値を変えることで,赤っぽい・緑っぽい・青っぽい色や,中間のさまざまな色を自由に作ることができる。

参考:RapidTables『RGB Color Codes』

さまざまな色で描画した四角形

Figure 56:さまざまな色で描画した四角形

プログラムの改造:マウスのクリック状態取得とスクリーンショット保存機能

今後の演習において画面のスクリーンショットを撮る機能と,マウスの状態を得る機能を入れておくと便利なので,今のうちに入れておく.メインループの前にmouseClickedというブール変数を用意し,またSキーが押されたときにスクリーンショットをscreenshot.pngというファイルに保存する機能を入れる.

# ...(途中省略)
mouseClicked = False

while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        # マウスのクリック状態取得
        elif event.type == pygame.MOUSEBUTTONUP:
            mouseClicked = False
        elif event.type == pygame.MOUSEBUTTONDOWN:
            mouseClicked = True
        # Sキーでスクリーンショット保存
        elif event.type == pygame.KEYDOWN and event.key == pygame.K_s:
            pygame.image.save(screen, "screenshot.png")
            print("Screenshot saved.")
    
    # ...(以下省略)

かんたんなアニメーション

アニメーションとは、少しずつ変化した絵を高速で描き直すことで動いているように見せる仕組みである。 先ほどのプログラムでは,この「描き直し」を1秒間に60回実行している。

円を動かしてみよう.つまり,円を描画する位置をメインループの中で変えてみよう.

x1 = 200
# メインループ
while running:
    # ...(途中省略)

    # 3. 図形を描く
    # 青い円 (x=x1, y=200, 半径30)
    pygame.draw.circle(screen, (0, 0, 255), (x1, 200), 30)

    # x1を更新
    x1 += 1
    if x1 >= WIDTH:
        x1 = 0

これで,青い円が左から右に動くはずである.

さまざまな図形の描画

外周と塗りつぶし

PyGameで図形を描画するとき,内側を塗りつぶさずに外周だけを描きたいときは,外周の太さを指定する.たとえば次のように指定すると,左側には青く塗りつぶされた四角形が,右側には外周のみ青い線で描かれた四角形が表示される.メインループの中で図形を描画しているところを,以下のように変更してみよう.

    pygame.draw.rect(screen, (0, 0, 255), (50, 50, 80, 80))
    pygame.draw.rect(screen, (0, 0, 255), (150, 50, 80, 80), 2)    # 線の太さ=2

このように線の太さを指定しなければ塗りつぶしに,太さを指定すれば外周のみになる.

塗りつぶした四角形と外周のみの四角形

Figure 57:塗りつぶした四角形と外周のみの四角形

外周と塗りつぶしを別の色にしたい場合は,最初に塗りつぶしてから,同じ場所にもういちど外周を描けば良い.

    pygame.draw.rect(screen, (0, 255, 255), (50, 50, 80, 80))     # 水色で塗りつぶし
    pygame.draw.rect(screen, (0, 0, 0), (50, 50, 80, 80), 2)    # 外周は黒い線で描く
塗りつぶしと外周とで異なる色を用いて描画した四角形

Figure 58:塗りつぶしと外周とで異なる色を用いて描画した四角形

円や,このあと紹介する楕円なども同様に塗りつぶしと外周のみの描画を切り替えることができる.

楕円

楕円の描画にはpygame.draw.ellipse(...)を用いる.楕円を囲む四角形の左上隅の座標と,横幅,高さを指定する.rectのときと同様である.

以下に示す例では,座標(200,200)(200, 200)の位置に半径100の円,横幅100,高さ50の矩形(外周のみ),横幅100,高さ50の楕円を描いている.

    pygame.draw.circle(screen, (255, 0, 0), (200, 200), 100)
    pygame.draw.rect(screen, (0, 0, 0), (200, 200, 100, 50), 2)
    pygame.draw.ellipse(screen, (0, 0, 255), (200, 200, 100, 50))
(200, 200)の位置に半径100の円,100×50の四角形(外周のみ),100×50の楕円を描画した例

Figure 59:(200, 200)の位置に半径100の円,100×50の四角形(外周のみ),100×50の楕円を描画した例

円を描画する場合と,四角形や楕円を描画する場合とで書き方が異なることを理解してほしい.円は中心の位置(座標)と半径を,四角形や楕円は左上隅の座標と横幅,高さを指定する.

また,あとから描かれた図形が上に描画されることも理解せよ.描画する順序を逆にすると,円が上から描かれるので四角形や楕円は端部しか見えなくなる.

描画順序を楕円 --> 四角形 --> 円に変えた例.円が重なるため,四角形と楕円は端部しか表示されない.

Figure 60:描画順序を楕円 --> 四角形 --> 円に変えた例.円が重なるため,四角形と楕円は端部しか表示されない.

発展:半透明の図形を描く

先ほどの描画例を改造し,円を半透明にしてみよう.

PyGameでは、画面全体を常時半透明対応にするのではなく、必要な部分だけを半透明に描画する仕組みになっている。これは、ゲームのように毎秒多数の描画を行うリアルタイム処理において、高速化を最優先とした設計である。画面全体で透明度を指定できるように(アルファチャンネル付きに)すると、すべてのピクセルで透明度の計算が必要となり、描画速度が大きく低下する。そのため、PyGameでは基本の表示面は不透明(RGB)とし、半透明を用いる場合は別の透明対応Surfaceを作成して重ねる方式を採用している。こうした設計により、PyGameは処理負荷を抑えつつ、必要な場面でのみ透明表現を実現できるようになっている。

なおPillowのように静止画を生成するプログラムでは、ピクセルごとに色と透明度を指定できるアルファチャンネル付きの表示が一般的である。 これは、静止画の生成では計算速度よりも表現の正確さや画質が重視されるためであり、透明度の処理を行っても性能上の問題が生じにくいためである。

# メインループの外で、アルファチャンネルをサポートしたSurfaceを作る.
surface = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)

while running:
    # ...(途中省略)...

    pygame.draw.ellipse(screen, (0, 0, 255), (200, 200, 100, 50))
    pygame.draw.rect(screen, (0, 0, 0), (200, 200, 100, 50), 2)
    # surfaceを完全透明にクリア
    surface.fill((0, 0, 0, 0))
    # 円を半透明にしたいので,surfaceの上にアルファチャンネルを指定して描画
    pygame.draw.circle(surface, (255, 0, 0, 128), (200, 200), 100)
    # surfaceをscreenに重ねて描画
    screen.blit(surface, (0, 0))
円を半透明にした例.描画順序は楕円 --> 四角形 --> 円だが,円が半透明なので四角形と楕円が透けて見える.

Figure 61:円を半透明にした例.描画順序は楕円 --> 四角形 --> 円だが,円が半透明なので四角形と楕円が透けて見える.

これまでは色の指定に(255, 0, 0)とRGBで指定してきたが,今回は円を描くとき(255, 0, 0, 128)と第4の数値を指定している.これがアルファチャンネルで,透明度を表す.0だと完全に透明,255だと不透明である.透明度を変えながら図形を描いてみよう.

    # はじめに細長い四角形を描画しておく.透明度を確認するため.
    pygame.draw.rect(screen, (0, 0, 255), (0, 195, 400, 10))
    # 円を透明度を変えつつ複数描画
    pygame.draw.circle(surface, (255, 0, 0, 0), (100, 200), 20)
    pygame.draw.circle(surface, (255, 0, 0, 64), (150, 200), 20)
    pygame.draw.circle(surface, (255, 0, 0, 128), (200, 200), 20)
    pygame.draw.circle(surface, (255, 0, 0, 192), (250, 200), 20)
    pygame.draw.circle(surface, (255, 0, 0, 255), (300, 200), 20)
    # 最初の円が見えなくなる(はずな)ので,外周を黒で描画しておく
    pygame.draw.circle(surface, (0, 0, 0, 255), (100, 200), 20, 1)
    screen.blit(surface, (0, 0))
円を透明度を変えながら表示している例.左から0,64,128,192,255と設定している.透明度0の円は表示されないので,そこだけ外枠を描画している.

Figure 62:円を透明度を変えながら表示している例.左から0,64,128,192,255と設定している.透明度0の円は表示されないので,そこだけ外枠を描画している.

多角形

三角形や五角形,星形など多角形(凹多角形を含む)を描画するには,pygame.draw.polygon(...)を用いる.点の座標をリストで与える.

    pygame.draw.polygon(screen, (255, 0, 0), [(100, 50), (50, 150), (150, 150)])
    pygame.draw.polygon(screen, (0, 0, 255), [(250, 150), (200, 250), (300, 250), (325, 200)])
多角形を描画している例.この例では三角形と不等辺の四角形を描画している.

Figure 63:多角形を描画している例.この例では三角形と不等辺の四角形を描画している.

星形を描いてみよう.はじめは頂点の5点を座標で指定し,外形を描いてみる.

    pygame.draw.polygon(screen, (0, 0, 0), [(200, 50), (260, 300),
        (100, 150), (300, 150), (140, 300)], 1) 
星形を描画(外形線のみ)

Figure 64:星形を描画(外形線のみ)

黒線で星形が描かれるはずである.今度はこれを塗りつぶしてみよう.

    pygame.draw.polygon(screen, (255, 255, 0), [(200, 50), (260, 300),
        (100, 150), (300, 150), (140, 300)]) 
    pygame.draw.polygon(screen, (0, 0, 0), [(200, 50), (260, 300), (100, 150),
        (300, 150), (140, 300)], 1) 
星形を描画(塗りつぶしあり)

Figure 65:星形を描画(塗りつぶしあり)

中央部分が塗りつぶされていない.実際に動かして確認してみよ.

これはコンピューターグラフィックスの塗りつぶし動作で一般的な奇偶ルール(even-odd rule)に基づいて塗りつぶされているからである.奇偶ルールは、多角形の内部を決定するための方法の一つであり、コンピューターグラフィックスの分野で広く用いられている。

この規則では、ある点から無限遠に線を引いたときに図形の辺と交わる回数が奇数であればその点は内部、偶数であれば外部と判断される。 SVG、PDF、PostScriptなど多くの描画システムでも採用されており、PyGameの多角形描画もこの方式に基づいている。

星形に関して言うと,内側の点から外に向かって線を引くと,星形の中央の五角形部分では外形線と必ず2回交差する.(五角形の頂点を通れば1回に見えるかもしれないが,そこは2本の外形線が重なっている.)従って中央の五角形部分が内側だと見做されないのである.

内側も含めて塗りつぶしたいときは,五角形の頂点(星形から見ると凹になっている頂点)も含めて10点の頂点を指定するのが確実である.

    pygame.draw.polygon(screen, (255, 255, 0), [(200, 50), (220, 150),
        (300, 150), (240, 200), (260, 300), (200, 240),
        (140, 300), (160, 200), (100, 150), (180, 150)])
    pygame.draw.polygon(screen, (0, 0, 0), [(200, 50), (220, 150),
        (300, 150), (240, 200), (260, 300), (200, 240),
        (140, 300), (160, 200), (100, 150), (180, 150)], 1)
星形を描画(凹の頂点を含め10点を指定)

Figure 66:星形を描画(凹の頂点を含め10点を指定)

線分と折れ線

線分と折れ線を描いてみよう.それぞれpygame.draw,line(...)pygame.draw.lines(...)を用いる.線分では始点と終点の2点を並べて,折れ線では接続する点の座標をリストとして指定する.

        pygame.draw.line(screen, (255, 0, 0), (50, 50), (350, 350), 3)  # 線分
        pygame.draw.lines(screen, (0, 0, 255), False, [(50, 350), (150, 250),
            (250, 350), (350, 250)], 2)  # 折れ線
線分と折れ線を描画

Figure 67:線分と折れ線を描画

pygame.draw.linesの3番目の引数にFalseと書いてあるが,これは折れ線を閉じるか否か,つまり,折れ線の先端の点と末尾の点とを線で結ぶかを指定する.線で結んだ場合は,pygame.draw.polygonで外形線を描いたときと同じように動作する.ただしpygame.draw.linesには内側を塗りつぶす機能は無い.これも試してみよう.

        pygame.draw.lines(screen, (0, 0, 255), True, [(50, 150), (150, 50),
            (250, 150), (350, 50)], 2)  # lines
        pygame.draw.polygon(screen, (0, 0, 255), [(50, 250), (150, 150),
            (250, 250), (350, 150)], 2)  # polygon, 外形線のみ
        pygame.draw.polygon(screen, (0, 0, 255), [(50, 350), (150, 250),
            (250, 350), (350, 250)])  # polygon 塗りつぶし
上からlines, polygon(外形線のみ),polygon(塗りつぶし)で描画した図形

Figure 68:上からlines, polygon(外形線のみ),polygon(塗りつぶし)で描画した図形

描画を繰り返す

Pythonのプログラムなので,描画も当然ループを使って繰り返したり,条件分岐で色を変えたりすることができる.

    for i in range(10):
        pygame.draw.rect(screen, (0, 255 - i * 20, i * 20), (20 + i * 30, 20 + i * 30, 80, 80))
位置と色を変えながら連続して描いた四角形の例

Figure 69:位置と色を変えながら連続して描いた四角形の例

    pygame.draw.rect(screen, (0, 0, 240), (40, 10, 100, 380))
    # 長い斜め四角形
    slide = 18
    for i in range(3):
        pygame.draw.polygon(screen, (255, 200, 0),
                            [(40 + i * slide, 260 + i * slide), (140 + i * slide, 160 + i * slide),
                                (150 + i * slide, 170 + i * slide), (50 + i * slide, 270 + i * slide)])
    # 短い斜め四角形
    for i in range(3):
        pygame.draw.polygon(screen, (255, 200, 0),
                            [(40 + (i+3) * slide, 260 + (i+3) * slide), (90 + (i+3) * slide, 210 + (i+3) * slide),
                                (100 + (i+3) * slide, 220 + (i+3) * slide), (50 + (i+3) * slide, 270 + (i+3) * slide)])
位置を変えながら連続して描いた多角形の例

Figure 70:位置を変えながら連続して描いた多角形の例

ランダムに図形を描く―randomの利用

ゲームやインタラクティブアートのプログラミングでは,常に同じ場所に図形を描画するのではなく,ランダムに位置や色を変えたほうが良い場合がある.ここでは乱数を用いてランダムな位置に図形を描いてみよう.

randomモジュールをインポートすると,整数の一様乱数random.randintを使えるようになるので,これを用いる.

import random

while running:
    # ...(途中省略)
    for i in range(50):
        x = random.randint(0, WIDTH)
        y = random.randint(0, HEIGHT)
        radius = random.randint(10, 50)
        color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
        pygame.draw.circle(screen, color, (x, y), radius)
    # ...(以下省略)

random.randint(0, 255)は0~255の範囲の整数乱数を生成する.

このプログラムを実行すると,フレームごとに円をランダムに描画するので,多数の円が高速に表示されたり消えたりするように見えるはずである.

ランダムに表示された円の例.色も位置もランダムに決められている.この画面は静止画だが,実際にはこのような画面が高速に切り替わりながら表示される.

Figure 71:ランダムに表示された円の例.色も位置もランダムに決められている.この画面は静止画だが,実際にはこのような画面が高速に切り替わりながら表示される.

マウスカーソルの位置に円を描く

「インタラクティブだ」と感じてもらうために,マウスの動きに追随する円を描画してみよう. マウスカーソルの位置はmx, my = pygame.mouse.get_pos()で得られるので,その位置に円を描くと良い.

    # マウス位置を取得
    mx, my = pygame.mouse.get_pos()

    # マウスのところに青い円
    pygame.draw.circle(screen, (0, 0, 255), (mx, my), 20)

このプログラムを実行すると,マウスポインタを追いかける青い円が表示されるはずである. これにより「リアルタイム入力 → 描画」というゲームやインタラクティブアートの基本となる機能が使えるようになった.

課題(LMS提出)

ライセンス