もどる

この記事は「うずら」とAIが協力して作成しました。
なるべく正確さを心がけていますが、最新の公式ドキュメントなどもあわせて確認してみてね!

python2026.3.10

3-7. 絶体絶命!被弾判定とゲームオーバー

実践:激突!
ゲームオーバー

皆さん、こんにちは!世界最高に親切な技術講師うずらです。

前回の記事では、自機の弾が敵に命中したときの「当たり判定」を実装し、敵を撃破できるようになりましたね!実践:命中!敵を撃破 敵を撃ち落とす爽快感は格別だったのではないでしょうか。

今回は、いよいよ「自機が敵に当たってしまったときの被弾判定」、そして「ゲームオーバー画面」の実装です。ゲームには終わりがあるからこそ、面白さが増します。ゲームの状態をPythonでどう管理するのか、ワクワクしながら学んでいきましょう!

1. 3秒でわかるまとめ

ゲームの状態を管理する game_over という変数を導入し、自機と敵が衝突したらこの変数を True にしてゲームオーバー画面を表示します。

2. 絶体絶命!自機と敵の当たり判定

ゲームがスリリングになるのは、ピンチがあるからこそです。
まずは、自機が敵に接触したときの「被弾判定」を実装しましょう。これがゲームオーバーのトリガーとなります。

update() 関数内の敵の更新ループに、以下のコードを追加します。

# 敵の更新
surviving_enemies = []
for e in enemies:
    e['y'] += e['vy']
        
    if e['y'] > SCREEN_HEIGHT:
        continue

    # --- ここから追加 ---
    # 敵の当たり判定サイズを設定 (このコードスナップショットにはENEMY_W定義がないため、別途定義が必要です)
    # 例: ENEMY_W = 8 を定数セクションに追加してください
    hit_w = ENEMY_W 
    if check_collision(player_x, player_y, PLAYER_W, PLAYER_H, e['x'], e['y'], hit_w, hit_w):
        game_over = True # ゲームオーバーフラグを立てる
    # --- ここまで追加 ---

    hit = False
    # ... 弾との当たり判定 ...

補足だよ

上記コードの ENEMY_W は、今回のコードスナップショットには定義されていませんが、ゲームの動作には必須です。SCREEN_HEIGHTなどの定数設定セクションに ENEMY_W = 8ENEMY_H = 8 を追加してください。これにより、敵の当たり判定サイズが定義されます。

check_collision 関数は、前回の記事 実践:命中!敵を撃破 で登場しました。
これは、2つの矩形(長方形)が重なっているかを判定し、重なっていれば True、そうでなければ False を返す関数です。

今回は、player_x, player_y, PLAYER_W, PLAYER_H (自機) と e['x'], e['y'], hit_w, hit_w (敵) の衝突を判定しています。
もし衝突が検出されたら、game_over = True とすることで、ゲームが終了したことを示すフラグを立てています。

豆知識

game_over = True のように、ゲームの状態を示す真偽値(True/False)を「フラグ」と呼びます。このフラグを使ってゲームの進行を制御することは、ゲーム開発において非常に一般的な手法です。

3. ゲームの終了状態を管理する「フラグ」

「ゲームオーバー」という新しいゲームの状態を導入するために、game_over という真偽値(ブール値)変数を使います。これは、ゲームの進行をコントロールするための大切な「フラグ」となります。

3.1. game_over 変数の初期化

まず、変数の初期化セクションに game_over 変数を追加します。

# ゲーム進行状況
score = 0
game_over = False # ここを追加

ゲーム開始時はまだゲームオーバーではないので、False を設定します。

3.2. ゲームオーバー中の処理停止

update() 関数の一番最初に、以下のコードを追加してください。

def update():
    global player_x, player_y, score, game_over # game_over を追加
    global bullets, enemies

    # --- 1. ゲームオーバー / クリアチェック ---
    if game_over: # ここから追加
        return    # ここまで追加

    # ... プレイヤー操作やオブジェクト更新の処理 ...

if game_over: は「もし game_overTrue なら」という意味です。
return は、関数の実行をそこで終了させる命令です。
つまり、ゲームオーバーになったら、その後のプレイヤー操作や弾・敵の更新処理は一切行われなくなり、ゲームの動きが止まります。

4. 勝利か敗北か?スコアとゲームオーバー表示

ゲームオーバーになったら、そのことをプレイヤーに伝える画面を表示する必要があります。また、これまでのスコアも表示しましょう。

4.1. スコア変数の追加と更新

score 変数を初期化し、敵を倒したときにスコアを加算する処理を追加します。

# ゲーム進行状況
score = 0 # ここを追加
game_over = False

そして、前回の記事で弾と敵の当たり判定を行った部分を思い出してください。実践:命中!敵を撃破
敵が弾に当たって消滅する際に、スコアを加算する処理を追加します。

# 敵の更新
surviving_enemies = []
for e in enemies:
    # ... プレイヤーとの当たり判定 ...

    hit = False
    for b_idx in reversed(range(len(bullets))):
        # ... 弾と敵の当たり判定ロジック ...
        if (e['x'] < bx < e['x'] + ENEMY_W and
            e['y'] < by < e['y'] + ENEMY_H):
            del bullets[b_idx]
            hit = True
            break
    
    if hit:
        score += e['score'] # ここを追加!
        continue
    
    surviving_enemies.append(e)
enemies = surviving_enemies

敵の辞書 e には score というキーが設定されていますね。score += e['score'] で、その敵を倒したときに得られる点数を現在のスコアに加算しています。

4.2. ゲームオーバー画面の描画

draw() 関数に、ゲームオーバー時の表示処理を追加します。

def draw():
    pyxel.cls(COL_BLACK)
    
    # 背景
    for star in stars:
        pyxel.pset(star[0], star[1], COL_WHITE if star[2] > 1 else COL_GRAY)

    # ゲームオーバー判定
    if game_over: # ここから追加
        pyxel.text(55, 50, "GAME OVER", COL_RED)
        pyxel.text(45, 60, f"SCORE: {score}", COL_WHITE)
        return    # ここまで追加
        
    # プレイヤー
    pyxel.rect(player_x, player_y, PLAYER_W, PLAYER_H, COL_ORANGE)
    pyxel.rect(player_x+3, player_y-2, 2, 2, COL_YELLOW)

    # ... 弾、敵の描画 ...

    # UI
    pyxel.text(5, 5, f"SCORE:{score}", COL_WHITE) # ここを追加

ここでも if game_over: を使って、ゲームオーバーかどうかをチェックしています。
もし game_overTrue なら、「GAME OVER」というテキストと最終スコアを表示します。
その後 return を記述することで、通常のプレイヤーや弾、敵の描画は行わずに、ゲームオーバー画面だけが表示され続けます。

補足だよ

pyxel.text(x, y, text, color) は、指定した座標にテキストを表示するPyxelの関数です。f"SCORE: {score}" のように f を文字列の前に付けると、波括弧 {} の中に変数名を直接記述でき、その変数の値が文字列に埋め込まれます。これを「f-string(フォーマット済み文字列リテラル)」と呼びます。

また、画面左上に常にスコアを表示するために、draw() 関数の最後に pyxel.text(5, 5, f"SCORE:{score}", COL_WHITE) を追加しています。

これで、自機が敵に当たるとゲームが停止し、「GAME OVER」とスコアが表示されるようになりました。

5. 今回のまとめと完成コード

今回は、自機と敵の当たり判定、そしてゲームの状態を管理する「ゲームオーバーフラグ」について学びました。

  • game_over というフラグ変数を導入し、ゲームの状態を管理しました。
  • update() 関数内で自機と敵の当たり判定を行い、衝突したら game_over フラグを True にしました。
  • update() 関数と draw() 関数の冒頭で game_over フラグをチェックし、True の場合はその後の処理を return でスキップするようにしました。
  • draw() 関数でゲームオーバー時の専用画面とスコア表示を実装しました。

ゲームの状態を管理するフラグの概念は、ゲーム開発だけでなく、様々なプログラミングで応用できる大切な考え方です。
ぜひ実際にコードを動かして、ゲームオーバーを体験してみてください!

豆知識

今はゲームオーバーになったらリスタートできませんが、今後のステップで「タイトル画面に戻る」「もう一度プレイする」といった機能を追加すると、より本格的なゲームになります。これは game_over フラグを False に戻したり、ゲームの状態を初期化したりすることで実現できます。

3-7_game_over.py

import pyxel
import random

# =========================================================
#  ASTRO SURVIVOR
#  - 宇宙シューティングゲーム -
# =========================================================

# --- 定数設定 ---
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 # この行を追加

# 色の定義
COL_BLACK   = 0
COL_NAVY    = 1
COL_PURPLE  = 2
COL_GREEN   = 3
COL_BROWN   = 4
COL_DBLUE   = 5
COL_LBLUE   = 6
COL_WHITE   = 7
COL_RED     = 8
COL_ORANGE  = 9
COL_YELLOW  = 10
COL_L_GREEN = 11
COL_CYAN    = 12
COL_GRAY    = 13
COL_PINK    = 14
COL_PEACH   = 15


# --- Pyxelの初期化と音の定義 ---
pyxel.init(SCREEN_WIDTH, SCREEN_HEIGHT, title=GAME_TITLE)

# --- 変数の初期化 ---
# プレイヤー
player_x = SCREEN_WIDTH // 2 - PLAYER_W // 2
player_y = SCREEN_HEIGHT - 20

# ゲームオブジェクト
bullets   = [] # [x, y, vx, vy]
enemies   = [] # Dictionary {'x', 'y', 'type', ...}
stars     = [] # [x, y, speed]

# 星空の準備
for _ in range(STAR_COUNT):
    stars.append([
        random.randint(0, SCREEN_WIDTH),
        random.randint(0, SCREEN_HEIGHT),
        random.randint(1, 3)
    ])

# ゲーム進行状況
score = 0
game_over = False


# =========================================================
#  関数定義 (エフェクト生成など)
# =========================================================

# 敵を出現させる
def spawn_enemy():
    enemies.append({
        'x': random.randint(0, SCREEN_WIDTH - ENEMY_W),
        'y': -10,
        'vx': 0,
        'vy': 1.0,
        'hp': 1,
        'w': ENEMY_W, 'h': ENEMY_H,
        'score': 10
    })

# 矩形同士の当たり判定
def check_collision(x1, y1, w1, h1, x2, y2, w2, h2):
    return (x1 < x2 + w2 and x1 + w1 > x2 and
            y1 < y2 + h2 and y1 + h1 > y2)


# =========================================================
#  メインループ (Update & Draw)
# =========================================================

# 毎フレーム実行される更新処理 (計算などはここで行う。描画は禁止)
def update():
    global player_x, player_y, score, game_over
    global bullets, enemies

    # --- 1. ゲームオーバー / クリアチェック ---
    if game_over:
        return

    # --- 3. プレイヤー操作 ---
    if pyxel.btn(pyxel.KEY_LEFT):  player_x = max(player_x - PLAYER_SPEED, 0)
    if pyxel.btn(pyxel.KEY_RIGHT): player_x = min(player_x + PLAYER_SPEED, SCREEN_WIDTH - PLAYER_W)
    if pyxel.btn(pyxel.KEY_UP):    player_y = max(player_y - PLAYER_SPEED, 0)
    if pyxel.btn(pyxel.KEY_DOWN):  player_y = min(player_y + PLAYER_SPEED, SCREEN_HEIGHT - PLAYER_H)

    # 弾の発射
    if pyxel.btnp(pyxel.KEY_SPACE):
        bullets.append([player_x + 3, player_y - 4, 0, -5])

    # --- 4. オブジェクト更新 ---
    # 弾の移動
    surviving_bullets = []
    for b in bullets:
        b[0] += b[2] # vx
        b[1] += b[3] # vy
        # 画面外でなければ残す
        if b[1] >= -10 and b[0] >= -10 and b[0] <= SCREEN_WIDTH + 10:
            surviving_bullets.append(b)
    bullets = surviving_bullets

    # 敵の出現
    if pyxel.frame_count % 30 == 0:
        spawn_enemy()


    # 敵の更新
    surviving_enemies = []
    for e in enemies:
        e['y'] += e['vy']
            
        if e['y'] > SCREEN_HEIGHT:
            continue

        hit_w = ENEMY_W
        if check_collision(player_x, player_y, PLAYER_W, PLAYER_H, e['x'], e['y'], hit_w, hit_w):
            game_over = True
            
        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
                break
        
        if hit:
            score += e['score']
            continue
        
        surviving_enemies.append(e)
    enemies = surviving_enemies

# 毎フレーム実行される描画処理 (結果を画面に表示する。計算はしない)
def draw():
    pyxel.cls(COL_BLACK)
    
    # 背景
    for star in stars:
        pyxel.pset(star[0], star[1], COL_WHITE if star[2] > 1 else COL_GRAY)

    # ゲームオーバー
    if game_over:
        pyxel.text(55, 50, "GAME OVER", COL_RED)
        pyxel.text(45, 60, f"SCORE: {score}", COL_WHITE)
        return
        
    # プレイヤー
    pyxel.rect(player_x, player_y, PLAYER_W, PLAYER_H, COL_ORANGE)
    pyxel.rect(player_x+3, player_y-2, 2, 2, COL_YELLOW)

    # 弾
    for b in bullets:
        pyxel.rect(b[0], b[1], 2, 4, COL_L_GREEN)

    # 敵
    for e in enemies:
        pyxel.rect(e['x'], e['y'], e['w'], e['h'], COL_GRAY)
        pyxel.rect(e['x']+2, e['y']+2, 4, 4, COL_CYAN)
            
    # UI
    pyxel.text(5, 5, f"SCORE:{score}", COL_WHITE)

pyxel.run(update, draw)

上記のコードを実際にブラウザで動かしてみてください。

  <div class="my-16 max-w-full">
    <div class="bg-gradient-to-br from-purple-50 to-blue-50 border-4 border-purple-200 rounded-2xl p-6 md:p-8 shadow-xl">
      <div class="flex items-center gap-3 mb-6">
        <img src="/icons/icon-denkyu.png" alt="" class="w-8 h-8 flex-shrink-0" />
        <h4 class="font-black text-purple-900 text-xl">実際に動かしてみよう!</h4>
      </div>
      <div class="bg-white rounded-xl overflow-hidden border-2 border-purple-100 shadow-inner">
        <iframe 
          src="/dev-snapshots/run/3-7_game_over"
          class="w-full aspect-[4/3] border-0"
          title="Pyxel Game: 3-7_game_over.py"
          loading="lazy"
        ></iframe>
      </div>
      <p class="text-sm text-purple-700 font-bold mt-4 text-center">
        矢印キーで自機を操作します。

敵に当たるとゲームオーバーです。



最後まで読んでくれてありがとう!🌱
ノートみたいに、いつでも見返してね。