【Python】pygameで当たり判定を実装する方法

この記事では、Pythonのゲーム開発ライブラリであるpygameを使って、ゲーム内での当たり判定を実装する方法を解説します。

具体的には、矩形、円形、そしてピクセルパーフェクトの3種類の当たり判定について学びます。

さらに、これらの技術を使って簡単なシューティングゲームを作成し、実際に当たり判定を実装する方法をステップバイステップで説明します。

目次から探す

矩形当たり判定の実装

矩形当たり判定の基本

ゲーム開発において、オブジェクト同士の衝突を検出することは非常に重要です。

特に、矩形(四角形)同士の当たり判定は多くのゲームで使用される基本的な技術です。

矩形当たり判定は、2つの矩形が重なっているかどうかを確認する方法です。

これにより、キャラクターが壁にぶつかったり、アイテムを取得したりするイベントを検出できます。

pygame.Rectクラスの使い方

pygameライブラリには、矩形を扱うための便利なクラスpygame.Rectが用意されています。

このクラスを使用することで、矩形の位置やサイズを簡単に管理でき、当たり判定も簡単に行えます。

pygame.Rectクラスの主なメソッドと属性は以下の通りです:

  • Rect(x, y, width, height): 矩形を作成します。
  • colliderect(rect): 他の矩形と衝突しているかどうかを判定します。
  • x, y, width, height: 矩形の位置とサイズを表す属性です。

矩形当たり判定の実装例

矩形オブジェクトの作成

まず、pygame.Rectクラスを使って矩形オブジェクトを作成します。

以下のコードは、2つの矩形を作成する例です。

import pygame
# Pygameの初期化
pygame.init()
# 矩形オブジェクトの作成
rect1 = pygame.Rect(50, 50, 100, 100)  # (x, y, width, height)
rect2 = pygame.Rect(100, 100, 100, 100)  # (x, y, width, height)
# 矩形の情報を表示
print(f"Rect1: {rect1}")
print(f"Rect2: {rect2}")

このコードでは、rect1rect2という2つの矩形オブジェクトを作成しています。

それぞれの矩形は、左上の座標と幅、高さを指定して作成されます。

矩形同士の衝突判定

次に、これらの矩形が衝突しているかどうかを判定します。

pygame.Rectクラスcolliderectメソッドを使用すると、簡単に衝突判定ができます。

# 矩形同士の衝突判定
if rect1.colliderect(rect2):
    print("Rect1とRect2は衝突しています。")
else:
    print("Rect1とRect2は衝突していません。")

このコードでは、rect1rect2が衝突しているかどうかを判定し、結果を表示します。

colliderectメソッドは、矩形が重なっている場合にTrueを返し、そうでない場合にFalseを返します。

これにより、ゲーム内でのオブジェクト同士の衝突を検出し、適切なアクションを取ることができます。

円形当たり判定の実装

円形当たり判定の基本

円形当たり判定は、ゲーム開発において非常に重要な要素です。

特に、キャラクターやオブジェクトが円形である場合、矩形当たり判定ではなく円形当たり判定を使用することで、より正確な衝突判定が可能になります。

円形当たり判定の基本的な考え方は、2つの円の中心点間の距離が、それぞれの半径の合計よりも小さいかどうかを確認することです。

円形当たり判定の計算方法

円形当たり判定の計算方法は非常にシンプルです。

2つの円の中心点の座標を知っていれば、以下の手順で当たり判定を行うことができます。

  1. 2つの円の中心点間の距離を計算します。
  2. その距離が2つの円の半径の合計よりも小さいかどうかを確認します。

具体的には、2つの円の中心点の座標を (x1, y1) と (x2, y2)、それぞれの半径を r1 と r2 とした場合、次のように計算します。

distance = sqrt((x2 - x1)^2 + (y2 - y1)^2)
if distance < (r1 + r2):
    # 衝突している
else:
    # 衝突していない

円形当たり判定の実装例

それでは、実際にpygameを使って円形当たり判定を実装してみましょう。

円形オブジェクトの作成

まず、円形オブジェクトを作成します。

pygameでは、pygame.draw.circle関数を使って円を描画することができます。

import pygame
import math
# 初期化
pygame.init()
# 画面の設定
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("円形当たり判定の例")
# 色の設定
WHITE = (255, 255, 255)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
# 円の設定
circle1 = {'pos': (200, 300), 'radius': 50}
circle2 = {'pos': (400, 300), 'radius': 75}
# メインループ
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, circle1['pos'], circle1['radius'])
    pygame.draw.circle(screen, BLUE, circle2['pos'], circle2['radius'])
    # 画面を更新
    pygame.display.flip()
# 終了処理
pygame.quit()

円形同士の衝突判定

次に、円形同士の衝突判定を実装します。

上記のコードに衝突判定のロジックを追加します。

import pygame
import math
# 初期化
pygame.init()
# 画面の設定
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("円形当たり判定の例")
# 色の設定
WHITE = (255, 255, 255)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
GREEN = (0, 255, 0)
# 円の設定
circle1 = {'pos': (200, 300), 'radius': 50}
circle2 = {'pos': (400, 300), 'radius': 75}
# メインループ
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, circle1['pos'], circle1['radius'])
    pygame.draw.circle(screen, BLUE, circle2['pos'], circle2['radius'])
    # 衝突判定
    dx = circle2['pos'][0] - circle1['pos'][0]
    dy = circle2['pos'][1] - circle1['pos'][1]
    distance = math.sqrt(dx**2 + dy**2)
    if distance < (circle1['radius'] + circle2['radius']):
        # 衝突している場合、円の色を緑に変更
        pygame.draw.circle(screen, GREEN, circle1['pos'], circle1['radius'])
        pygame.draw.circle(screen, GREEN, circle2['pos'], circle2['radius'])
    # 画面を更新
    pygame.display.flip()
# 終了処理
pygame.quit()

このコードでは、2つの円の中心点間の距離を計算し、その距離が2つの円の半径の合計よりも小さい場合に衝突していると判定します。

衝突している場合、円の色を緑に変更して表示します。

以上で、円形当たり判定の基本的な実装方法について解説しました。

次は、より複雑な当たり判定方法について学んでいきましょう。

ピクセルパーフェクト当たり判定の実装

ピクセルパーフェクト当たり判定の基本

ピクセルパーフェクト当たり判定は、ゲーム開発において非常に精度の高い当たり判定方法です。

矩形や円形の当たり判定では、オブジェクトの形状が単純であるため、判定が簡単ですが、複雑な形状のオブジェクトに対しては不正確になることがあります。

ピクセルパーフェクト当たり判定では、オブジェクトのピクセル単位での重なりをチェックするため、非常に正確な判定が可能です。

マスクを使った当たり判定

pygameでは、ピクセルパーフェクト当たり判定を行うためにpygame.maskモジュールを使用します。

マスクは、画像の透明部分と不透明部分を区別するためのビットマップです。

マスクを使うことで、画像の不透明な部分同士が重なっているかどうかを判定できます。

ピクセルパーフェクト当たり判定の実装例

マスクオブジェクトの作成

まず、画像からマスクオブジェクトを作成する方法を見てみましょう。

以下のコードは、画像を読み込み、その画像からマスクを作成する例です。

import pygame
# Pygameの初期化
pygame.init()
# 画像の読み込み
image = pygame.image.load('example.png')
# マスクの作成
mask = pygame.mask.from_surface(image)
# マスクの表示
print(mask)

このコードでは、example.pngという画像ファイルを読み込み、その画像からマスクを作成しています。

pygame.mask.from_surface関数を使用することで、画像の不透明部分を基にマスクを生成できます。

マスク同士の衝突判定

次に、2つのマスクオブジェクト同士の衝突判定を行う方法を見てみましょう。

以下のコードは、2つの画像からマスクを作成し、それらのマスク同士が衝突しているかどうかを判定する例です。

import pygame
# Pygameの初期化
pygame.init()
# 画像の読み込み
image1 = pygame.image.load('example1.png')
image2 = pygame.image.load('example2.png')
# マスクの作成
mask1 = pygame.mask.from_surface(image1)
mask2 = pygame.mask.from_surface(image2)
# マスクの位置
offset = (50, 50)  # image1の位置から見たimage2の位置
# 衝突判定
collision_point = mask1.overlap(mask2, offset)
if collision_point:
    print("衝突しました!")
else:
    print("衝突していません。")

このコードでは、example1.pngexample2.pngという2つの画像ファイルを読み込み、それぞれからマスクを作成しています。

mask1.overlap(mask2, offset)関数を使用して、2つのマスクが指定されたオフセット位置で重なっているかどうかを判定します。

重なっている場合は、衝突点の座標が返され、重なっていない場合はNoneが返されます。

以上が、ピクセルパーフェクト当たり判定の基本的な実装方法です。

これを応用することで、より精度の高い当たり判定をゲームに組み込むことができます。

実践例:簡単なゲームでの当たり判定

ここでは、簡単なシューティングゲームを例にして、pygameを使った当たり判定の実装方法を解説します。

このゲームでは、プレイヤーが敵を撃ち落とすことを目標とし、プレイヤーと敵、弾と敵の当たり判定を実装します。

ゲームの概要

このゲームでは、以下の要素を含みます:

  • プレイヤー:画面下部を左右に移動し、弾を発射する。
  • 敵:画面上部から降りてくる。
  • 弾:プレイヤーが発射し、敵に当たると敵を消す。

ゲームオブジェクトの作成

まず、ゲームオブジェクト(プレイヤー、敵、弾)を作成します。

以下は、基本的なpygameのセットアップとオブジェクトの作成方法です。

import pygame
import random
# 初期化
pygame.init()
# 画面サイズ
screen_width = 800
screen_height = 600
screen = pygame.display.set_mode((screen_width, screen_height))
# 色の定義
white = (255, 255, 255)
black = (0, 0, 0)
red = (255, 0, 0)
# プレイヤーの設定
player_width = 50
player_height = 50
player_x = screen_width // 2
player_y = screen_height - player_height - 10
player_speed = 5
# 弾の設定
bullet_width = 5
bullet_height = 10
bullet_speed = 7
bullets = []
# 敵の設定
enemy_width = 50
enemy_height = 50
enemy_speed = 3
enemies = []
# 敵を生成する関数
def create_enemy():
    x = random.randint(0, screen_width - enemy_width)
    y = random.randint(-100, -40)
    return pygame.Rect(x, y, enemy_width, enemy_height)
# ゲームループ
running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
    # キー入力の処理
    keys = pygame.key.get_pressed()
    if keys[pygame.K_LEFT] and player_x > 0:
        player_x -= player_speed
    if keys[pygame.K_RIGHT] and player_x < screen_width - player_width:
        player_x += player_speed
    if keys[pygame.K_SPACE]:
        bullets.append(pygame.Rect(player_x + player_width // 2, player_y, bullet_width, bullet_height))
    # 弾の移動
    for bullet in bullets:
        bullet.y -= bullet_speed
        if bullet.y < 0:
            bullets.remove(bullet)
    # 敵の生成と移動
    if random.randint(1, 20) == 1:
        enemies.append(create_enemy())
    for enemy in enemies:
        enemy.y += enemy_speed
        if enemy.y > screen_height:
            enemies.remove(enemy)
    # 画面の描画
    screen.fill(black)
    pygame.draw.rect(screen, white, (player_x, player_y, player_width, player_height))
    for bullet in bullets:
        pygame.draw.rect(screen, red, bullet)
    for enemy in enemies:
        pygame.draw.rect(screen, white, enemy)
    pygame.display.flip()
    pygame.time.Clock().tick(60)
pygame.quit()

当たり判定の実装

次に、プレイヤーと敵、弾と敵の当たり判定を実装します。

プレイヤーと敵の当たり判定

プレイヤーと敵が衝突した場合、ゲームオーバーとします。

以下のコードをゲームループ内に追加します。

# プレイヤーと敵の当たり判定
player_rect = pygame.Rect(player_x, player_y, player_width, player_height)
for enemy in enemies:
    if player_rect.colliderect(enemy):
        running = False  # ゲームオーバー

弾と敵の当たり判定

弾が敵に当たった場合、弾と敵の両方を消します。

以下のコードをゲームループ内に追加します。

# 弾と敵の当たり判定
for bullet in bullets:
    for enemy in enemies:
        if bullet.colliderect(enemy):
            bullets.remove(bullet)
            enemies.remove(enemy)
            break

これで、プレイヤーと敵、弾と敵の当たり判定が実装されました。

最終的なゲームループは以下のようになります。

# ゲームループ
running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
    # キー入力の処理
    keys = pygame.key.get_pressed()
    if keys[pygame.K_LEFT] and player_x > 0:
        player_x -= player_speed
    if keys[pygame.K_RIGHT] and player_x < screen_width - player_width:
        player_x += player_speed
    if keys[pygame.K_SPACE]:
        bullets.append(pygame.Rect(player_x + player_width // 2, player_y, bullet_width, bullet_height))
    # 弾の移動
    for bullet in bullets:
        bullet.y -= bullet_speed
        if bullet.y < 0:
            bullets.remove(bullet)
    # 敵の生成と移動
    if random.randint(1, 20) == 1:
        enemies.append(create_enemy())
    for enemy in enemies:
        enemy.y += enemy_speed
        if enemy.y > screen_height:
            enemies.remove(enemy)
    # プレイヤーと敵の当たり判定
    player_rect = pygame.Rect(player_x, player_y, player_width, player_height)
    for enemy in enemies:
        if player_rect.colliderect(enemy):
            running = False  # ゲームオーバー
    # 弾と敵の当たり判定
    for bullet in bullets:
        for enemy in enemies:
            if bullet.colliderect(enemy):
                bullets.remove(bullet)
                enemies.remove(enemy)
                break
    # 画面の描画
    screen.fill(black)
    pygame.draw.rect(screen, white, (player_x, player_y, player_width, player_height))
    for bullet in bullets:
        pygame.draw.rect(screen, red, bullet)
    for enemy in enemies:
        pygame.draw.rect(screen, white, enemy)
    pygame.display.flip()
    pygame.time.Clock().tick(60)
pygame.quit()

これで、簡単なシューティングゲームの当たり判定が実装できました。

完成したコード
import pygame
import random
# 初期化
pygame.init()
# 画面サイズ
screen_width = 800
screen_height = 600
screen = pygame.display.set_mode((screen_width, screen_height))
# 色の定義
white = (255, 255, 255)
black = (0, 0, 0)
red = (255, 0, 0)
# プレイヤーの設定
player_width = 50
player_height = 50
player_x = screen_width // 2
player_y = screen_height - player_height - 10
player_speed = 5
# 弾の設定
bullet_width = 5
bullet_height = 10
bullet_speed = 7
bullets = []
# 敵の設定
enemy_width = 50
enemy_height = 50
enemy_speed = 3
enemies = []
# 敵を生成する関数
def create_enemy():
    x = random.randint(0, screen_width - enemy_width)
    y = random.randint(-100, -40)
    return pygame.Rect(x, y, enemy_width, enemy_height)
# ゲームループ
running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
    # キー入力の処理
    keys = pygame.key.get_pressed()
    if keys[pygame.K_LEFT] and player_x > 0:
        player_x -= player_speed
    if keys[pygame.K_RIGHT] and player_x < screen_width - player_width:
        player_x += player_speed
    if keys[pygame.K_SPACE]:
        bullets.append(pygame.Rect(player_x + player_width // 2, player_y, bullet_width, bullet_height))
    # 弾の移動
    for bullet in bullets:
        bullet.y -= bullet_speed
        if bullet.y < 0:
            bullets.remove(bullet)
    # 敵の生成と移動
    if random.randint(1, 20) == 1:
        enemies.append(create_enemy())
    for enemy in enemies:
        enemy.y += enemy_speed
        if enemy.y > screen_height:
            enemies.remove(enemy)
    # プレイヤーと敵の当たり判定
    player_rect = pygame.Rect(player_x, player_y, player_width, player_height)
    for enemy in enemies:
        if player_rect.colliderect(enemy):
            running = False  # ゲームオーバー
    # 弾と敵の当たり判定
    for bullet in bullets:
        for enemy in enemies:
            if bullet.colliderect(enemy):
                bullets.remove(bullet)
                enemies.remove(enemy)
                break
    # 画面の描画
    screen.fill(black)
    pygame.draw.rect(screen, white, (player_x, player_y, player_width, player_height))
    for bullet in bullets:
        pygame.draw.rect(screen, red, bullet)
    for enemy in enemies:
        pygame.draw.rect(screen, white, enemy)
    pygame.display.flip()
    pygame.time.Clock().tick(60)
pygame.quit()

プレイヤーと敵、弾と敵の当たり判定を理解することで、より複雑なゲームの開発にも応用できます。

目次から探す