Pygame入門: draw.linesで自由自在な線を描画する方法

2025-05-27

pygame.draw.lines の基本的な使い方

pygame.draw.lines は以下の形式で使用します。

pygame.draw.lines(surface, color, closed, points, width=1)

それぞれの引数の意味は以下の通りです。

  1. surface: 線を描画する対象のSurfaceオブジェクトです。通常は pygame.display.set_mode() で作成したスクリーンオブジェクトになります。

  2. color: 線の色を指定します。RGB形式のタプル (R, G, B) で指定します。例えば、赤は (255, 0, 0)、青は (0, 0, 255) となります。

  3. closed: ブール値(True または False)で、線が閉じた図形になるかどうかを指定します。

    • True の場合: points リストの最後の点と最初の点が結ばれ、閉じた図形(多角形など)が描画されます。
    • False の場合: points リストの最後の点と最初の点は結ばれず、開いた線が描画されます。
  4. points: 線の頂点の座標リストです。各座標は (x, y) 形式のタプルまたはリストで、これを複数格納したリスト(例: [(x1, y1), (x2, y2), (x3, y3)])を指定します。少なくとも2つ以上の点が必要です。

  5. width (オプション): 線の太さをピクセル単位で指定します。デフォルトは 1 です。

簡単な使用例を見てみましょう。

import pygame

# Pygameの初期化
pygame.init()

# 画面の設定
screen_width = 800
screen_height = 600
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("Pygame draw.lines の例")

# 色の定義
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
GREEN = (0, 255, 0)

# 描画する点のリスト
# 開いた線(ジグザグ線)
points_open = [
    (100, 100),
    (200, 50),
    (300, 150),
    (400, 100)
]

# 閉じた線(三角形)
points_closed = [
    (500, 100),
    (600, 200),
    (500, 300)
]

# メインループ
running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    # 画面を黒で塗りつぶし
    screen.fill(BLACK)

    # 開いた線を描画
    pygame.draw.lines(screen, RED, False, points_open, 3)

    # 閉じた線(三角形)を描画
    pygame.draw.lines(screen, BLUE, True, points_closed, 5)
    
    # より複雑な閉じた線(多角形)
    points_polygon = [
        (100, 400),
        (200, 450),
        (250, 550),
        (150, 500)
    ]
    pygame.draw.lines(screen, GREEN, True, points_polygon, 2)


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

pygame.quit()
  • 柔軟な形状: 任意の数の点を指定することで、複雑な折れ線グラフや図形を表現できます。
  • 多角形のアウトライン: closed=True を指定することで、多角形の外枠を簡単に描画できます。
  • 複数の線分を一度に描画: pygame.draw.line を繰り返し呼び出すよりも効率的です。


線が画面に表示されない

これは pygame.draw.lines の最も一般的なトラブルシューティング項目です。

原因と解決策

  • 線が画面外に描画されている
    points リスト内の座標が画面の幅 (screen_width) や高さ (screen_height) の範囲外にある場合、当然線は表示されません。座標が適切か確認してください。デバッグのために、小さな座標で試してみるのが有効です。

  • 線の色が背景色と同じ
    線の色と背景色が同じだと、線は表示されません。色のRGB値が正しいか確認してください。

  • 背景で上書きされている
    ゲームループ内で毎フレーム screen.fill() で背景を塗りつぶしている場合、その後に線を再描画しないと、線は次のフレームで消えてしまいます。screen.fill()後に pygame.draw.lines を呼び出すようにしてください。

    while running:
        # イベント処理
    
        screen.fill(BLACK) # 背景を塗りつぶす
        pygame.draw.lines(screen, RED, False, points) # 線を描画する
        pygame.display.flip()
    
  • 描画対象のSurfaceが間違っている
    pygame.draw.lines の最初の引数に、表示したいSurface(通常は screen オブジェクト)を渡しているか確認してください。別のSurfaceに描画してから blit していない場合、表示されません。

    # 間違いの例: screenではなく、別のSurfaceに描画している
    # temp_surface = pygame.Surface((100, 100))
    # pygame.draw.lines(temp_surface, RED, False, points)
    
    # 正しい例: screenに直接描画
    pygame.draw.lines(screen, RED, False, points)
    
  • pygame.display.flip() または pygame.display.update() の呼び忘れ
    Pygameで描画した内容は、これらの関数を呼び出すまで画面に表示されません。描画処理の後、必ずこれらのいずれかを呼び出してください。

    # 描画後
    pygame.display.flip() # または pygame.display.update()
    

TypeError または ValueError

引数の型や値が正しくない場合に発生します。

原因と解決策

  • width 引数が整数でない、または負の値
    線の太さは正の整数で指定します。

    # NG
    # width = 1.5 # TypeError
    # width = -1 # ValueError
    
    # OK
    width = 1
    width = 5
    
  • closed 引数がブール値でない
    True または False 以外の値を渡すとエラーになります。

    # NG
    # closed = 1 # TypeError
    
    # OK
    closed = True
    
  • color がタプルでない、または要素数が不正
    色は (R, G, B) または (R, G, B, A) のタプルである必要があります。

    # NG
    # color = "red" # TypeError
    # color = [255, 0, 0] # Pygameではタプルが推奨される (リストでも動く場合があるが、一貫性のためタプルを)
    # color = (255, 0) # TypeError
    
    # OK
    color = (255, 0, 0)
    
  • points リストに2つ以上の点がない
    線を引くためには少なくとも2つの点が必要です。点が1つしかない場合や空のリストの場合、ValueError: points must have at least 2 points が発生します。

    # NG
    # points = [(100, 100)] # ValueError
    # points = [] # ValueError
    
    # OK
    points = [(100, 100), (200, 200)]
    
  • points リストの形式が不正
    points 引数は、(x, y) の形式のタプルまたはリストを要素とするリストである必要があります。例えば、[(10, 20), (30, 40), (50, 60)] のような形式です。数値以外のものが含まれていたり、タプルの要素数が2以外だったりすると TypeError が発生します。

    # OK
    points = [(100, 100), (200, 200)]
    points = [[100, 100], [200, 200]]
    
    # NG: floatが含まれている (整数に変換する)
    # points = [(100.5, 100), (200, 200)] # TypeError
    
    # NG: タプルの要素数が違う
    # points = [(100, 100, 50), (200, 200)] # TypeError
    

線が途切れる、または期待通りの太さにならない

線のレンダリングに関する問題です。

原因と解決策

  • アンチエイリアシングの欠如
    pygame.draw.lines はアンチエイリアシング(ギザギザの軽減)を行いません。そのため、線はギザギザに見えることがあります。滑らかな線が必要な場合は、pygame.draw.aaline (単一のアンチエイリアシングされた線) や、より複雑な描画ライブラリ(pygame.gfxdraw など)を検討してください。ただし、pygame.draw.aalinewidth を指定できません。

  • 線の太さの解釈
    pygame.draw.lines (および pygame.draw.line) の width 引数は、線の中心から両側に太さが適用されます。例えば width=1 なら1ピクセル、width=2 なら中心線から上下左右に0.5ピクセルずつ太さが適用されます。奇数の太さ (width=3 など) は中心ピクセルを持ちますが、偶数の太さ (width=2 など) は中心ピクセルを持ちません。これにより、特定の座標で期待と異なる見た目になることがあります。

    • 解決策
      ピクセル単位で厳密な描画が必要な場合、width の値を試行錯誤するか、より低レベルな描画方法(例えば pygame.Surface を使って自分でピクセルを描画する)を検討する必要があるかもしれません。しかし、ほとんどのケースでは width で十分です。

非常に多くの線を頻繁に描画する場合に発生する可能性があります。

原因と解決策

  • 毎フレームの再計算と再描画
    何万もの線や複雑な多角形を毎フレーム描画し直している場合、パフォーマンスが低下することがあります。

    • 解決策1: 変更がない部分は一度だけ描画する
      静的な背景や動かないオブジェクトの線は、専用の pygame.Surface に一度だけ描画し、それをメインのスクリーンに blit するようにします。これにより、毎フレーム線を描画し直す必要がなくなります。

      static_lines_surface = pygame.Surface(screen.get_size(), pygame.SRCALPHA) # アルファチャンネル対応
      pygame.draw.lines(static_lines_surface, GRAY, False, static_points)
      
      # メインループ内で
      screen.fill(BLACK)
      screen.blit(static_lines_surface, (0, 0))
      pygame.display.flip()
      
    • 解決策2: 描画する線の数を減らす
      画面に表示されていない線や、非常に小さい線など、描画する必要のない線をフィルタリングして描画数を減らします。

    • 解決策3: ハードウェアアクセラレーションの利用
      一部のPygameの機能はハードウェアアクセラレーションを利用できますが、pygame.draw 関数は主にCPUベースの描画です。より高性能な描画が必要な場合は、OpenGLなど他のグラフィックスライブラリとの連携を検討するかもしれません(ただし、これはPygameの範疇を大きく超えます)。

pygame.draw.lines のトラブルシューティングのほとんどは、以下の点を確認することで解決できます。

  1. 描画関数が正しく呼び出されているか? (surface, color, closed, points, width の引数を確認)
  2. points リストの形式が正しいか? (要素が (x, y) タプルであること、2つ以上の点があること)
  3. pygame.display.flip() または update() を呼び出しているか?
  4. 線が背景で上書きされていないか、画面外に出ていないか?
  5. 線の色と背景色が同じではないか?


例1:基本的な線(開いた線と閉じた線)の描画

最も基本的な使い方です。開いた線(折れ線)と閉じた線(多角形のアウトライン)を描画します。

import pygame

# Pygameの初期化
pygame.init()

# 画面の設定
screen_width = 800
screen_height = 600
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("基本的な draw.lines の例")

# 色の定義 (R, G, B)
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
GREEN = (0, 255, 0)
YELLOW = (255, 255, 0)

# 描画する点のリスト
# 開いた線(ジグザグ線)の点
points_open_line = [
    (100, 100),  # 点1
    (250, 50),   # 点2
    (400, 150),  # 点3
    (550, 100)   # 点4
]

# 閉じた線(三角形)の点
points_closed_triangle = [
    (150, 300),  # 点1
    (300, 450),  # 点2
    (150, 450)   # 点3
]

# 閉じた線(四角形)の点
points_closed_square = [
    (500, 300),  # 左上
    (700, 300),  # 右上
    (700, 500),  # 右下
    (500, 500)   # 左下
]

# メインゲームループ
running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    # 画面を黒で塗りつぶす (毎フレーム描画内容をリセット)
    screen.fill(BLACK)

    # 開いた線を描画 (太さ3ピクセル)
    pygame.draw.lines(screen, RED, False, points_open_line, 3)

    # 閉じた線(三角形)を描画 (太さ5ピクセル)
    pygame.draw.lines(screen, BLUE, True, points_closed_triangle, 5)

    # 閉じた線(四角形)を描画 (太さ2ピクセル)
    pygame.draw.lines(screen, YELLOW, True, points_closed_square, 2)
    
    # 描画された内容を画面に表示
    pygame.display.flip()

# Pygameの終了
pygame.quit()

解説

  • pygame.display.flip(): 実際に描画した内容を画面に表示します。これを忘れると、何も表示されません。
  • pygame.draw.lines(surface, color, closed, points, width): この関数が線の描画を行います。
    • screen: 描画先のSurface(画面)
    • RED, BLUE, YELLOW: 線の色
    • False (points_open_line): 最初の点と最後の点が結ばれないため、開いた線になります。
    • True (points_closed_triangle, points_closed_square): 最初の点と最後の点が結ばれるため、閉じた図形のアウトラインになります。
    • points_...: 線の頂点となる点のリストです。各点は (x, y) のタプルで指定します。
    • 3, 5, 2: 線の太さ(ピクセル単位)です。
  • screen.fill(BLACK): 毎フレーム、画面を黒で塗りつぶしています。これにより、前のフレームの描画内容が消去され、新しい内容だけが表示されます。
  • screen = pygame.display.set_mode((screen_width, screen_height)): 描画するキャンバス(画面)を作成します。
  • pygame.init()pygame.quit(): Pygameの初期化と終了処理は常に必要です。

例2:マウスで線を描く(動的な点の追加)

ユーザーの操作によって点が追加され、線が動的に描画される例です。

import pygame

pygame.init()

screen_width = 800
screen_height = 600
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("マウスで線を描く例")

BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)

# 描画する点のリスト
# ここにマウスでクリックした座標を追加していく
drawing_points = []
# 線が閉じるかどうか
is_closed_shape = False

# メインゲームループ
running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.MOUSEBUTTONDOWN:
            # マウスクリックで点を追加
            drawing_points.append(event.pos) # event.pos はクリックされた座標 (x, y)
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_c:
                # 'C' キーで線を閉じる/開くを切り替え
                is_closed_shape = not is_closed_shape
                print(f"閉じた線モード: {is_closed_shape}")
            elif event.key == pygame.K_r:
                # 'R' キーで点をリセット
                drawing_points = []
                print("点をリセットしました。")

    screen.fill(BLACK)

    # 2つ以上の点があれば線を描画
    if len(drawing_points) >= 2:
        pygame.draw.lines(screen, GREEN, is_closed_shape, drawing_points, 2)
    elif len(drawing_points) == 1:
        # 点が1つの場合は、カーソル位置と最初の点を結ぶ補助線を描画(オプション)
        pygame.draw.line(screen, WHITE, drawing_points[0], pygame.mouse.get_pos(), 1)


    # 各点の位置を小さく表示 (デバッグ用)
    for point in drawing_points:
        pygame.draw.circle(screen, RED, point, 3) # 各点を赤い点で表示

    pygame.display.flip()

pygame.quit()

解説

  • pygame.draw.circle(screen, RED, point, 3): 描画された各点を小さな赤い円で表示し、視覚的にわかりやすくしています。
  • if len(drawing_points) >= 2:: pygame.draw.lines は少なくとも2つの点が必要なので、点が2つ以上ある場合のみ描画します。
  • pygame.KEYDOWN イベント: キーが押されたときに発生します。
    • pygame.K_c: 'C' キーを押すと、is_closed_shape のブール値を反転させ、線が閉じるか開くかを切り替えます。
    • pygame.K_r: 'R' キーを押すと、drawing_points を空にして描画をリセットします。
  • pygame.MOUSEBUTTONDOWN イベント: マウスがクリックされたときに発生します。event.pos でクリックされた座標を取得し、drawing_points に追加します。
  • drawing_points = []: マウスクリックで追加される座標を格納するリストです。

draw.lines を使って多角形(閉じた線)を描画し、さらにその多角形を回転させるアニメーションの例です。

import pygame
import math

pygame.init()

screen_width = 800
screen_height = 600
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("多角形の回転アニメーション")

BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
PURPLE = (128, 0, 128)

# 多角形の中心座標
center_x, center_y = screen_width // 2, screen_height // 2

# 多角形の頂点(初期状態、中心からの相対座標)
# ここでは正五角形を作成
num_sides = 5
radius = 150
initial_polygon_points_relative = []
for i in range(num_sides):
    angle = 2 * math.pi * i / num_sides
    x = radius * math.cos(angle)
    y = radius * math.sin(angle)
    initial_polygon_points_relative.append((x, y))

# 回転角度
rotation_angle = 0

# メインゲームループ
running = True
clock = pygame.time.Clock() # フレームレート制御用

while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    screen.fill(BLACK)

    # 頂点を回転させて絶対座標に変換
    rotated_polygon_points = []
    for rel_x, rel_y in initial_polygon_points_relative:
        # 回転行列を適用
        # x' = x * cos(theta) - y * sin(theta)
        # y' = x * sin(theta) + y * cos(theta)
        rotated_x = rel_x * math.cos(rotation_angle) - rel_y * math.sin(rotation_angle)
        rotated_y = rel_x * math.sin(rotation_angle) + rel_y * math.cos(rotation_angle)
        
        # 中心座標を加えて絶対座標に変換
        abs_x = int(rotated_x + center_x)
        abs_y = int(rotated_y + center_y)
        rotated_polygon_points.append((abs_x, abs_y))

    # 回転した多角形を描画
    # points_closed_polygon が少なくとも2つの点を持つことを確認
    if len(rotated_polygon_points) >= 2:
        pygame.draw.lines(screen, PURPLE, True, rotated_polygon_points, 3)

    # 回転角度を更新
    rotation_angle += 0.02 # 0.02ラジアンずつ増やす (約1.15度)

    pygame.display.flip()
    clock.tick(60) # 1秒間に60フレームに制限

pygame.quit()
  • clock.tick(60): ゲームのフレームレートを60FPSに制限し、アニメーションの速度を一定に保ちます。
  • rotation_angle += 0.02: 毎フレーム、回転角度を少しずつ増加させ、アニメーションさせます。
  • abs_x, abs_y: 回転後の相対座標に多角形の中心座標 (center_x, center_y) を加えることで、画面上の絶対座標に変換します。int() で整数に丸めることを忘れないでください。
  • rotated_x, rotated_y: 各頂点を現在の rotation_angle で回転させた後の相対座標を計算します。これは2D回転の一般的な公式です。
  • rotation_angle: 多角形の現在の回転角度を保持します。
  • initial_polygon_points_relative: 多角形の頂点を、中心 (0, 0) からの相対座標で定義します。これにより、回転計算が簡単になります。ここでは、正五角形を作成しています。
  • import math: 回転計算のために math モジュールをインポートします。


pygame.draw.line を繰り返し呼び出す

pygame.draw.lines は複数の点を結ぶ線分を一括で描画しますが、個々の線分を pygame.draw.line で描画することも可能です。


import pygame

pygame.init()
screen = pygame.display.set_mode((400, 300))
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
BLUE = (0, 0, 255)

points = [(50, 50), (150, 100), (250, 50), (350, 100)]

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    screen.fill(BLACK)

    # points_open_line を draw.line で描画する場合
    for i in range(len(points) - 1):
        pygame.draw.line(screen, BLUE, points[i], points[i+1], 3)

    pygame.display.flip()
pygame.quit()

利点

  • 各線分に対して個別に色や太さを設定したい場合に柔軟性が高い。
  • pygame.draw.lines を使わない場合に、単一の線分を描画する基本的な方法。

欠点

  • closed=True のような機能がないため、閉じた図形を同じように描画するには追加のコードが必要。
  • pygame.draw.lines よりも処理が遅くなる可能性がある(特に線分が多い場合)。

アンチエイリアシングされた線を描画する (pygame.draw.aaline, pygame.draw.aalines)

pygame.draw.lines はギザギザした線(エイリアシング)を描画します。より滑らかな線が必要な場合、アンチエイリアシングに対応した関数を使用します。

pygame.draw.aaline (単一のアンチエイリアシングされた線)

import pygame

pygame.init()
screen = pygame.display.set_mode((400, 300))
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    screen.fill(BLACK)

    # ギザギザの線
    pygame.draw.line(screen, WHITE, (50, 50), (350, 150), 1)
    # 滑らかな線 (アンチエイリアシング)
    pygame.draw.aaline(screen, GREEN, (50, 150), (350, 250))

    pygame.display.flip()
pygame.quit()

pygame.draw.aalines (複数のアンチエイリアシングされた線)
pygame.draw.lines と同じように複数の点を結ぶ線をアンチエイリアシング付きで描画します。

import pygame

pygame.init()
screen = pygame.display.set_mode((400, 300))
BLACK = (0, 0, 0)
BLUE = (0, 0, 255)

points = [(50, 50), (150, 100), (250, 50), (350, 100)]
closed_points = [(100, 150), (200, 250), (100, 250)]

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    screen.fill(BLACK)

    # アンチエイリアシングされた開いた線
    pygame.draw.aalines(screen, BLUE, False, points)
    # アンチエイリアシングされた閉じた線 (三角形)
    pygame.draw.aalines(screen, GREEN, True, closed_points)

    pygame.display.flip()
pygame.quit()

利点

  • 線が滑らかに表示され、見た目が向上する。特に低解像度や斜めの線で効果的。

欠点

  • 描画に若干のCPU負荷がかかる可能性がある。
  • pygame.draw.aaline および pygame.draw.aalines は、width 引数をサポートしていません(太い線を描画できません)。常に1ピクセルの細い線になります。

pygame.gfxdraw モジュールを使用する

pygame.gfxdraw モジュールは、より高度な描画機能を提供します。これには、アンチエイリアシングされた多角形、円、線などを描画する関数が含まれています。pygame.draw よりも柔軟性がありますが、公式ドキュメントでは「実験的」とされています。

アンチエイリアシングされた太い線や多角形
pygame.gfxdraw.aapolygon を使用すると、アンチエイリアシングされた多角形を描画できます。太い線は、小さな多角形として描画することでシミュレートできます。

import pygame
import pygame.gfxdraw # gfxdraw モジュールをインポート

pygame.init()
screen = pygame.display.set_mode((400, 300))
BLACK = (0, 0, 0)
MAGENTA = (255, 0, 255)

# 太い線を多角形として表現
# 例えば、太さ10ピクセルの線を2つの点で定義し、それを多角形に変換
def draw_thick_aaline(surface, color, start_pos, end_pos, width):
    # 線の中間点を計算し、垂直方向にオフセットして4つの頂点を持つ多角形を形成
    dx = end_pos[0] - start_pos[0]
    dy = end_pos[1] - start_pos[1]
    
    # 垂直方向の単位ベクトルを計算
    length = math.sqrt(dx*dx + dy*dy)
    if length == 0: return # 点が同じなら描画しない

    nx = -dy / length * (width / 2)
    ny = dx / length * (width / 2)

    p1 = (start_pos[0] + nx, start_pos[1] + ny)
    p2 = (start_pos[0] - nx, start_pos[1] - ny)
    p3 = (end_pos[0] - nx, end_pos[1] - ny)
    p4 = (end_pos[0] + nx, end_pos[1] + ny)

    # 4つの頂点を閉じた多角形として描画
    pygame.gfxdraw.aapolygon(surface, [p1, p2, p3, p4], color)
    pygame.gfxdraw.filled_polygon(surface, [p1, p2, p3, p4], color)


running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    screen.fill(BLACK)

    # draw_thick_aaline を使って太くて滑らかな線を描画
    draw_thick_aaline(screen, MAGENTA, (50, 50), (350, 150), 10)
    draw_thick_aaline(screen, (0, 200, 200), (50, 250), (350, 50), 5)


    # gfxdraw を使ったアンチエイリアシングされた円 (太さは指定できない)
    pygame.gfxdraw.aacircle(screen, 100, 200, 30, WHITE)
    pygame.gfxdraw.filled_circle(screen, 100, 200, 30, WHITE)


    pygame.display.flip()
pygame.quit()

利点

  • ピクセル単位での描画など、より細かい制御が可能。
  • pygame.draw よりも多くのアンチエイリアシング機能を提供。

欠点

  • 厚いアンチエイリアシングされた線を描画するための直接的な関数がないため、多角形としてシミュレートする必要がある。
  • gfxdraw モジュールは「実験的」であるため、将来のPygameバージョンで変更される可能性がある。
  • pygame.draw よりも低レベルであり、使い方が複雑になることがある。

pygame.Surface に描画し、blit する

複雑な図形や、何度も再描画する必要のない静的な線などは、別の pygame.Surface オブジェクトに一度だけ描画し、それをメインのスクリーンに blit (コピー) する方が効率的です。

import pygame

pygame.init()
screen_width = 800
screen_height = 600
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("Surfaceに描画してblitする例")

BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)

# 静的な背景オブジェクトを作成
static_surface = pygame.Surface((screen_width, screen_height), pygame.SRCALPHA) # SRCALPHAで透明度をサポート

# static_surface に一度だけ線を描画
static_points = [
    (100, 100),
    (200, 200),
    (100, 300),
    (200, 400)
]
pygame.draw.lines(static_surface, WHITE, False, static_points, 2)

# 回転する線のための設定
rotating_points = [
    (50, 50), (150, 50), (150, 150), (50, 150) # 正方形
]
rotation_angle = 0

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

while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    screen.fill(BLACK)

    # 静的な線をblitで描画 (毎フレーム再描画する必要がない)
    screen.blit(static_surface, (0, 0))

    # 回転する線(動的なオブジェクト)
    rotated_points = []
    center_x, center_y = 600, 300 # 回転の中心
    for px, py in rotating_points:
        # 相対座標に変換
        rel_x, rel_y = px - center_x, py - center_y
        # 回転
        cos_a = math.cos(math.radians(rotation_angle))
        sin_a = math.sin(math.radians(rotation_angle))
        new_rel_x = rel_x * cos_a - rel_y * sin_a
        new_rel_y = rel_x * sin_a + rel_y * cos_a
        # 絶対座標に戻す
        rotated_points.append((int(new_rel_x + center_x), int(new_rel_y + center_y)))

    if len(rotated_points) >= 2:
        pygame.draw.lines(screen, GREEN, True, rotated_points, 4)

    rotation_angle += 1 # 角度を増やす
    if rotation_angle >= 360:
        rotation_angle = 0

    pygame.display.flip()
    clock.tick(60)

pygame.quit()

利点

  • アルファブレンド (透明度)
    pygame.SRCALPHA フラグを使用してSurfaceを作成すると、透明度を持つ線や図形を描画し、背景とブレンドすることができます。
  • モジュール化
    ゲームの各要素(背景、キャラクター、UIなど)を個別のSurfaceに描画し、それらを組み合わせて表示することで、コードの管理がしやすくなります。
  • パフォーマンス向上
    静的な、あるいは頻繁に変更されない複雑な図形を一度だけ描画し、後は高速な blit で表示できるため、CPU負荷を軽減できます。

欠点

  • 追加の Surface オブジェクトを管理する必要がある。
  • 動的に変化するオブジェクトの場合、結局毎フレーム再描画が必要になるため、大きな利点はない。

pygame.draw.lines は多角形のアウトラインを描画しますが、pygame.draw.polygon を使用すると、塗りつぶされた多角形を描画できます。width 引数を指定すると、アウトラインのみを描画することも可能です(pygame.draw.lines と同じような見た目になります)。

import pygame

pygame.init()
screen = pygame.display.set_mode((400, 300))
BLACK = (0, 0, 0)
CYAN = (0, 255, 255)
MAGENTA = (255, 0, 255)

triangle_points = [(100, 50), (50, 150), (150, 150)]
square_points = [(250, 50), (350, 50), (350, 150), (250, 150)]

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    screen.fill(BLACK)

    # 塗りつぶされた三角形
    pygame.draw.polygon(screen, CYAN, triangle_points)

    # アウトラインのみの四角形 (width > 0)
    pygame.draw.polygon(screen, MAGENTA, square_points, 5)

    pygame.display.flip()
pygame.quit()

利点

  • アウトライン描画と塗りつぶしが同じ関数で制御できる。
  • 多角形を簡単に塗りつぶすことができる。

欠点

  • pygame.draw.lines と同様に、アンチエイリアシングはデフォルトでは適用されない。

どの方法を選択するかは、描画したい線の特性(太さ、滑らかさ、静的か動的か、塗りつぶしの必要性など)によって異なります。

  • 静的な図形のパフォーマンス最適化
    pygame.Surface に一度描画し、blit する。
  • 塗りつぶされた多角形
    pygame.draw.polygon
  • 滑らかな太い線や複雑な図形
    pygame.gfxdraw を使った多角形シミュレーション、または pygame.Surface に描画して blit
  • 滑らかな細い線
    pygame.draw.aaline または pygame.draw.aalines
  • 基本的な折れ線/多角形のアウトライン
    pygame.draw.lines