ロボ団群馬のブログ

Pyxelで衝突判定(あたり判定)を作る方法|矩形・円・タイル・実践パターン

Pyxelで衝突判定(あたり判定)を作る方法|矩形・円・タイル・実践パターン

Pythonでレトロゲームを作れるPyxel(ピクセル)
今回は「衝突判定(あたり判定)」を、最小実装 → 応用の順でやさしく解説します。
プレイヤーと敵、アイテムの取得、壁との当たり、タイルマップとの衝突など、ゲーム作りの必須テクニックをまとめて学べます。


まずは覚えておきたい:3つの基本判定

1) 点が矩形に入っているか


def point_in_rect(px, py, x, y, w, h):
    return (x <= px < x + w) and (y <= py < y + h)

2) 矩形と矩形(AABB)の衝突

一番よく使う基本。AABB(Axis-Aligned Bounding Box)=回転していない四角どうしの判定です。


def rect_rect(ax, ay, aw, ah, bx, by, bw, bh):
    return (ax < bx + bw and ax + aw > bx and
            ay < by + bh and ay + ah > by)

3) 円と円の衝突


def circle_circle(x1, y1, r1, x2, y2, r2):
    dx = x1 - x2
    dy = y1 - y2
    return dx * dx + dy * dy <= (r1 + r2) * (r1 + r2)

最小の動くサンプル:プレイヤー四角 vs 壁四角

矢印キーで動くプレイヤー(8×8)と、固定の壁(32×8)で、AABB判定を使います。
コツ:「x方向 → 判定・修正」「y方向 → 判定・修正」の順に分けて解決(分離軸の基本)。


import pyxel

W, H = 160, 120

def rect_rect(ax, ay, aw, ah, bx, by, bw, bh):
    return (ax < bx + bw and ax + aw > bx and
            ay < by + bh and ay + ah > by)

class App:
    def __init__(self):
        pyxel.init(W, H, title="AABB Collision")
        self.x, self.y = 20, 20
        self.w, self.h = 8, 8
        # 壁
        self.wx, self.wy, self.ww, self.wh = 60, 60, 32, 8
        pyxel.run(self.update, self.draw)

    def update(self):
        vx = (pyxel.btn(pyxel.KEY_RIGHT) - pyxel.btn(pyxel.KEY_LEFT)) * 2
        vy = (pyxel.btn(pyxel.KEY_DOWN) - pyxel.btn(pyxel.KEY_UP)) * 2

        # --- X方向を先に移動・判定 ---
        nx = self.x + vx
        if rect_rect(nx, self.y, self.w, self.h, self.wx, self.wy, self.ww, self.wh):
            # 右に進む場合:壁の左側で止める/左に進む場合:壁の右側で止める
            if vx > 0:
                nx = self.wx - self.w
            elif vx < 0:
                nx = self.wx + self.ww
        self.x = max(0, min(W - self.w, nx))

        # --- Y方向を次に移動・判定 ---
        ny = self.y + vy
        if rect_rect(self.x, ny, self.w, self.h, self.wx, self.wy, self.ww, self.wh):
            if vy > 0:
                ny = self.wy - self.h
            elif vy < 0:
                ny = self.wy + self.wh
        self.y = max(0, min(H - self.h, ny))

    def draw(self):
        pyxel.cls(0)
        pyxel.rect(self.x, self.y, self.w, self.h, 11)   # プレイヤー
        pyxel.rect(self.wx, self.wy, self.ww, self.wh, 8) # 壁

App()

これで、壁にめり込まずにカチッと止まる感覚が作れます。
当たりで引っかかる場合は、移動→判定→補正の順序を再確認してください。


アイテム取得:プレイヤー矩形 vs アイテム矩形

当たったら「取得済み」にして非表示にする典型パターン。


# プレイヤー:x,y,w,h / アイテム:ix,iy,iw,ih / gotフラグ
if not got and rect_rect(x, y, w, h, ix, iy, iw, ih):
    got = True  # スコア加算や効果音再生もここで

タイルマップと衝突(8×8タイル)

PyxelのTILEMAPは8×8のタイルで構成されます。
基本は「プレイヤーの足元や先端の座標 → タイル座標に変換 → そのタイルが壁かどうかを見る」という流れです。

1) タイル座標へ変換


TILE = 8

def to_tile(v):
    return int(v // TILE)

2) タイルが「壁」か調べる(簡易:タイルマップのタイル種で判定)

Pyxel Editorで、壁として使うタイル(画像上の位置)を決めておき、集合で管理します。


# 例:壁に使うタイルの「画像上のU,V(8の倍数)」を登録
SOLID_TILES = {(0, 0), (8, 0), (16, 0)}  # 必要に応じて追加

def is_solid_tile(tx, ty):
    # マップ範囲外は壁扱い(落下防止など)
    if tx < 0 or ty < 0:
        return True
    try:
        u, v = pyxel.tilemap(0).pget(tx, ty)  # タイルの画像座標を取得
    except Exception:
        return True
    return (u, v) in SOLID_TILES

3) 進行方向の先端だけチェックして止める

移動予定位置に対して、接触しそうな角のタイルだけを調べるのが軽量です。


def move_and_collide(x, y, w, h, vx, vy):
    # 先にX
    nx = x + vx
    if vx > 0:
        # 右上・右下の角
        if is_solid_tile(to_tile(nx + w - 1), to_tile(y)) or \
           is_solid_tile(to_tile(nx + w - 1), to_tile(y + h - 1)):
            nx = (to_tile(nx + w - 1)) * TILE - w  # 壁の手前で止める
    elif vx < 0:
        # 左上・左下
        if is_solid_tile(to_tile(nx), to_tile(y)) or \
           is_solid_tile(to_tile(nx), to_tile(y + h - 1)):
            nx = (to_tile(nx) + 1) * TILE

    # 次にY
    ny = y + vy
    if vy > 0:
        # 右下・左下
        if is_solid_tile(to_tile(nx), to_tile(ny + h - 1)) or \
           is_solid_tile(to_tile(nx + w - 1), to_tile(ny + h - 1)):
            ny = (to_tile(ny + h - 1)) * TILE - h
    elif vy < 0:
        # 右上・左上
        if is_solid_tile(to_tile(nx), to_tile(ny)) or \
           is_solid_tile(to_tile(nx + w - 1), to_tile(ny)):
            ny = (to_tile(ny) + 1) * TILE

    return nx, ny

ポイント:
「X→判定→補正」「Y→判定→補正」の順を守ると、角でガタつきにくくなります。


円形の当たり(近接判定・攻撃範囲など)

当たりが丸い(近接距離で判定したい)ときは円判定が簡単です。


if circle_circle(player_x, player_y, 6, enemy_x, enemy_y, 6):
    # 当たり!
    hp -= 1

画像スプライトの当たりサイズを調整する(当たり枠)

見た目が16×16でも、当たりだけ12×12などにして「当たり負け」を減らすのが実用的です。


# 見た目は16x16、当たりは12x12(上下左右に2pxの余白)
HIT_W, HIT_H = 12, 12
hit_x = x + 2
hit_y = y + 2
# 以後の判定は hit_x, hit_y, HIT_W, HIT_H で行う

よくあるハマりポイント(FAQ)

  • 角でガタつく/引っかかる:「X→補正→Y→補正」の順に分ける。1フレームの移動量が大きすぎる場合は小さくする。
  • タイルとすり抜ける:チェックする角の数が足りない、またはタイル座標の丸め(floor)が合っていない可能性。// 8(8で割る)で確実に。
  • タイル種類の見分け:pyxel.tilemap(0).pget(tx, ty) が返す(画像内の座標)が「壁扱いの集合」に含まれているかで判定するのが簡単。
  • 処理が重い:接触可能性のある「近傍だけ判定」する(四分木やグリッド分割が理想、まずは“距離が近い相手だけ”の簡易フィルタでも効果大)。

まとめ:まずはAABB→タイル→応用の順で固めよう

  • 最初に覚える:矩形×矩形(AABB)
  • 背景と当たる:8×8タイルで角だけ判定(X→Yの順で補正)
  • 演出を上げる:円判定や当たり枠の微調整

衝突判定が安定すると、ゲームの気持ち良さが一気に上がります。
次は「ダッシュ/坂/斜面」「当たり判定ごとの状態遷移(無敵時間・ノックバック)」に挑戦してみましょう!

関連記事

コメント

  1. この記事へのコメントはありません。

  1. この記事へのトラックバックはありません。

ページ上部へ戻る