タイトル画像
琴葉茜

さて、ホッケーの動きとバーの動きが出来たことだし、次はこの2つの衝突判定を考えていこうか

琴葉葵

この衝突判定って、「ウィンドウから出てしまわないようにする」時の考え方と同じになるのかな?

琴葉茜

そうだね!だけど、前回までと大きく違う点は「判定対象が両方とも大きさを持っている」ってことだね

琴葉葵

今回は両方とも動くものだし、イメージするのがなかなか難しそう...

琴葉茜

そうでしょ
だから、まずはホッケーとバーの衝突範囲の関係を図にしてみよう

解説画像1
琴葉葵

図を見た感じ、両方動くものの場合は片方を固定して考えるのが良さそうだね

琴葉茜

そういうことだね!
今回の場合、幅と高さが同じ「ホッケー」を固定して考えると分かりやすいかな

琴葉茜

ホッケーの上下左右について、それぞれどの範囲でバーが触れているか条件式を作って、その全てが当てはまる場合に「衝突した!」って考えていけば良いよ

琴葉葵

ほうほう

琴葉茜

例えば、ホッケーの左側のみに注目して条件を考えるとどうなると思う??
バーのx座標は「self.bar_x」、バーの幅は「self.bar_width」として考えてみて!

琴葉葵

「ホッケーの中心のx座標 - ホッケーの半径」がホッケーの左端で、「バーの左上のx座標 + バーの幅」がバーの右端になって、バーがホッケーのx座標より小さくなると接触していることにならないから...

琴葉葵

「self.x - self.r <= self.bar_x + self.bar_width」がホッケーの左側についての条件だね!

琴葉茜

正解!あとは、同じような考え方で残りの条件式を考えて、すべての条件を「and」で繋げてあげればバッチリだよ!

琴葉葵

うへー大変だ汗

琴葉茜

今回は練習だから、事前に用意しておくよ!
もし、「自分で条件式を考えてみたい!」って場合は下のプログラムを見る前に自分でチャレンジしてみてね

# 関数「update」の最後に追加
# 4つの条件式で当たり判定
if (
    self.x + self.r >= self.bar_x and # ホッケーの右側
    self.x - self.r <= self.bar_x + self.bar_width and # ホッケーの左側
    self.y + self.r >= self.bar_y and # ホッケーの下側
    self.y - self.r <= self.bar_y + self.bar_height # ホッケーの上側
):
    self.vx = abs(self.vx)  # 絶対値「abs()」を使うことで、必ず数値をプラスに出来る
琴葉葵

バーのx座標「self.bar_x」と、バーの幅「self.bar_width」を追加しておかないとだね!

# 関数「__init__」内を変更
self.bar_height = 20 # バーの高さ
self.bar_width = 5 # バーの幅
self.bar_x = 10 # バーのx座標
self.bar_y = (pyxel.height - self.bar_height) // 2 # バーのy座標
# 関数「draw」内を変更
pyxel.rect(self.bar_x, self.bar_y, self.bar_width, self.bar_height, 11)
琴葉葵

よし!ちゃんと変更完了!!
バーにホッケーを当てたらちゃんと跳ね返ったよ!!

琴葉茜

良い感じだね!Pyxelでは衝突判定の考え方がちょっと大変だから、慣れるまでは何度もトライアンドエラーで頑張ろう!

この時点での全体のプログラム

import pyxel

class App:
    def __init__(self):
        pyxel.init(160, 120, title="ホッケーゲーム")
        # ホッケーの座標、速度、半径
        self.x = pyxel.width // 2
        self.y = pyxel.height // 2
        self.vx = 1
        self.vy = 1
        self.r = 4 

        # プレイヤー(左側)のバー
        self.bar_height = 20 # バーの高さ
        self.bar_width = 5 # バーの幅
        self.bar_x = 10 # プレイヤーのx座標
        self.bar_y = (pyxel.height - self.bar_height) // 2 # プレイヤーのy座標

        pyxel.run(self.update, self.draw)

    def update(self):
        self.x += self.vx
        self.y += self.vy
        
        # プレイヤーバーの操作(上下キー)
        if pyxel.btn(pyxel.KEY_UP):
            self.bar_y -= 2
        if pyxel.btn(pyxel.KEY_DOWN):
            self.bar_y += 2

        # 画面外に出ないように制限
        self.bar_y = max(0, min(pyxel.height - self.bar_height, self.bar_y))

        # ウィンドウの端に当たったら反射(跳ね返る)
        if self.x <= self.r or self.x >= pyxel.width - self.r:
            self.vx *= -1
        if self.y <= self.r or self.y >= pyxel.height - self.r:
            self.vy *= -1

        # 4つの条件式で当たり判定
        if (
            self.x + self.r >= self.bar_x and # ホッケーの右側
            self.x - self.r <= self.bar_x + self.bar_width and # ホッケーの左側
            self.y + self.r >= self.bar_y and # ホッケーの下側
            self.y - self.r <= self.bar_y + self.bar_height # ホッケーの上側
        ):
            self.vx = abs(self.vx)  # 絶対値「abs()」を使うことで、必ず数値をプラスに出来る

    def draw(self):
        pyxel.cls(0)
        pyxel.circ(self.x, self.y, self.r, 8)
        pyxel.rect(self.bar_x, self.bar_y, self.bar_width, self.bar_height, 11)
App()