Pygameで円を描く完全ガイド: draw.circleの使い方から応用まで

2025-05-27

基本的な使い方は以下のようになります。

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

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

  1. surface:

    • 円を描画する対象となるSurfaceオブジェクトです。通常は pygame.display.set_mode() で作成したゲーム画面を表すSurface(例: screen)を指定します。
  2. color:

    • 円の色を指定します。RGB形式のタプル (R, G, B) で表現します。例えば、赤は (255, 0, 0)、青は (0, 0, 255)、白は (255, 255, 255) となります。
  3. center:

    • 円の中心座標を指定します。(x, y) のタプルで表現します。これは、Surfaceの左上を (0, 0) とした座標になります。
  4. radius:

    • 円の半径を指定します。ピクセル単位の整数値です。
  5. width (オプション):

    • 円の線の太さを指定します。
      • width0 の場合(デフォルト): 円は塗りつぶされます。
      • width0 より大きい場合: 指定した太さの線で円が描画されます(中身は透明)。
      • width0 より小さい場合: 何も描画されません。

使用例

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 Circle Example")

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

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

    # 画面を白で塗りつぶす
    screen.fill(WHITE)

    # 中心に赤い塗りつぶしの円を描画
    pygame.draw.circle(screen, RED, (screen_width // 2, screen_height // 2), 50)

    # 左上に青い線で描画された円(太さ5ピクセル)
    pygame.draw.circle(screen, BLUE, (100, 100), 30, 5)

    # 右下に緑の塗りつぶしの円
    pygame.draw.circle(screen, GREEN, (screen_width - 100, screen_height - 100), 40)

    # 描画内容を更新
    pygame.display.flip()

# Pygameの終了
pygame.quit()

このコードを実行すると、白い画面の中央に赤い塗りつぶしの円、左上に青い枠線の円、右下に緑の塗りつぶしの円が表示されます。



円が表示されない、または期待通りに表示されない

よくある原因とトラブルシューティング

  • width 引数の指定ミス

    • 原因
      width0 の場合は塗りつぶし円、0 より大きい場合は線のみの円が描画されます。しかし、width が半径より大きい場合や、負の値を指定した場合、期待通りに描画されないことがあります。特に width が半径以上だと、円の内部が全て線で埋まってしまい、期待と異なる見た目になることがあります。また、width が負の値の場合は何も描画されません。
    • 解決策
      • 塗りつぶし円を描きたい場合は width=0 を指定するか、省略します。
      • 枠線のみの円を描きたい場合は、widthradius より小さい正の整数であることを確認します。
  • 座標や半径が画面外、または小さすぎる

    • 原因
      円の中心座標が画面の範囲外だったり、半径が非常に小さすぎたりすると、見えない、またはほとんど見えない状態になります。
    • 解決策
      center (x, y) 座標と radius の値を確認し、画面のサイズに対して適切か、正の数値になっているかを確認してください。デバッグのために、一時的に座標や半径を大きな値に設定して表示されるか試すのも有効です。
  • 画面のクリア(screen.fill())の順序

    • 原因
      毎フレーム、新しい描画を行う前に画面をクリア(例えば screen.fill(WHITE))するのが一般的です。しかし、円を描画した後に画面をクリアしてしまうと、円がすぐに消えてしまいます。
    • 解決策
      画面のクリアは、すべての描画処理の前に行います。
    # 悪い例
    # pygame.draw.circle(screen, RED, (100, 100), 30)
    # screen.fill(WHITE) # これだと描画した円がすぐ消える
    
    # 良い例
    screen.fill(WHITE) # まず画面をクリア
    pygame.draw.circle(screen, RED, (100, 100), 30) # その後で円を描画
    pygame.display.flip()
    
    • 原因
      draw.circle は画面上に描画を指示するだけで、実際にその描画を画面に表示するためには、pygame.display.flip()(画面全体を更新)または pygame.display.update()(一部を更新)を呼び出す必要があります。これらの関数を呼び出さないと、描画された内容はバックバッファに留まり、ユーザーには見えません。
    • 解決策
      メインループの最後に pygame.display.flip() または pygame.display.update() を必ず追加してください。
    # 悪い例
    # pygame.draw.circle(screen, RED, (100, 100), 30)
    # (ここに更新処理がない)
    
    # 良い例
    pygame.draw.circle(screen, RED, (100, 100), 30)
    pygame.display.flip() # または pygame.display.update()
    

TypeError (引数の型が正しくない)

  • 色、中心座標のタプルの形式が間違っている

    • 原因
      color(R, G, B) のタプル、center(x, y) のタプルである必要があります。これらがリスト [R, G, B][x, y] になっている場合でも動作しますが、例えば要素数が間違っているなど、期待される形式と異なる場合にエラーになることがあります。
    • 解決策
      引数が正しい形式(タプルまたはリスト)であり、かつ正しい要素数(色なら3つまたは4つ、座標なら2つ)を持っているか確認します。
  • 浮動小数点数(float)を整数(int)として渡すべき引数に渡している

    • 原因
      center の座標 (x, y) や radiuswidth は基本的に整数(ピクセル単位)を期待します。計算結果が浮動小数点数になった場合、TypeError: integer argument expected, got float のようなエラーが発生します。
    • 解決策
      関連する計算結果を int() で整数にキャストします。特に割り算 / を使うと浮動小数点数になるため、整数除算 // を使うか、明示的に int() で変換します。
    # 悪い例 (centerのx, yがfloatになる可能性がある)
    # center_x = screen_width / 2
    # center_y = screen_height / 2
    # pygame.draw.circle(screen, RED, (center_x, center_y), 50)
    
    # 良い例
    center_x = screen_width // 2 # 整数除算を使用
    center_y = screen_height // 2
    # または
    # center_x = int(screen_width / 2)
    # center_y = int(screen_height / 2)
    pygame.draw.circle(screen, RED, (center_x, center_y), 50)
    

AttributeError (モジュールや関数の存在しない属性を呼び出している)

  • pygame.draw モジュールを正しくインポートしていない
    • 原因
      pygameimport pygame のようにインポートせず、from pygame.draw import circle のように個別の関数をインポートしようとした際に、誤ったインポートを行うと発生することがあります。通常は pygame.draw.circle と書くのが一般的です。
    • 解決策
      import pygame を行い、pygame.draw.circle(...) のように完全なパスで関数を呼び出します。
  • 円の縁がギザギザになる
    • 原因
      pygame.draw.circle はデフォルトではアンチエイリアシング(ジャギーを滑らかにする処理)を行いません。そのため、斜めの線や曲線の縁がギザギザに見えることがあります。

    • 解決策

      • pygame.gfxdraw を使用する
        Pygameには、より高品質な描画を行うための pygame.gfxdraw モジュールがあります。これを使うと、アンチエイリアシングされた円を描画できます。ただし、pygame.draw よりも少し複雑になります。
      import pygame
      import pygame.gfxdraw # gfxdrawをインポート
      
      # ... (Pygame初期化、画面設定など)
      
      # アンチエイリアシングされた円の描画 (塗りつぶし)
      pygame.gfxdraw.aacircle(screen, center_x, center_y, radius, color) # 外枠
      pygame.gfxdraw.filled_circle(screen, center_x, center_y, radius, color) # 塗りつぶし
      
      # アンチエイリアシングされた枠線のみの円の描画
      # pygame.gfxdraw.aacircle(screen, center_x, center_y, radius, color)
      # その後、内側に少し小さい円を塗りつぶしで描画して枠線にするテクニックも
      # pygame.gfxdraw.filled_circle(screen, center_x, center_y, radius - width, background_color)
      
      • pygame-ce (Community Edition) を検討する
        Pygameのコミュニティエディション (pygame-ce) は、元のPygameのバグ修正や機能改善が活発に行われています。古いPygameバージョンで発生する特定の描画の不具合(例: 円が歪む、期待しないピクセルが表示されるなど)が pygame-ce で修正されている場合があります。
  • 描画のパフォーマンス
    • 毎フレーム多数の円を描画すると、パフォーマンスが低下する可能性があります。
    • 解決策
      • 動的に変化しない円は、一度別のSurfaceに描画し、それを画面に blit() する。
      • 更新が必要な領域だけを pygame.display.update(rect_list) で更新する。
      • より高度な描画(例: スプライトシート、OpenGLなど)を検討する。


例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("基本の円描画")

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

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

    # 画面を白で塗りつぶす (各フレームでクリア)
    screen.fill(WHITE)

    # 1. 塗りつぶしの赤い円
    # 中心: (100, 100), 半径: 50, 幅: 0 (塗りつぶし)
    pygame.draw.circle(screen, RED, (100, 100), 50)

    # 2. 枠線のみの青い円 (太さ5ピクセル)
    # 中心: (300, 150), 半径: 70, 幅: 5
    pygame.draw.circle(screen, BLUE, (300, 150), 70, 5)

    # 3. 少し太い枠線の緑の円
    # 中心: (550, 100), 半径: 40, 幅: 10
    pygame.draw.circle(screen, GREEN, (550, 100), 40, 10)

    # 4. 画面中央に黄色の塗りつぶしの円
    pygame.draw.circle(screen, YELLOW, (screen_width // 2, screen_height // 2), 80)

    # 5. 黒い塗りつぶしの小さな円 (目などを表現する際に)
    pygame.draw.circle(screen, BLACK, (700, 500), 20)

    # 描画内容を画面に反映
    pygame.display.flip()

# Pygameの終了
pygame.quit()

ポイント

  • width=0 が塗りつぶし、width > 0 が枠線のみの円になります。
  • screen.fill(WHITE) で毎フレーム画面をクリアしないと、前のフレームの描画が残り続けます。

例2: マウス追従円

マウスカーソルの位置に追従する円を描画します。

import pygame

pygame.init()

screen_width = 800
screen_height = 600
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("マウス追従円")

WHITE = (255, 255, 255)
RED = (255, 0, 0)

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

    # マウスの現在の位置を取得
    mouse_x, mouse_y = pygame.mouse.get_pos()

    screen.fill(WHITE)

    # マウスの位置を中心として赤い円を描画
    # int() でキャストして整数にする
    pygame.draw.circle(screen, RED, (int(mouse_x), int(mouse_y)), 30)

    pygame.display.flip()

pygame.quit()

ポイント

  • int() で座標を整数に変換しているのは、draw.circle が整数座標を期待するためです。
  • pygame.mouse.get_pos() でマウスの (x, y) 座標を取得できます。

例3: クリックで円を生成

マウスをクリックした場所に円を生成し、クリックするたびに円が増えていく例です。

import pygame

pygame.init()

screen_width = 800
screen_height = 600
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("クリックで円生成")

WHITE = (255, 255, 255)
BLUE = (0, 0, 255)

# 描画する円のリスト
circles = [] # 各要素は (x, y, radius, color) のタプル

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        if event.type == pygame.MOUSEBUTTONDOWN: # マウスボタンが押された時
            if event.button == 1: # 左クリックの場合
                mouse_pos = event.pos # クリックされた位置を取得
                new_circle = (mouse_pos[0], mouse_pos[1], 20, BLUE) # 新しい円のデータ
                circles.append(new_circle) # リストに追加

    screen.fill(WHITE)

    # リスト内のすべての円を描画
    for circle_data in circles:
        x, y, radius, color = circle_data
        pygame.draw.circle(screen, color, (x, y), radius)

    pygame.display.flip()

pygame.quit()

ポイント

  • 描画ループ内で circles リストをforループで回し、すべての円を描画しています。
  • pygame.MOUSEBUTTONDOWN イベントでクリックを検知し、その位置に新しい円の情報を追加しています。
  • circles というリストに、描画する円の情報を格納しています。

例4: アニメーションする円 (動く円)

円が画面内を動き回るアニメーションの例です。

import pygame

pygame.init()

screen_width = 800
screen_height = 600
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("動く円")

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

# 円の初期位置と速度
circle_x = 50
circle_y = 50
circle_radius = 30
speed_x = 5
speed_y = 3

# フレームレートの設定
clock = pygame.time.Clock()
FPS = 60

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

    # 円の位置を更新
    circle_x += speed_x
    circle_y += speed_y

    # 画面端での跳ね返り処理
    if circle_x + circle_radius > screen_width or circle_x - circle_radius < 0:
        speed_x *= -1 # X方向の速度を反転
    if circle_y + circle_radius > screen_height or circle_y - circle_radius < 0:
        speed_y *= -1 # Y方向の速度を反転

    screen.fill(WHITE)

    # 更新された位置で円を描画
    pygame.draw.circle(screen, GREEN, (circle_x, circle_y), circle_radius)

    pygame.display.flip()

    # フレームレートを制御
    clock.tick(FPS)

pygame.quit()

ポイント

  • clock.tick(FPS) でフレームレートを制御し、アニメーションの速さを一定に保っています。
  • if 文で画面端に到達したかを判定し、速度を反転させて跳ね返りを表現しています。
  • speed_xspeed_y は、円の移動速度と方向を表します。
  • circle_xcircle_y の値を毎フレーム更新することで円を動かしています。

draw.circle の円の縁がギザギザになる問題を解決するため、pygame.gfxdraw を使って滑らかな円を描画します。

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

pygame.init()

screen_width = 800
screen_height = 600
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("アンチエイリアシング円 (gfxdraw)")

WHITE = (255, 255, 255)
PURPLE = (128, 0, 128) # 紫
ORANGE = (255, 165, 0) # オレンジ

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

    screen.fill(WHITE)

    # 従来の draw.circle (比較用)
    pygame.draw.circle(screen, BLACK, (200, 150), 60)
    font = pygame.font.Font(None, 30)
    text = font.render("draw.circle (ジャギーあり)", True, BLACK)
    screen.blit(text, (100, 230))

    # gfxdraw を使ったアンチエイリアシング円 (塗りつぶし)
    # まず外枠のアンチエイリアシング円を描き、その後に塗りつぶしの円を描く
    # gfxdraw.aacircle の引数は (surface, x, y, radius, color)
    pygame.gfxdraw.aacircle(screen, 500, 150, 60, PURPLE)
    pygame.gfxdraw.filled_circle(screen, 500, 150, 60, PURPLE)
    text = font.render("gfxdraw (アンチエイリアシング)", True, PURPLE)
    screen.blit(text, (400, 230))

    # gfxdraw を使ったアンチエイリアシング枠線のみの円
    # 枠線のみの場合は aacircle だけを使う
    pygame.gfxdraw.aacircle(screen, 350, 450, 80, ORANGE)
    text = font.render("gfxdraw (枠線のみ)", True, ORANGE)
    screen.blit(text, (280, 530))


    pygame.display.flip()

pygame.quit()
  • 塗りつぶしのアンチエイリアシング円を描くには、通常 aacircle で外枠を描いた後に filled_circle で中を塗りつぶします。
  • gfxdraw.filled_circle() はアンチエイリアシングされた塗りつぶしの円を描画します。
  • gfxdraw.aacircle() はアンチエイリアシングされた円の枠線を描画します。
  • pygame.gfxdraw はPygameの標準モジュールですが、draw とは別にインポートが必要です。


pygame.gfxdraw を使用する

最も一般的な代替手段であり、特に高品質なグラフィックを求める場合に有効です。

  • 使用例

    import pygame
    import pygame.gfxdraw # gfxdrawをインポート
    
    # ... (Pygame初期化、画面設定など)
    
    # アンチエイリアシングされた塗りつぶしの円
    # まず外枠のアンチエイリアシング円を描き、その後に塗りつぶしの円を描く
    pygame.gfxdraw.aacircle(screen, x, y, radius, color)
    pygame.gfxdraw.filled_circle(screen, x, y, radius, color)
    
    # アンチエイリアシングされた枠線のみの円
    pygame.gfxdraw.aacircle(screen, x, y, radius, color)
    
  • 欠点

    • 描画速度
      pygame.draw よりも一般的に描画速度が遅いです。多数の円を毎フレーム描画する場合、パフォーマンスに影響が出る可能性があります。
    • 利用方法の複雑さ
      塗りつぶしのアンチエイリアシング円を描くには、通常 gfxdraw.aacircle() で外枠を描画し、その上に gfxdraw.filled_circle() で塗りつぶしを行う必要があります。draw.circle のように width=0 で塗りつぶしと枠線の切り替えができません。
    • 「実験的 (experimental)」なモジュールとされていますが、多くのプロジェクトで問題なく使用されています。
    • アンチエイリアシング (Anti-Aliasing)
      pygame.draw.circle が描画する円の縁はギザギザ(ジャギー)になることが多いですが、pygame.gfxdraw はアンチエイリアシング処理を行い、滑らかな円を描画できます。
    • より多様な図形描画機能(例: アンチエイリアシングされた線、多角形など)を提供します。

pygame.Surface に描画し、blit する

円が頻繁に形状や色を変えない場合、事前に 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を使った円")
    
    WHITE = (255, 255, 255)
    BLUE = (0, 0, 255)
    
    # 円を描画するためのSurfaceを作成
    radius = 50
    # 円を完全に含めるために、幅と高さは直径+αに設定し、透明なSurfaceにする
    circle_surface = pygame.Surface((radius * 2, radius * 2), pygame.SRCALPHA)
    pygame.draw.circle(circle_surface, BLUE, (radius, radius), radius)
    
    # 円の位置
    circle_x, circle_y = 100, 100
    
    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
    
        screen.fill(WHITE)
    
        # 作成した円のSurfaceを画面にblit
        # blitの座標はSurfaceの左上の座標
        screen.blit(circle_surface, (circle_x, circle_y))
    
        pygame.display.flip()
    
    pygame.quit()
    

    Spriteとの組み合わせ例

    import pygame
    
    class CircleSprite(pygame.sprite.Sprite):
        def __init__(self, color, radius, center_pos):
            super().__init__()
            self.image = pygame.Surface((radius * 2, radius * 2), pygame.SRCALPHA)
            pygame.draw.circle(self.image, color, (radius, radius), radius)
            self.rect = self.image.get_rect(center=center_pos)
    
        def update(self):
            # 例: スプライトを動かす
            self.rect.x += 1
            if self.rect.left > pygame.display.get_surface().get_width():
                self.rect.right = 0
    
    pygame.init()
    screen = pygame.display.set_mode((800, 600))
    all_sprites = pygame.sprite.Group()
    
    my_circle = CircleSprite((255, 0, 0), 30, (100, 100))
    all_sprites.add(my_circle)
    
    clock = pygame.time.Clock()
    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
    
        all_sprites.update() # スプライトを更新
        screen.fill((255, 255, 255))
        all_sprites.draw(screen) # スプライトを描画
        pygame.display.flip()
        clock.tick(60)
    
    pygame.quit()
    
  • 欠点

    • 円の半径や色が頻繁に変わる場合、毎フレーム新しいSurfaceを作成する必要があり、かえってパフォーマンスが低下する可能性があります。
    • 初期設定の手間が増えます。
  • 利点

    • パフォーマンス
      円が一度描画された後、それを使い回すため、毎フレーム draw.circle を呼び出すよりも高速になることがあります。特に、同じデザインの円が多数ある場合に有効です。
    • 柔軟性
      事前に作成した円のSurfaceを回転、拡大縮小、透明度設定など、様々な加工をしてから blit できます。
    • Spriteとの組み合わせ
      Pygameの Sprite クラスと非常に相性が良く、円をゲームオブジェクトとして扱う場合に理想的です。

画像ファイル (Image) を使用する

円のグラフィックが複雑な場合や、デザイナーが作成したアセットを使用する場合、画像ファイル(PNG、JPGなど)として円を読み込むのが最も簡単な方法です。

  • 使用例
    (事前に circle.png という名前の円の画像ファイルが必要)

    import pygame
    
    pygame.init()
    
    screen_width = 800
    screen_height = 600
    screen = pygame.display.set_mode((screen_width, screen_height))
    pygame.display.set_caption("画像ファイルを使った円")
    
    WHITE = (255, 255, 255)
    
    try:
        # 画像ファイルを読み込む (アルファチャンネルを保持)
        circle_image = pygame.image.load("circle.png").convert_alpha()
    except pygame.error as e:
        print(f"画像読み込みエラー: {e}")
        print("circle.png ファイルが同じディレクトリにあるか確認してください。")
        pygame.quit()
        exit()
    
    # 画像の中心座標
    image_center_x = screen_width // 2
    image_center_y = screen_height // 2
    
    # 画像の矩形オブジェクトを取得し、中心を設定
    image_rect = circle_image.get_rect(center=(image_center_x, image_center_y))
    
    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
    
        screen.fill(WHITE)
    
        # 画像を画面にblit
        screen.blit(circle_image, image_rect)
    
        pygame.display.flip()
    
    pygame.quit()
    
  • 欠点

    • 円の半径や色を動的に変更するのが難しい(画像をロードし直すか、画像処理を行う必要がある)。
    • 画像ファイルを用意する手間がかかる。
  • 利点

    • 高品質なグラフィック
      デザイナーが作成した複雑なテクスチャやシェーディングを含む円を簡単に表示できます。
    • アーティストフレンドリー
      プログラマーが描画コードを書く手間が省け、グラフィックの変更が容易になります。
    • 透明性 (PNGの場合)
      PNG形式を使用すれば、背景が透明な円を簡単に扱えます。
  • 複雑な見た目の円、デザイナーが提供するアセット
    画像ファイルを使用するのが最も直接的です。
  • 同じ円を何度も描画する、スプライトとして扱う、動的に加工したい
    pygame.Surface に一度描画して blit する方法が効率的です。
  • 高品質な円(アンチエイリアシングが必要)、描画回数が少ない
    pygame.gfxdraw を検討します。
  • 簡単なプロトタイプ、シンプルな円、速度重視
    pygame.draw.circle が最も簡単で良い選択です。