3. 敵に弾が当たった!コードで実現する命中判定
それでは、いよいよ弾が敵に当たったときの処理を実装していきましょう。
前回のコードから、update 関数の中にある敵の更新処理 (enemies リストを処理する部分) に変更を加えます。
update 関数は毎フレーム、ゲームの状態を更新する場所でしたね。
もしもの魔法自機移動
変更点その1: 当たり判定の定数と spawn_enemy の微調整
まず、敵のサイズに関する定数 ENEMY_W と ENEMY_H を追加します。
今回のコードでは敵の幅と高さをそれぞれ 8 に設定しています。
# --- 定数設定 ---
SCREEN_WIDTH = 160
SCREEN_HEIGHT = 120
GAME_TITLE = "ASTRO SURVIVOR"
STAR_COUNT = 40
# キャラクターのサイズ
PLAYER_W = 8
PLAYER_H = 8
PLAYER_SPEED = 3
ENEMY_W = 8 # ★追加★
ENEMY_H = 8 # ★追加★
変更点その2: update 関数内の当たり判定ロジック
update 関数の中の for e in enemies: のループに注目してください。
ここが今回追加する、当たり判定のメインの部分です。
# 敵の更新
surviving_enemies = []
for e in enemies:
e['y'] += e['vy']
if e['y'] > SCREEN_HEIGHT:
continue
# ここから追加・変更される部分
hit = False # 弾が当たったかどうかを記録するフラグ
# 弾のリストを逆順に見ていく
for b_idx in reversed(range(len(bullets))):
bx, by = bullets[b_idx][0], bullets[b_idx][1] # 弾の座標を取得
# 弾の座標が敵の四角形の中にあるか判定
if (e['x'] < bx < e['x'] + ENEMY_W and
e['y'] < by < e['y'] + ENEMY_H):
# 当たっていたら、その弾をリストから削除
del bullets[b_idx]
hit = True # フラグをTrueにする
break # この敵に当たった弾が見つかったので、次の弾のチェックは不要
# 弾が当たっていたら、この敵は削除対象なのでsurviving_enemiesには追加しない
if hit:
continue
surviving_enemies.append(e) # 当たらなかった敵は生き残る
enemies = surviving_enemies # 生き残った敵だけでリストを更新
新しいコードは、敵のループ (for e in enemies:) の中に、さらに弾のループ (for b_idx in reversed(range(len(bullets))):) を入れ子にしています。
これは「全ての敵に対して、全ての弾が当たったかどうかをチェックする」という処理を行っているためです。
当たり判定の条件式を詳しく見よう
特に重要なのは、この部分です。
if (e['x'] < bx < e['x'] + ENEMY_W and
e['y'] < by < e['y'] + ENEMY_H):
これは、「弾の x 座標 (bx) が、敵の x 座標 (e['x']) より大きく、かつ、敵の右端 (e['x'] + ENEMY_W) より小さい」という条件と、「弾の y 座標 (by) が、敵の y 座標 (e['y']) より大きく、かつ、敵の下端 (e['y'] + ENEMY_H) より小さい」という条件を同時に満たすかどうかをチェックしています。
イメージとしては、敵の四角形の左上座標を (e['x'], e['y'])、右下座標を (e['x'] + ENEMY_W, e['y'] + ENEMY_H) としたときに、弾の左上座標 (bx, by) がその範囲内に完全に収まっているかを判定しています。
補足だよ
弾の描画サイズは幅2、高さ4ですが、ここでは弾の左上座標(1点)が敵の領域内に入ったかで判定しています。より厳密な四角形同士の当たり判定は check_collision のような関数を使うことが多いですが、まずはシンプルな点判定で動作を確認しましょう。
reversed() と del の使い方
弾のリストを処理する部分で、for b_idx in reversed(range(len(bullets))): と del bullets[b_idx] という記述が出てきました。
reversed() でリストを逆順に処理する理由は何ですか?
リストから要素を削除する際、前から順番に削除すると、リストの要素数が減り、インデックスがずれてしまいます。例えば、bullets の0番目を削除すると、元の1番目が新しい0番目になります。そのままループを続けると、処理がスキップされてしまったり、範囲外エラーが発生したりする可能性があります。
しかし、後ろから順番に削除していけば、それより前のインデックスがずれる心配がないため、安全に要素を削除できるのです。
del bullets[b_idx] は、指定したインデックス b_idx の要素をリスト bullets から削除するPythonの命令です。
弾が敵に当たったら、その弾は消えるべきなので、この命令を使ってリストから取り除きます。
break と continue でループを制御する
-
break:この文に出会うと、現在実行中の for ループ(または while ループ)を直ちに終了し、ループの次の行に処理を進めます。
今回のコードでは、1つの敵に弾が当たったら、それ以上この敵に対して他の弾が当たるかチェックする必要はないので、break で弾のループを抜けています。
-
continue:この文に出会うと、ループの残りの処理をスキップし、次のループの繰り返しへと進みます。
もし弾が当たった敵 (if hit:) があれば、その敵は撃破されるので、surviving_enemies リストには追加せず、次の敵の処理へと移っています。
豆知識
break と continue は、ループ処理を効率的に、かつ意図通りに制御するために非常に役立つ命令です。Pythonの基本的な制御フローなので、ぜひ使いこなしてください。