この記事は「うずら」とAIが協力して作成しました。
なるべく正確さを心がけていますが、最新の公式ドキュメントなどもあわせて確認してみてね!
3-4. バスター発射:リスト操作(append)で弾を撃て
1. 3秒でわかるまとめ
スペースキーを押すと、自機から弾が発射され、画面上へ飛んでいくようになります。これは、弾をリストに追加(append)し、そのリスト内の弾を全て動かし、描画するという仕組みで実現します。
この記事は「うずら」とAIが協力して作成しました。
なるべく正確さを心がけていますが、最新の公式ドキュメントなどもあわせて確認してみてね!
スペースキーを押すと、自機から弾が発射され、画面上へ飛んでいくようになります。これは、弾をリストに追加(append)し、そのリスト内の弾を全て動かし、描画するという仕組みで実現します。
bulletsリストの登場前回は、
リスト魔法星空の実装で星空を描画するために、たくさんの星の情報をstarsリストにまとめました。ゲームで弾を管理するためにもリストを使います。
弾は、一発撃ったら終わりではありません。連続して何発も撃ちたいですよね。そのため、発射されるたびに新しい弾の情報をリストに追加していく必要があります。
まず、前回のコードに、弾を格納する新しいリストbulletsを追加しましょう。
# --- 変数の初期化 --- # プレイヤー player_x = SCREEN_WIDTH // 2 - PLAYER_W // 2 player_y = SCREEN_HEIGHT - 20 # ゲームオブジェクト bullets = [] # [x, y, vx, vy] ← ここを追加 stars = [] # [x, y, speed]
bulletsは空のリストとして初期化します。これから、このリストに弾の情報をどんどん追加していきます。
vx, vyとは?vxとvyはそれぞれ、X方向(横方向)とY方向(縦方向)の「速度」を表す変数名としてよく使われます。Bullet(弾)がどの方向にどれくらいの速さで動くかを指定するために、リストに含めます。
appendメソッドの魔法いよいよ、プレイヤーが弾を発射する処理を実装します。弾を発射するのは、プレイヤーがスペースキーを押した時です。
update関数の中に、以下のコードを追加してください。
# 毎フレーム実行される更新処理 (計算などはここで行う。描画は禁止) def update(): global player_x, player_y global bullets # bulletsリストも更新するのでglobal宣言 # --- 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])
ここで重要なのがbullets.append([player_x + 3, player_y - 4, 0, -5])という行です。
pyxel.btnp(pyxel.KEY_SPACE): これは「スペースキーが押された瞬間」だけTrueになる関数です。pyxel.btn()だと押しっぱなしで連射されてしまうため、btnpを使います。bullets.append(...): これはPythonのリストが持つappendメソッドです。リストの末尾に新しい要素を追加する働きがあります。[player_x + 3, player_y - 4, 0, -5]: これが追加される「弾の情報」です。
player_x + 3: 自機のX座標より少し右(自機の中心あたり)から発射されるように調整しています。player_y - 4: 自機のY座標より少し上(自機の先端あたり)から発射されるように調整しています。0: 弾の横方向の速度(vx)です。今回はまっすぐ上に飛ぶので0です。-5: 弾の縦方向の速度(vy)です。PyxelのY座標は上が小さいので、上へ移動するにはマイナスの値を指定します。-5なので、毎フレーム5ピクセル上に移動します。
データの箱リスト配列で学んだリストのappendメソッドを、ゲームの世界で実際に使っている様子がわかりますね。
弾は発射されたら終わりではありません。画面内を動き回り、画面外に出たら消える必要があります。
update関数の中に、さらに以下のコードを追加してください。
# --- 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
一つずつ見ていきましょう。
surviving_bullets = []:
これは、「画面内に残るべき弾」を一時的に保存するための新しい空のリストです。
なぜこんなものを用意するのでしょうか? bulletsリストから直接弾を削除しようとすると、forループの途中でリストの長さが変わってしまい、予期せぬエラーが起きることがあります。そのため、一度生き残る弾だけを集めて、最後にbulletsリストをその新しいリストに置き換える、という方法が安全です。
for b in bullets::
bulletsリストの中にある弾(b)を、一つずつ取り出して処理します。弾bは[x, y, vx, vy]という形式のリストです。
b[0] += b[2] と b[1] += b[3]:
現在の弾のX座標(b[0])に、X方向の速度(b[2]、つまりvx)を加算します。
現在の弾のY座標(b[1])に、Y方向の速度(b[3]、つまりvy)を加算します。
これで弾が動きます。
if b[1] >= -10 and b[0] >= -10 and b[0] <= SCREEN_WIDTH + 10::
これは、弾が画面内にいるかどうかをチェックする条件式です。
b[1] >= -10: 弾のY座標が画面の上端(0)よりも少し上(-10)に入っているうちは残します。少し余裕を持たせることで、弾が完全に画面外に出るまで描画され続けるようにします。b[0] >= -10 と b[0] <= SCREEN_WIDTH + 10: X座標についても同様に、画面の左右に少し余裕を持たせます。surviving_bulletsリストに追加されます。bullets = surviving_bullets:
forループが終わったら、もともとのbulletsリストを、生き残った弾だけが入っているsurviving_bulletsリストに置き換えます。これで、画面外に出た弾は自動的にリストから消え、メモリの無駄遣いを防げます。
forループ中にリストから要素を削除したい場合、今回のように新しいリストを作成して「生き残る要素だけを移す」方法は非常に有効で安全です。元のリストから直接removeやpopを使うと、インデックスのずれなどでエラーになりやすいため注意が必要です。
最後に、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) # プレイヤー 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)
bulletsリストの全ての弾(b)に対して、pyxel.rect()を使って小さな四角形を描画しています。
b[0]: 弾のX座標b[1]: 弾のY座標2, 4: 弾の幅を2ピクセル、高さを4ピクセルに設定しています。COL_L_GREEN: 明るい緑色で弾を描画します。これで、スペースキーを押すたびに弾が発射され、画面を上に飛んでいくのが見えるはずです。
これまでの変更を全て適用した完成コードです。ぜひ動かして、弾を撃ってみてください!
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] 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) ]) # ========================================================= # メインループ (Update & Draw) # ========================================================= # 毎フレーム実行される更新処理 (計算などはここで行う。描画は禁止) def update(): global player_x, player_y global bullets # --- 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 # 毎フレーム実行される描画処理 (結果を画面に表示する。計算はしない) 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) # プレイヤー 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) pyxel.run(update, draw)
上記のコードを実際にブラウザで動かしてみてください。
矢印キーで自機を操作し、スペースキーで弾を発射できます。
今回の記事では、Pythonのリスト操作appendを使って、ゲームで連続して弾を発射する仕組みを学びました。複数のオブジェクト(弾)を効率的に管理し、それぞれを動かし、画面外に出たら自動的に削除する。これが、ゲームプログラミングの基本的な考え方の一つです。
次回は、いよいよ敵キャラクターが登場します。複数の敵をどうやって管理するのか、楽しみにしていてください!
辞書魔法敵の襲来
最後まで読んでくれてありがとう!🌱
ノートみたいに、いつでも見返してね。