【Python】二つの四角形の当たり判定を行う方法

この記事では、Pythonを使って二つの四角形が重なっているかどうかを判定する方法について解説します。

具体的には、Pygameというライブラリを使って四角形を描画し、軸に平行な境界ボックス(AABB)法を用いて当たり判定を行います。

また、複数の四角形や動的に動く四角形の当たり判定についても紹介します。

初心者の方でも理解しやすいように、サンプルコードとその実行結果を交えながら説明していきます。

目次から探す

Pythonでの当たり判定の実装方法

二つの四角形の当たり判定を行う方法について、Pythonを使って実装する方法を解説します。

ここでは、必要なライブラリの紹介から、四角形の定義、当たり判定のアルゴリズムまでを順を追って説明します。

必要なライブラリの紹介

Pythonで二つの四角形の当たり判定を行うためには、いくつかのライブラリを使用します。

特に、ゲーム開発やグラフィック処理に便利なライブラリが役立ちます。

Pygame

Pygameは、Pythonでゲームを作成するためのライブラリです。

グラフィックの描画やイベント処理、サウンドの再生など、ゲーム開発に必要な機能が豊富に揃っています。

四角形の描画や当たり判定の実装にも非常に便利です。

Pygameのインストールは以下のコマンドで行います。

pip install pygame

その他のライブラリ

基本的な当たり判定の実装にはPygameだけで十分ですが、他にも便利なライブラリがあります。

例えば、NumPyは数値計算に強力なライブラリで、複雑な計算を簡単に行うことができます。

四角形の定義

当たり判定を行うためには、まず四角形を定義する必要があります。

ここでは、座標系の設定と四角形の属性について説明します。

座標系の設定

Pygameでは、画面の左上が原点(0, 0)となる座標系を使用します。

X軸は右方向に、Y軸は下方向に増加します。

この座標系を基に四角形の位置を指定します。

四角形の属性(位置、幅、高さ)

四角形は以下の属性を持ちます。

  • 位置 (x, y): 四角形の左上の座標
  • 幅 (width): 四角形の横幅
  • 高さ (height): 四角形の縦幅

これらの属性を使って四角形を定義します。

当たり判定のアルゴリズム

当たり判定のアルゴリズムにはいくつかの方法がありますが、ここでは軸に平行な境界ボックス(AABB)法を紹介します。

軸に平行な境界ボックス(AABB)法

AABB法は、四角形が軸に平行であることを前提とした当たり判定の方法です。

二つの四角形が重なっているかどうかを簡単に判定できます。

AABB法の具体的な手順

AABB法の手順は以下の通りです。

  1. 四角形Aの左端、右端、上端、下端の座標を求めます。
  2. 四角形Bの左端、右端、上端、下端の座標を求めます。
  3. 四角形Aと四角形Bの端の座標を比較し、重なっているかどうかを判定します。

具体的には、以下の条件を満たす場合に四角形が重なっていると判定します。

  • 四角形Aの右端が四角形Bの左端より右にある
  • 四角形Aの左端が四角形Bの右端より左にある
  • 四角形Aの下端が四角形Bの上端より下にある
  • 四角形Aの上端が四角形Bの下端より上にある

これらの条件をPythonのコードで実装することで、二つの四角形の当たり判定を行うことができます。

実際のコード例

ここでは、Pythonを使って二つの四角形の当たり判定を行う具体的なコード例を紹介します。

まずは基本的なコードの構成から始め、次にPygameを使った実装例を見ていきます。

基本的なコードの構成

四角形クラスの定義

まず、四角形を表すクラスを定義します。

このクラスには四角形の位置とサイズを属性として持たせます。

class Rectangle:
    def __init__(self, x, y, width, height):
        self.x = x  # 左上のx座標
        self.y = y  # 左上のy座標
        self.width = width  # 幅
        self.height = height  # 高さ
    def get_rect(self):
        return (self.x, self.y, self.width, self.height)

当たり判定関数の実装

次に、二つの四角形の当たり判定を行う関数を実装します。

ここでは軸に平行な境界ボックス(AABB)法を用います。

def is_collision(rect1, rect2):
    # 四角形1の右端が四角形2の左端より右にあり、
    # 四角形1の左端が四角形2の右端より左にあり、
    # 四角形1の下端が四角形2の上端より下にあり、
    # 四角形1の上端が四角形2の下端より上にある場合、衝突している
    if (rect1.x < rect2.x + rect2.width and
        rect1.x + rect1.width > rect2.x and
        rect1.y < rect2.y + rect2.height and
        rect1.y + rect1.height > rect2.y):
        return True
    return False

Pygameを使った実装例

次に、Pygameを使って四角形の当たり判定を視覚的に確認できるようにします。

Pygameはインストールが必要です。まだの場合はpip install pygameでインストールしておきましょう。

Pygameの初期設定

まず、Pygameの初期設定を行います。

import pygame
import sys
# Pygameの初期化
pygame.init()
# 画面サイズの設定
screen = pygame.display.set_mode((800, 600))
# タイトルの設定
pygame.display.set_caption("Rectangle Collision Detection")

この時点でも実行できますが、一瞬でウィンドウが終了します。

四角形の描画

次に、四角形を画面に描画するためのコードを追加します。

# 四角形の定義
rect1 = Rectangle(100, 100, 200, 150)
rect2 = Rectangle(300, 200, 200, 150)
# 色の定義
color1 = (255, 0, 0)  # 赤
color2 = (0, 255, 0)  # 緑
collision_color = (0, 0, 255)  # 青

当たり判定の実装と表示

最後に、当たり判定を行い、その結果に応じて四角形の色を変更するコードを追加します。

# メインループ
while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
    # 画面を白でクリア
    screen.fill((255, 255, 255))
    # 当たり判定のチェック
    if is_collision(rect1, rect2):
        color1 = collision_color
        color2 = collision_color
    else:
        color1 = (255, 0, 0)
        color2 = (0, 255, 0)
    # 四角形の描画
    pygame.draw.rect(screen, color1, rect1.get_rect())
    pygame.draw.rect(screen, color2, rect2.get_rect())
    # 画面の更新
    pygame.display.flip()

このコードを実行すると、二つの四角形が衝突している場合は青色に変わり、衝突していない場合はそれぞれ赤と緑のままになります。

完成したコードがこちらです。

import pygame
import sys

class Rectangle:
    def __init__(self, x, y, width, height):
        self.x = x  # 左上のx座標
        self.y = y  # 左上のy座標
        self.width = width  # 幅
        self.height = height  # 高さ
    def get_rect(self):
        return (self.x, self.y, self.width, self.height)

def is_collision(rect1, rect2):
    # 四角形1の右端が四角形2の左端より右にあり、
    # 四角形1の左端が四角形2の右端より左にあり、
    # 四角形1の下端が四角形2の上端より下にあり、
    # 四角形1の上端が四角形2の下端より上にある場合、衝突している
    if (rect1.x < rect2.x + rect2.width and
        rect1.x + rect1.width > rect2.x and
        rect1.y < rect2.y + rect2.height and
        rect1.y + rect1.height > rect2.y):
        return True
    return False

# Pygameの初期化
pygame.init()
# 画面サイズの設定
screen = pygame.display.set_mode((800, 600))
# タイトルの設定
pygame.display.set_caption("Rectangle Collision Detection")

# 四角形の定義
rect1 = Rectangle(100, 100, 200, 150)
rect2 = Rectangle(300, 200, 200, 150)
# 色の定義
color1 = (255, 0, 0)  # 赤
color2 = (0, 255, 0)  # 緑
collision_color = (0, 0, 255)  # 青

# メインループ
while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
    # 画面を白でクリア
    screen.fill((255, 255, 255))
    # 当たり判定のチェック
    if is_collision(rect1, rect2):
        color1 = collision_color
        color2 = collision_color
    else:
        color1 = (255, 0, 0)
        color2 = (0, 255, 0)
    # 四角形の描画
    pygame.draw.rect(screen, color1, rect1.get_rect())
    pygame.draw.rect(screen, color2, rect2.get_rect())
    # 画面の更新
    pygame.display.flip()

これにより、視覚的に当たり判定の結果を確認することができます。

応用例

ここでは、基本的な二つの四角形の当たり判定を応用して、複数の四角形や動的な四角形の当たり判定を行う方法について解説します。

複数の四角形の当たり判定

複数の四角形の当たり判定を行う場合、基本的な考え方は二つの四角形の当たり判定と同じです。

ただし、全ての四角形の組み合わせについて当たり判定を行う必要があります。

以下に、複数の四角形の当たり判定を行うコード例を示します。

class Rectangle:
    def __init__(self, x, y, width, height):
        self.x = x
        self.y = y
        self.width = width
        self.height = height
def is_collision(rect1, rect2):
    return not (rect1.x + rect1.width < rect2.x or
                rect1.x > rect2.x + rect2.width or
                rect1.y + rect1.height < rect2.y or
                rect1.y > rect2.y + rect2.height)
# 複数の四角形を定義
rectangles = [
    Rectangle(0, 0, 100, 100),
    Rectangle(50, 50, 100, 100),
    Rectangle(200, 200, 100, 100)
]
# 全ての四角形の組み合わせについて当たり判定を行う
for i in range(len(rectangles)):
    for j in range(i + 1, len(rectangles)):
        if is_collision(rectangles[i], rectangles[j]):
            print(f"Rectangle {i} and Rectangle {j} are colliding.")

このコードでは、rectanglesリストに複数の四角形を定義し、全ての組み合わせについて当たり判定を行っています。

動的な四角形の当たり判定

動的な四角形の当たり判定では、四角形が移動したり回転したりする場合の当たり判定を行います。

ここでは、移動する四角形と回転する四角形の当たり判定について解説します。

移動する四角形の当たり判定

移動する四角形の当たり判定は、四角形の位置を更新しながら当たり判定を行います。

以下に、移動する四角形の当たり判定を行うコード例を示します。

import pygame
import sys
class Rectangle:
    def __init__(self, x, y, width, height):
        self.x = x
        self.y = y
        self.width = width
        self.height = height
    def move(self, dx, dy):
        self.x += dx
        self.y += dy
def is_collision(rect1, rect2):
    return not (rect1.x + rect1.width < rect2.x or
                rect1.x > rect2.x + rect2.width or
                rect1.y + rect1.height < rect2.y or
                rect1.y > rect2.y + rect2.height)
# Pygameの初期設定
pygame.init()
screen = pygame.display.set_mode((400, 400))
clock = pygame.time.Clock()
# 四角形を定義
rect1 = Rectangle(0, 0, 100, 100)
rect2 = Rectangle(200, 200, 100, 100)
# メインループ
while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
    # 四角形を移動
    rect1.move(1, 1)
    color1 = (255, 0, 0)
    color2 = (0, 0, 255)
    # 当たり判定
    if is_collision(rect1, rect2):
        print("Collision detected!")
        color1 = (0, 255, 0)
    # 画面の描画
    screen.fill((255, 255, 255))
    pygame.draw.rect(screen, color1, (rect1.x, rect1.y, rect1.width, rect1.height))
    pygame.draw.rect(screen, color2, (rect2.x, rect2.y, rect2.width, rect2.height))
    pygame.display.flip()
    clock.tick(60)

このコードでは、rect1を毎フレーム移動させ、rect2との当たり判定を行っています。

当たり判定が発生すると、コンソールに Collision detected! と表示されます。

回転する四角形の当たり判定

回転する四角形の当たり判定は、少し複雑になります。

回転する四角形の当たり判定を行うためには、四角形の頂点の位置を計算し、それらの頂点を使って当たり判定を行います。

以下に、回転する四角形の当たり判定を行うコード例を示します。

import pygame
import sys
import math
class Rectangle:
    def __init__(self, x, y, width, height, angle=0):
        self.x = x
        self.y = y
        self.width = width
        self.height = height
        self.angle = math.radians(angle)  # 角度をラジアンに変換
    def rotate(self, angle):
        self.angle += math.radians(angle)  # 角度をラジアンに変換して追加
    def get_vertices(self):
        cx, cy = self.x + self.width / 2, self.y + self.height / 2
        vertices = []
        for dx, dy in [(-self.width / 2, -self.height / 2), (self.width / 2, -self.height / 2), 
                       (self.width / 2, self.height / 2), (-self.width / 2, self.height / 2)]:
            x = cx + dx * math.cos(self.angle) - dy * math.sin(self.angle)
            y = cy + dx * math.sin(self.angle) + dy * math.cos(self.angle)
            vertices.append((x, y))
        return vertices

def project_polygon(axis, vertices):
    min_proj = float('inf')
    max_proj = float('-inf')
    for x, y in vertices:
        proj = (x * axis[0] + y * axis[1])
        min_proj = min(min_proj, proj)
        max_proj = max(max_proj, proj)
    return min_proj, max_proj

def is_projection_overlap(proj1, proj2):
    return not (proj1[1] < proj2[0] or proj2[1] < proj1[0])

def is_collision(rect1, rect2):
    vertices1 = rect1.get_vertices()
    vertices2 = rect2.get_vertices()
    axes = []

    for i in range(len(vertices1)):
        x1, y1 = vertices1[i]
        x2, y2 = vertices1[(i + 1) % len(vertices1)]
        edge = (x2 - x1, y2 - y1)
        normal = (-edge[1], edge[0])
        axes.append(normal)

    for i in range(len(vertices2)):
        x1, y1 = vertices2[i]
        x2, y2 = vertices2[(i + 1) % len(vertices2)]
        edge = (x2 - x1, y2 - y1)
        normal = (-edge[1], edge[0])
        axes.append(normal)

    for axis in axes:
        proj1 = project_polygon(axis, vertices1)
        proj2 = project_polygon(axis, vertices2)
        if not is_projection_overlap(proj1, proj2):
            return False

    return True

# Pygameの初期設定
pygame.init()
screen = pygame.display.set_mode((400, 400))
clock = pygame.time.Clock()
# 四角形を定義
rect1 = Rectangle(115, 115, 100, 100)
rect2 = Rectangle(200, 200, 100, 100)
# メインループ
while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
    # 四角形を回転
    rect1.rotate(1)
    rect2.rotate(-0.66)
    color1 = (255, 0, 0)
    # 当たり判定
    if is_collision(rect1, rect2):
        print("Collision detected!")
        color1 = (0, 255, 0)
    # 画面の描画
    screen.fill((255, 255, 255))
    pygame.draw.polygon(screen, color1, rect1.get_vertices())
    pygame.draw.polygon(screen, (0, 0, 255), rect2.get_vertices())
    pygame.display.flip()
    clock.tick(60)
衝突判定のテスト

このコードでは、rect1を毎フレーム回転させ、rect2との当たり判定を行っています。

当たり判定が発生すると、コンソールに Collision detected! と表示されます。

回転する四角形の頂点を計算し、それを使って描画しています。

回転しない四角形と比べるとかなり複雑ですが、上記のコードを扱うことで回転した二つの四角形の衝突判定を行うことができます。

以上で、複数の四角形の当たり判定と動的な四角形の当たり判定についての解説を終わります。

これらの応用例を参考にして、より複雑な当たり判定を実装してみてください。

目次から探す