この記事は「うずら」とAIが協力して作成しました。
なるべく正確さを心がけていますが、最新の公式ドキュメントなどもあわせて確認してみてね!
4-4. 救援物資:アイテムの出現と取得判定
1. 3秒でわかるまとめ
敵を倒すと確率でアイテムが出現し、プレイヤーがそのアイテムに触れると取得判定が行われ、アイテムの効果(今回はボムの増加)が適用されます。
この記事は「うずら」とAIが協力して作成しました。
なるべく正確さを心がけていますが、最新の公式ドキュメントなどもあわせて確認してみてね!
敵を倒すと確率でアイテムが出現し、プレイヤーがそのアイテムに触れると取得判定が行われ、アイテムの効果(今回はボムの増加)が適用されます。
まずは、ゲーム中に登場するアイテムを管理するための準備をしましょう。アイテムは敵が倒されたときに発生するので、その仕組みを加えていきます。
アイテムの情報をどこかに保存する必要がありますね。これまでに「弾」や「敵」をリストで管理してきたように、今回も新しいリスト items を用意します。そして、敵が倒されたときにアイテムをリストに追加する関数 drop_item を作ります。
4-3_bomb.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 items = [] # [x, y, kind] # ★ここを追加 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) ])
items リストは、各アイテムの [x座標, y座標, 種類] を保存する予定です。
次に、アイテムをリストに追加する関数 drop_item を定義します。これは、spawn_particles などと同じように、関数定義のセクションに追加してください。
# アイテムを出現させる # ★ここを追加 def drop_item(x, y, kind): items.append([x, y, kind]) # 矩形同士の当たり判定 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)
drop_item 関数は、アイテムが出現する x, y 座標と、その kind(種類)を受け取り、items リストに新しいアイテムの情報を追加します。今回は'bomb'という種類のアイテムを想定しています。
敵が撃破されたときに、確率でアイテムをドロップするように update 関数内の処理を修正します。
update 関数の 敵の更新 ループ内、敵が弾に当たって hit した場合のブロックを探してください。
if hit: spawn_particles(e['x'], e['y'], 5, e['color']) score += e['score'] # ★ここから追加 if random.random() < 0.08: drop_item(e['x'], e['y'], 'bomb') # ★ここまで追加 continue
random.random() < 0.08 という条件を追加しました。
random.random() は、0.0以上1.0未満の浮動小数点数をランダムに生成する関数です。
この例では、生成された乱数が0.08未満であればTrueになるので、約8%の確率でアイテムがドロップすることになります。
random.random() のような乱数生成は、ゲームにおけるアイテムドロップや敵の行動パターンなど、予測不能な要素を生み出すためによく使われます。これにより、ゲームにリプレイ性やサプライズ感を与えることができます。
アイテムが出現するだけでは、画面に表示されず、動きません。update 関数でアイテムを移動させ、draw 関数で描画する処理を加えましょう。
アイテムは画面上からゆっくりと下に落ちてくるようにしましょう。これも update 関数内の オブジェクト更新 セクションに追加します。
# パーティクル更新 の上に、以下を追加 # アイテム更新 # ★ここを追加 surviving_items = [] for item in items: item[1] += 1 # アイテムのY座標を増やして下に移動させる ix, iy, kind = item # 分かりやすいように変数に分解 # ※ここに後でアイテム取得判定のコードが入ります if iy <= SCREEN_HEIGHT: # 画面外に出ていなければ残す surviving_items.append(item) items = surviving_items
このコードでは、items リスト内の各アイテムの y 座標を 1 ずつ増やしています。これにより、アイテムが下方向へ移動します。
surviving_items リストを使って、画面外に出たアイテムは自動的に削除されるようにしています。これまでの弾や敵の管理と同じ仕組みですね。
次に、draw 関数でアイテムを画面に表示します。今回は「B」(BombのB)という文字で表現してみましょう。
draw 関数内の UI の描画処理の上に、以下を追加してください。
# 敵 の描画処理の後、UI の描画処理の前に以下を追加 # アイテム # ★ここを追加 for item in items: # 仮で文字「B」を描画します。後でちゃんとした画像に変更することも可能です。 pyxel.text(item[0], item[1], "B", COL_CYAN)
これで、敵を倒したときに約8%の確率で、"B"と書かれたシアン色のアイテムが落ちてくるようになりました。実際に動かして確認してみましょう。
矢印キーで宇宙船を操作し、スペースキーで弾を発射します。敵を倒すと確率でアイテムが出現します。
アイテムが画面に表示され、移動するようになったら、次はプレイヤーがアイテムを取得できるようにしましょう。
プレイヤーがアイテムに触れたら、アイテムを取得したと判定し、効果を適用します。update 関数の アイテム更新 ループの ※ここに後でアイテム取得判定のコードが入ります と書いた部分に、当たり判定のロジックを追加します。
# アイテム更新 surviving_items = [] for item in items: item[1] += 1 ix, iy, kind = item # ★ここから追加 # プレイヤーとアイテムの当たり判定 # アイテムのサイズを仮に4x4として判定します if check_collision(player_x, player_y, PLAYER_W, PLAYER_H, ix, iy, 4, 4): # 取得したアイテムの種類に応じて効果を適用 if kind == 'bomb': num_bombs += 1 # ボムの数を1増やす continue # アイテムを取得したので、surviving_itemsには追加せず、リストから消す # ★ここまで追加 if iy <= SCREEN_HEIGHT: surviving_items.append(item) items = surviving_items
check_collision 関数 (
実践:命中!敵を撃破 で登場しましたね) を使って、プレイヤーとアイテムの矩形同士の当たり判定を行っています。アイテムのサイズは仮に 4x4 として判定しています。
surviving_items リストを使って、条件を満たす(取得された、画面外に出たなど)オブジェクトだけを新しいリストに追加し、最後に元のリストを新しいリストで置き換える方法は、ループ中にリストの内容を変更する際によく使われる安全な方法です。これにより、意図しないエラーを防ぎます。
もし当たり判定が True になったら、アイテムの種類 kind に応じた効果を適用します。今回は'bomb'アイテムなので、num_bombs (ボムの数) を1つ増やします。そして continue を使うことで、そのアイテムは surviving_items リストに追加されずにスキップされ、結果として items リストから削除されます。
これで、プレイヤーがアイテムに触れると、画面からアイテムが消え、ボムの数が増えるようになりました!
お疲れ様でした!これまでの変更をすべて加えた完成コードを見てみましょう。
これで、敵を倒したときに稀に救援物資が投下され、プレイヤーが取得することでボムの数が回復する、というゲーム性が加わりましたね。
自分でコードを書き換え、動作を確認してみましょう。
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 # 色の定義 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 items = [] # [x, y, kind] 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 # パワーアップ & ボム num_bombs = 1 is_bomb_active = False bomb_radius = 0 # ========================================================= # 関数定義 (エフェクト生成など) # ========================================================= # 爆発エフェクトなどを生成する 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 trigger_bomb(): global num_bombs, is_bomb_active, bomb_radius if num_bombs > 0 and not is_bomb_active: num_bombs -= 1 is_bomb_active = True bomb_radius = 5 # 敵を出現させる 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 drop_item(x, y, kind): items.append([x, y, kind]) # 矩形同士の当たり判定 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 num_bombs, is_bomb_active, bomb_radius global bullets, enemies, particles, items # --- 1. ゲームオーバー / クリアチェック --- if game_over: return # --- 2. システム更新 (ボム) if is_bomb_active: bomb_radius += 4 if bomb_radius > 200: is_bomb_active = False bomb_radius = 0 # --- 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]) # ボム発動 if pyxel.btnp(pyxel.KEY_Z) or pyxel.btnp(pyxel.KEY_B): trigger_bomb() # --- 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'] destroyed = False if is_bomb_active: # ボムの範囲 (中心座標から左上座標を計算) bomb_x = player_x + 4 - bomb_radius bomb_y = player_y + 4 - bomb_radius bomb_size = bomb_radius * 2 if check_collision(bomb_x, bomb_y, bomb_size, bomb_size, e['x'], e['y'], ENEMY_W, ENEMY_H): destroyed = True if destroyed: spawn_particles(e['x'], e['y'], 5, COL_WHITE) if e['type'] != 'bullet': score += 10 continue # del せずにループ続行 (appendされないので消える) 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'] if random.random() < 0.08: drop_item(e['x'], e['y'], 'bomb') continue surviving_enemies.append(e) enemies = surviving_enemies # アイテム更新 surviving_items = [] for item in items: item[1] += 1 ix, iy, kind = item if check_collision(player_x, player_y, PLAYER_W, PLAYER_H, ix, iy, 4, 4): num_bombs += 1 continue # 取得したのでリストに追加しない (消える) if iy <= SCREEN_HEIGHT: surviving_items.append(item) items = surviving_items # パーティクル更新 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 is_bomb_active: pyxel.circb(player_x + 4, player_y + 4, bomb_radius, COL_WHITE) pyxel.circb(player_x + 4, player_y + 4, bomb_radius - 2, COL_LBLUE) # ゲームオーバー 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 item in items: pyxel.text(item[0], item[1], "B", COL_CYAN) # UI pyxel.text(5, 5, f"SCORE:{score}", COL_WHITE) pyxel.text(120, 5, f"BOMB:{num_bombs}", COL_CYAN) pyxel.run(update, draw)
上記のコードを実際にブラウザで動かしてみてください。
矢印キーで宇宙船を操作し、スペースキーで弾を発射します。敵を倒すと確率でアイテムが出現し、取得するとボムが増えます。
これで、ゲームに新たな戦略要素が加わりました。プレイヤーはただ敵を倒すだけでなく、アイテムを狙って取得することでゲームを有利に進められるようになりますね。
次回は、さらにプレイヤーの攻撃力をアップさせる「3WAY弾」の実装に挑戦します。お楽しみに!
実践:火力増強3WAY弾
最後まで読んでくれてありがとう!🌱
ノートみたいに、いつでも見返してね。