もどる

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

python2026.3.12

4-2. 閃光の演出:爆砕パーティクル

演出の極意
爆砕演出

皆さん、こんにちは!世界最高に親切な技術講師うずらです。
退屈な授業からの脱出!から始まった私たちの冒険も、いよいよゲームの「演出」に踏み込みます。

今回は、敵を撃破したときに画面上で小さな破片が飛び散る「パーティクルエフェクト」を実装し、ゲームにさらなる臨場感と爽快感を与えましょう!

1. 3秒でわかるまとめ

敵を倒した時、小さな光の粒(パーティクル)が四方八方に飛び散る爆発エフェクトを追加します。これはPythonのリストと辞書を使ってパーティクル一つ一つの動きと寿命を管理し、一定時間で自動的に消滅させることで表現できます。

2. 派手に散る!爆砕パーティクルとは?

ゲームをプレイしていて、敵を倒したときに「パチッ」と小さな火花が散ったり、爆発の破片が飛び散ったりするのを見たことはありませんか?
あれが「パーティクルエフェクト」です。

パーティクルとは、小さな粒子のことで、これらをたくさん同時に動かすことで、爆発、煙、水しぶき、魔法の光など、様々な視覚効果を生み出します。
ゲームの演出は、プレイヤーの興奮や没入感を高めるために非常に重要です。

今回の「爆砕パーティクル」では、敵が破壊された瞬間に、小さな光の粒がその場所から勢いよく飛び散り、少しずつ消えていく様子を表現します。

3. パーティクル管理用の「リスト」と「生成関数」を追加しよう

まずは、パーティクルを管理するための新しいリストと、パーティクルを生成するための関数を準備します。

4-1_red_enemy.py のコードを開き、以下の変更を加えてください。

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

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

新しく particles = [] という空のリストを用意しました。
このリストには、生成されるパーティクル一つ一つを辞書として格納していきます。

次に、パーティクルを生成するための関数 spawn_particles を追加しましょう。
これは既存の spawn_enemy 関数と同じように、def キーワードを使って定義します。

# 爆発エフェクトなどを生成する
def spawn_particles(x, y, count, color):
    for _ in range(count):
        particles.append({
            'x': x, 
            'y': y,
            'vx': random.uniform(-2.0, 2.0),
            'vy': random.uniform(-2.0, 2.0),
            'life': random.randint(10, 20),
            'color': color
        })

この spawn_particles 関数は、次の4つの情報を受け取ります。

  • x, y: パーティクルが生成される中心座標
  • count: 生成するパーティクルの数
  • color: パーティクルの色

関数の中では、指定された count の回数だけループを回し、パーティクルを一つずつ particles リストに追加しています。
パーティクル一つは辞書として表現されており、それぞれ以下の情報を持っています。

  • 'x', 'y': パーティクルの現在の座標
  • 'vx', 'vy': 毎フレームどれだけ移動するか(速度ベクトル)。random.uniform(-2.0, 2.0) で -2.0 から 2.0 までの小数値のランダムな速度を与え、様々な方向に飛び散るようにしています。
  • 'life': パーティクルの残り寿命。random.randint(10, 20) でランダムな寿命を与えます。
  • 'color': パーティクルの色。

補足だよ

random.uniform(a, b) は、指定した範囲内で小数点を含むランダムな数値を生成します。これにより、パーティクルはより自然で滑らかな軌道を描き、単なる整数値では表現できないリアリティが生まれます。もし random.randint(a, b) を使うと、整数値しか生成されないため、動きがカクカクして見える可能性があります。

この関数を使うには random モジュールが必要なので、ファイルの先頭に import random があるか確認してください。もしなければ追加してください。

import pyxel
import random # ここを追加(または確認)

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

敵の撃破時にこの関数を呼び出すように、update 関数内の敵の更新処理を変更しましょう。

    # 敵の更新
    surviving_enemies = []
    for e in enemies:
        # ... (中略) ...

        if hit:
            # ここから追加
            spawn_particles(e['x'], e['y'], 5, e['color'])
            score += e['score']
            # ここまで追加
            continue
        
        surviving_enemies.append(e)
    enemies = surviving_enemies

敵が弾に当たって hitTrue になったとき、spawn_particles を呼び出しています。
敵の座標 (e['x'], e['y']) と色 (e['color']) を渡し、5つのパーティクルを生成するように指定しました。

4. パーティクルを動かし、命を吹き込もう(update関数)

パーティクルが生成されるようになったので、次はそれらを動かし、寿命が尽きたら消滅させる処理を update 関数に追加します。

まず、update 関数の中で particles リストを操作するために、global キーワードで宣言しておきましょう。

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

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

そして、update 関数の一番最後に、パーティクルの更新処理を追加します。
これは、弾や敵の更新処理とよく似た方法で記述します。

    # パーティクル更新 # ここから追加
    surviving_particles = []
    for p in particles:
        p['x'] += p['vx']
        p['y'] += p['vy']
        p['life'] -= 1
        if p['life'] > 0:
            surviving_particles.append(p)
    particles = surviving_particles # ここまで追加

この処理を順に見ていきましょう。

  1. surviving_particles = []: まず、生き残るパーティクルを格納するための新しい空のリストを作成します。
  2. for p in particles:: 現在の particles リストに入っているすべてのパーティクルに対してループを回します。
  3. p['x'] += p['vx']p['y'] += p['vy']: 各パーティクルの x, y 座標に、それぞれの速度 vx, vy を加えて移動させます。
  4. p['life'] -= 1: パーティクルの残り寿命を1減らします。
  5. if p['life'] > 0:: もしパーティクルの寿命がまだ残っていれば、そのパーティクルは生き残るパーティクルとして surviving_particles リストに追加されます。
  6. particles = surviving_particles: ループが全て終わったら、元の particles リストを、生き残ったパーティクルだけのリストで上書きします。これにより、寿命が尽きたパーティクルは自動的にリストから消滅し、画面から消えます。

豆知識

パーティクルが一つ一つ持つ「寿命」という考え方は、ゲーム開発でよく使われるテクニックです。life 変数を使って時間経過で自動的に消滅させることで、画面がごちゃごちゃするのを防ぎ、不要な処理の負荷を減らすことができます。

5. 画面に描画して、閃光の演出を完成させよう(draw関数)

最後に、動いているパーティクルを実際に画面に描画する処理を draw 関数に追加します。
パーティクルは一点の光として表現するので、pyxel.pset 関数を使います。

draw 関数の一番最後、UIの描画より前の部分に追加しましょう。

    # 敵
    for e in enemies:
        # ... (中略) ...
            
    # パーティクル # ここから追加
    for p in particles:
        pyxel.pset(p['x'], p['y'], p['color'])
    # ここまで追加
            
    # UI
    pyxel.text(5, 5, f"SCORE:{score}", COL_WHITE)

for p in particles: でパーティクルリストを一つずつ取り出し、pyxel.pset(p['x'], p['y'], p['color']) を使って、それぞれのパーティクルの座標と色で1ピクセルだけ点を描画しています。

豆知識

pyxel.pset(x, y, col) は、指定した座標に1ピクセルだけ色を塗る関数です。今回のパーティクルのような小さな要素を描画するのにとても便利です。点の集合で様々な図形を表現する「点描」のようなイメージです。

6. 完成コードと実行

これで「爆砕パーティクル」の完成です!
敵を撃破した時に、派手に光の粒が飛び散るようになりました。

最終的なコードは以下のようになります。
このコードを 4-2_particles.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', ...}
particles = [] # Dictionary
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_particles(x, y, count, color):
    for _ in range(count):
        particles.append({
            'x': x, 
            'y': y,
            'vx': random.uniform(-2.0, 2.0),
            'vy': random.uniform(-2.0, 2.0),
            'life': random.randint(10, 20),
            'color': color
        })

# 敵を出現させる
def spawn_enemy():
    is_fast = random.random() < 0.3
    kind = 'fast' if is_fast else 'zako'

    enemies.append({
        'x': random.randint(0, SCREEN_WIDTH - ENEMY_W),
        'y': -10,
        'vx': 0,
        'vy': random.uniform(1.5, 2.5) if is_fast else random.uniform(0.5, 0.8),
        'hp': 1,
        'type': kind,
        'w': ENEMY_W, 'h': ENEMY_H,
        'score': 20 if is_fast else 10,
        'color': COL_RED if is_fast else COL_GRAY
    })

# 矩形同士の当たり判定
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, particles

    # --- 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:
        if e['type'] == 'bullet':
            e['x'] += e['vx']
            e['y'] += e['vy']
        else:
            e['y'] += e['vy']
            
        if e['y'] > SCREEN_HEIGHT:
            continue

        hit_w = ENEMY_W if e['type'] != 'bullet' else 4
        if check_collision(player_x, player_y, PLAYER_W, PLAYER_H, e['x'], e['y'], hit_w, hit_w):
            game_over = True
            
        hit = False
        if e['type'] != 'bullet':
            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:
            spawn_particles(e['x'], e['y'], 5, e['color'])
            score += e['score']
            
            continue
        
        surviving_enemies.append(e)
    enemies = surviving_enemies

    # パーティクル更新
    surviving_particles = []
    for p in particles:
        p['x'] += p['vx']
        p['y'] += p['vy']
        p['life'] -= 1
        if p['life'] > 0:
            surviving_particles.append(p)
    particles = surviving_particles



# 毎フレーム実行される描画処理 (結果を画面に表示する。計算はしない)
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:
        if e['type'] == 'bullet':
            pyxel.circ(e['x'], e['y'], 2, COL_YELLOW)
        else:
            col = e['color']
            pyxel.rect(e['x'], e['y'], e['w'], e['h'], col)
            if e['type'] == 'zako':
                pyxel.rect(e['x']+2, e['y']+2, 4, 4, COL_NAVY)
            else:
                pyxel.pset(e['x']+3, e['y']+3, COL_WHITE)
            
    # パーティクル
    for p in particles:
        pyxel.pset(p['x'], p['y'], p['color'])

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

pyxel.run(update, draw)

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

実際に動かしてみよう!

矢印キーで自機を操作し、スペースキーで弾を発射できます。敵を撃破すると、爆砕パーティクルが表示されます。

これで、ゲームがより派手で爽快になりましたね!
Pythonの「リスト」や「辞書」という基本的なデータ構造が、データの箱リスト配列 名前付き辞書の魔法 関数で呪文作成 画面で目に見える形で動く「演出」として活用できることが実感できたのではないでしょうか。

次回は、起死回生ボム炸裂で「起死回生:ボム(衝撃波)炸裂!」の実装に挑戦します。
お楽しみに!

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