【Python】シューティングゲームを作ってみた(初心者プログラミング)
おはようございます。タカヒデです。
本日はPythonで「シューティングゲーム」を作ってみました。
STEP形式で解説しているので、「まずは何かを作ってみたい」という初心者の方の参考になれば幸いです。
- プログラミング初心者
- 何から始めればよいか分からない
- まずは簡単なゲームを作って興味を持ってみたい
ぜひ実際にコードを打ちながら作成してみてください。
「シューティングゲーム」完成イメージ
まずは、「シューティングゲーム」完成後の最終的なコードと完成イメージです。
import pygame
import random
# ===== 基本設定 =====
WIDTH, HEIGHT = 800, 600
FPS = 60
PLAYER_SPEED = 6
BULLET_SPEED = -10
ENEMY_SPEED_MIN, ENEMY_SPEED_MAX = 2, 4
SPAWN_EVERY_FRAMES = 30
SHOOT_COOLDOWN_FRAMES = 8
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Simple Shooting - Minimal")
clock = pygame.time.Clock()
font = pygame.font.SysFont("meiryo", 28)
# ===== プレイヤー =====
class Player:
def __init__(self):
self.w, self.h = 36, 28
self.x, self.y = WIDTH // 2, HEIGHT - 70
self.cooldown = 0
self.alive = True
def rect(self):
return pygame.Rect(int(self.x - self.w//2), int(self.y - self.h//2), self.w, self.h)
def update(self, keys):
if keys[pygame.K_LEFT] or keys[pygame.K_a]:
self.x -= PLAYER_SPEED
if keys[pygame.K_RIGHT] or keys[pygame.K_d]:
self.x += PLAYER_SPEED
self.x = max(self.w//2, min(WIDTH - self.w//2, self.x))
if self.cooldown > 0:
self.cooldown -= 1
def can_shoot(self):
return self.cooldown == 0 and self.alive
def shoot(self, bullets):
bullets.append(Bullet(self.x, self.rect().top))
self.cooldown = SHOOT_COOLDOWN_FRAMES
def draw(self, surface):
top = (self.x, self.y - self.h//2)
left = (self.x - self.w//2, self.y + self.h//2)
right = (self.x + self.w//2, self.y + self.h//2)
pygame.draw.polygon(surface, (80, 180, 255), [left, right, top])
pygame.draw.polygon(surface, (200, 230, 255), [left, right, top], 2)
# ===== 弾 =====
class Bullet:
def __init__(self, x, y):
self.x, self.y = x, y
self.r = 4
self.alive = True
def rect(self):
return pygame.Rect(int(self.x - self.r), int(self.y - self.r), self.r*2, self.r*2)
def update(self):
self.y += BULLET_SPEED
if self.y < -10:
self.alive = False
def draw(self, surface):
pygame.draw.circle(surface, (255, 255, 120), (int(self.x), int(self.y)), self.r)
# ===== 敵 =====
class Enemy:
def __init__(self):
self.w, self.h = 28, 20
self.x = random.randint(40, WIDTH - 40)
self.y = -30
self.speed = random.uniform(ENEMY_SPEED_MIN, ENEMY_SPEED_MAX)
self.alive = True
base = random.randint(130, 220)
self.color = (base, 60, 80)
def rect(self):
return pygame.Rect(int(self.x - self.w//2), int(self.y - self.h//2), self.w, self.h)
def update(self):
self.y += self.speed
if self.y > HEIGHT + 40:
self.alive = False
def draw(self, surface):
pygame.draw.rect(surface, self.color, self.rect(), border_radius=4)
pygame.draw.rect(surface, (240, 160, 170), self.rect(), 2, border_radius=4)
# ===== メインゲーム =====
def main():
player = Player()
bullets, enemies = [], []
score, frame = 0, 0
game_over = False
running = True
while running:
clock.tick(FPS)
for e in pygame.event.get():
if e.type == pygame.QUIT:
running = False
if e.type == pygame.KEYDOWN and game_over and e.key == pygame.K_RETURN:
# リスタート(最小限の初期化)
player = Player()
bullets.clear()
enemies.clear()
score, frame, game_over = 0, 0, False
keys = pygame.key.get_pressed()
if not game_over:
player.update(keys)
if keys[pygame.K_SPACE] and player.can_shoot():
player.shoot(bullets)
for b in bullets:
b.update()
bullets = [b for b in bullets if b.alive]
frame += 1
if frame % SPAWN_EVERY_FRAMES == 0:
enemies.append(Enemy())
for en in enemies:
en.update()
enemies = [en for en in enemies if en.alive]
# 衝突判定(弾→敵)
for b in bullets:
for en in enemies:
if b.alive and en.alive and b.rect().colliderect(en.rect()):
b.alive = False
en.alive = False
score += 10
bullets = [b for b in bullets if b.alive]
enemies = [en for en in enemies if en.alive]
# 衝突判定(敵→自機)
for en in enemies:
if en.rect().colliderect(player.rect()):
player.alive = False
game_over = True
break
# 描画(背景は単色)
screen.fill((15, 18, 25))
for b in bullets:
b.draw(screen)
for en in enemies:
en.draw(screen)
if player.alive:
player.draw(screen)
# スコアと案内
screen.blit(font.render(f"Score: {score}", True, (230, 230, 240)), (12, 10))
if game_over:
msg1 = pygame.font.SysFont(None, 64).render("GAME OVER", True, (255, 220, 220))
msg2 = font.render("Press Enter to Restart", True, (210, 210, 220))
screen.blit(msg1, msg1.get_rect(center=(WIDTH//2, HEIGHT//2 - 16)))
screen.blit(msg2, msg2.get_rect(center=(WIDTH//2, HEIGHT//2 + 28)))
pygame.display.flip()
pygame.quit()
if __name__ == "__main__":
main()
こんなゲームが作れます。

STEP1:Pygameの準備とウィンドウ表示
まずはゲームの土台となる「ウィンドウ」と「メインループ」を用意します。ここができれば、以降はこの枠の中にゲーム要素を足していくだけです。
#Pygameを使うための準備
import pygame
#画面サイズとフレームレート
WIDTH, HEIGHT = 800, 600 # 幅と高さ
FPS = 60 # 1秒あたりの更新回数
#Pygameの初期化とウィンドウ作成
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Simple Shooting - Minimal")
clock = pygame.time.Clock()
#テキスト描画用のフォント
font = pygame.font.SysFont("meiryo", 28)
#メインループ
def main():
running = True
while running:
clock.tick(FPS) # 更新速度を一定に保つ
# イベント処理:ウィンドウの×ボタンなど
for e in pygame.event.get():
if e.type == pygame.QUIT:
running = False
# 背景を塗りつぶす
screen.fill((15, 18, 25))
# 画面中央付近に案内テキストを描く
msg = font.render("ウィンドウが表示されれば準備完了です", True, (220, 220, 230))
rect = msg.get_rect(center=(WIDTH // 2, HEIGHT // 2))
screen.blit(msg, rect)
# 画面を更新
pygame.display.flip()
pygame.quit()
#このファイルを実行したときだけmain()を起動
if __name__ == "__main__":
main()
手順①:Pygameの初期化とウィンドウ作成
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Simple Shooting - Minimal")
clock = pygame.time.Clock()
「pygame.init()」はPygame全体の準備を一括で行う関数です。
「display.set_mode」はウィンドウを作る関数で、幅と高さを指定します。
「set_caption」はタイトルバーに表示される文字列を設定します。
「Clock」は「1秒に何回画面を更新するか」を調整します。これにより、動作が速すぎたり遅すぎたりするのを防ぎます。
手順②:定数で基本設定をまとめる
WIDTH, HEIGHT = 800, 600
FPS = 60
「定数」とは、途中で変えない前提の値をまとめておく書き方です。
画面サイズやフレームレートをここに置くことで、後で調整が必要になったときに見つけやすくしています。
手順③:メインループで「入力→更新→描画」を回す
while running:
clock.tick(FPS)
for e in pygame.event.get():
if e.type == pygame.QUIT:
running = False
screen.fill((15, 18, 25))
...
pygame.display.flip()
ゲームは毎フレーム「入力を読む→状態を更新する→画面に描く」を繰り返して動きます。
ここではまず入力として「ウィンドウを閉じる」操作だけを処理し、背景を塗って、最後に「flip」で画面を切り替えています。
「clock.tick(FPS)」でフレームレートを固定し、PC性能による速度差を抑えます。
手順④:テスト用の文字を描く
font = pygame.font.SysFont(None, 28)
msg = font.render("ウィンドウが表示されれば準備完了です", True, (220, 220, 230))
rect = msg.get_rect(center=(WIDTH // 2, HEIGHT // 2))
screen.blit(msg, rect)
「font」は文字の見た目を作る仕組みです。「render」で文字列から画像を作り、「blit」で画面に貼り付けます。
「get_rect(center=…)」を使うと、画像の位置合わせが簡単になります。ここでは中央に表示しています。
STEP1が完了した時点では以下のアプリができています。

STEP2:プレイヤー機体を表示して左右に動かす
次は、画面下に自分の機体(プレイヤー)を表示し、左右キーで動かせるようにします。
まだ弾は撃ちませんが、「動くものがある画面」になることで一気にゲームらしくなります。
import pygame # 2Dゲームや描画を簡単に行えるライブラリ
import random # ★STEP2追加:ランダムな数値を作る標準モジュール
#画面サイズとフレームレート
WIDTH, HEIGHT = 800, 600 # 幅と高さ(ピクセル)
FPS = 60 # 1秒あたりの更新回数
# ★STEP2追加:ゲームの基本パラメータ
PLAYER_SPEED = 6
BULLET_SPEED = -10
ENEMY_SPEED_MIN, ENEMY_SPEED_MAX = 2, 4
SPAWN_EVERY_FRAMES = 30
SHOOT_COOLDOWN_FRAMES = 8
#Pygameの初期化とウィンドウ作成
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Simple Shooting - Minimal")
clock = pygame.time.Clock()
#テキスト描画用のフォント(案内表示に使用)
font = pygame.font.SysFont("meiryo", 28)
# ★STEP2追加:プレイヤー(自機)を表すクラス
class Player:
def __init__(self):
self.w, self.h = 36, 28
self.x, self.y = WIDTH // 2, HEIGHT - 70
self.cooldown = 0
self.alive = True
def rect(self):
return pygame.Rect(int(self.x - self.w//2), int(self.y - self.h//2), self.w, self.h)
def update(self, keys):
if keys[pygame.K_LEFT] or keys[pygame.K_a]:
self.x -= PLAYER_SPEED
if keys[pygame.K_RIGHT] or keys[pygame.K_d]:
self.x += PLAYER_SPEED
# 画面外に出ないように左右の位置を制限
self.x = max(self.w//2, min(WIDTH - self.w//2, self.x))
if self.cooldown > 0:
self.cooldown -= 1
def can_shoot(self):
return self.cooldown == 0 and self.alive
def shoot(self, bullets):
# Bulletクラスは後のSTEPで定義します
bullets.append(Bullet(self.x, self.rect().top))
self.cooldown = SHOOT_COOLDOWN_FRAMES
def draw(self, surface):
top = (self.x, self.y - self.h//2)
left = (self.x - self.w//2, self.y + self.h//2)
right = (self.x + self.w//2, self.y + self.h//2)
# 本体
pygame.draw.polygon(surface, (80, 180, 255), [left, right, top])
# 外枠
pygame.draw.polygon(surface, (200, 230, 255), [left, right, top], 2)
# ★STEP2変更:プレイヤーを動かすメインループ
def main():
player = Player() # ★STEP2追加:プレイヤーのインスタンスを作成
running = True
while running:
clock.tick(FPS) # 更新速度を一定に保つ
# イベント処理:ウィンドウの×ボタンなど
for e in pygame.event.get():
if e.type == pygame.QUIT:
running = False
# ★STEP2追加:キーボード入力の状態をまとめて取得
keys = pygame.key.get_pressed()
# ★STEP2追加:プレイヤーの位置を更新
player.update(keys)
# 背景を塗りつぶす
screen.fill((15, 18, 25))
# ★STEP2追加:プレイヤーを描画
player.draw(screen)
# ★STEP2変更:画面下部に案内テキストを描く
msg = font.render("←→ または A/D で移動できます", True, (220, 220, 230))
screen.blit(msg, (20, HEIGHT - 40))
# 画面を更新
pygame.display.flip()
pygame.quit()
#このファイルを実行したときだけmain()を起動
if __name__ == "__main__":
main()
手順①:ゲーム用のパラメータをまとめて定義する
PLAYER_SPEED = 6
BULLET_SPEED = -10
ENEMY_SPEED_MIN, ENEMY_SPEED_MAX = 2, 4
SPAWN_EVERY_FRAMES = 30
SHOOT_COOLDOWN_FRAMES = 8
ここではゲーム全体で使う「数値の設定」をひとまとめにしています。
このような値を「パラメータ」や「定数」と呼びます。途中で何度も出てくる数字を上のほうに集めておくと、難易度調整やスピード調整をしたいときに探しやすくなります。
このSTEPでは実際に使っているのは「PLAYER_SPEED」だけですが、後のSTEPで弾や敵を追加するときに、ここで定義した値をそのまま使います。
手順②:Playerクラスで自機の情報をまとめる
class Player:
def __init__(self):
self.w, self.h = 36, 28
self.x, self.y = WIDTH // 2, HEIGHT - 70
self.cooldown = 0
self.alive = True
「クラス」は、そのゲーム内の「もの」の情報と動きをまとめる仕組みです。ここではプレイヤーを表す「Player」というクラスを作っています。
「init」は「コンストラクタ」と呼ばれ、Playerが作られた瞬間に一度だけ呼ばれる初期設定用の関数です。ここで大きさや初期位置などを決めています。
プレイヤーの位置は「x」と「y」で表します。xが左右方向、yが上下方向です。画面の中央下あたりに自機が配置されるようにしています。
手順③:左右キーでプレイヤーを動かす
def update(self, keys):
if keys[pygame.K_LEFT] or keys[pygame.K_a]:
self.x -= PLAYER_SPEED
if keys[pygame.K_RIGHT] or keys[pygame.K_d]:
self.x += PLAYER_SPEED
self.x = max(self.w//2, min(WIDTH - self.w//2, self.x))
「update」は毎フレーム呼び出される「状態を更新する」ための関数です。ここではキー入力に応じて左右に移動しています。
「keys」は「pygame.key.get_pressed()」で取得した、現在押されているキーです。
左キーまたはAキーが押されていれば左へ、右キーまたはDキーが押されていれば右へ少しずつ移動します。
最後の1行では「max」と「min」を使って、プレイヤーが画面の外へ出ないように制限しています。
- 「min(WIDTH – self.w//2, self.x)」で右端の限界
- 「max(self.w//2, …)」で左端の限界
手順④:自機の当たり判定の作成
def rect(self):
return pygame.Rect(int(self.x - self.w//2), int(self.y - self.h//2), self.w, self.h)
「rect」は、プレイヤーを囲む四角形(当たり判定用の領域)を作るための関数です。
Pygameの「Rect」は「左上の座標+幅と高さ」で四角形を表現します。
この四角形は後のSTEPで「弾や敵とぶつかったかどうか」を判断するときに使います。
手順⑤:自機を画面に描画する
def draw(self, surface):
top = (self.x, self.y - self.h//2)
left = (self.x - self.w//2, self.y + self.h//2)
right = (self.x + self.w//2, self.y + self.h//2)
pygame.draw.polygon(surface, (80, 180, 255), [left, right, top])
pygame.draw.polygon(surface, (200, 230, 255), [left, right, top], 2)
「draw」は自機を画面に描くための関数です。ここでは三角形を組み合わせて、シンプルな機体を表現しています。
「polygon」は多角形を描く関数で、3点の座標を渡して三角形を描いています。その上に少し明るい色で枠線を描くことで、見やすくしています。
手順⑥:メインループでプレイヤーを動かして描く
def main():
player = Player()
running = True
while running:
clock.tick(FPS)
for e in pygame.event.get():
if e.type == pygame.QUIT:
running = False
keys = pygame.key.get_pressed()
player.update(keys)
screen.fill((15, 18, 25))
player.draw(screen)
msg = font.render("←→ または A/D で移動できます", True, (220, 220, 230))
screen.blit(msg, (20, HEIGHT - 40))
pygame.display.flip()
ここではSTEP1で作ったメインループに、プレイヤーの生成・更新・描画を追加しています。
流れとしては、
- イベント処理
- キー入力の取得
- プレイヤーの更新
- 背景の描画
- プレイヤーの描画
- 画面更新
という順番になっています。
このSTEP2までで、「自機が画面下に表示され、左右キーで動かせる」状態になっています。

STEP3:弾を撃てるようにする
次は、スペースキーを押すと弾が発射されるようにします。画面下の自機から上方向に弾が飛んでいくことで、一気に「シューティングゲーム」らしくなります。
import pygame # 2Dゲームや描画を簡単に行えるライブラリ
import random # ★STEP2追加:ランダムな数値を作る標準モジュール
#画面サイズとフレームレート
WIDTH, HEIGHT = 800, 600 # 幅と高さ(ピクセル)
FPS = 60 # 1秒あたりの更新回数
#ゲームの基本パラメータ
PLAYER_SPEED = 6
BULLET_SPEED = -10
ENEMY_SPEED_MIN, ENEMY_SPEED_MAX = 2, 4
SPAWN_EVERY_FRAMES = 30
SHOOT_COOLDOWN_FRAMES = 8
#Pygameの初期化とウィンドウ作成
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Simple Shooting - Minimal")
clock = pygame.time.Clock()
#テキスト描画用のフォント(案内表示に使用)
font = pygame.font.SysFont("meiryo", 28)
#プレイヤー(自機)を表すクラス
class Player:
def __init__(self):
self.w, self.h = 36, 28
self.x, self.y = WIDTH // 2, HEIGHT - 70
self.cooldown = 0
self.alive = True
def rect(self):
return pygame.Rect(int(self.x - self.w//2), int(self.y - self.h//2), self.w, self.h)
def update(self, keys):
if keys[pygame.K_LEFT] or keys[pygame.K_a]:
self.x -= PLAYER_SPEED
if keys[pygame.K_RIGHT] or keys[pygame.K_d]:
self.x += PLAYER_SPEED
# 画面外に出ないように左右の位置を制限
self.x = max(self.w//2, min(WIDTH - self.w//2, self.x))
if self.cooldown > 0:
self.cooldown -= 1
def can_shoot(self):
return self.cooldown == 0 and self.alive
def shoot(self, bullets):
bullets.append(Bullet(self.x, self.rect().top))
self.cooldown = SHOOT_COOLDOWN_FRAMES
def draw(self, surface):
top = (self.x, self.y - self.h//2)
left = (self.x - self.w//2, self.y + self.h//2)
right = (self.x + self.w//2, self.y + self.h//2)
# 本体
pygame.draw.polygon(surface, (80, 180, 255), [left, right, top])
# 外枠
pygame.draw.polygon(surface, (200, 230, 255), [left, right, top], 2)
# ★STEP3追加:弾(プレイヤーのショット)を表すクラス
class Bullet:
def __init__(self, x, y):
self.x, self.y = x, y
self.r = 4
self.alive = True
def rect(self):
return pygame.Rect(int(self.x - self.r), int(self.y - self.r), self.r*2, self.r*2)
def update(self):
self.y += BULLET_SPEED
if self.y < -10:
self.alive = False
def draw(self, surface):
pygame.draw.circle(surface, (255, 255, 120), (int(self.x), int(self.y)), self.r)
# ★STEP3変更:プレイヤーと弾を動かすメインループ
def main():
player = Player()
bullets = [] # ★STEP3追加:発射された弾を入れておくリスト
running = True
while running:
clock.tick(FPS) # 更新速度を一定に保つ
# イベント処理:ウィンドウの×ボタンなど
for e in pygame.event.get():
if e.type == pygame.QUIT:
running = False
# キーボード入力の状態をまとめて取得
keys = pygame.key.get_pressed()
# プレイヤーの位置を更新
player.update(keys)
# ★STEP3追加:スペースキーでショット
if keys[pygame.K_SPACE] and player.can_shoot():
player.shoot(bullets)
# ★STEP3追加:弾の位置を更新
for b in bullets:
b.update()
bullets = [b for b in bullets if b.alive] # 画面外に出た弾を片付ける
# 背景を塗りつぶす
screen.fill((15, 18, 25))
# ★STEP3追加:弾を描画
for b in bullets:
b.draw(screen)
# プレイヤーを描画
player.draw(screen)
# 画面下部に案内テキストを描く
msg = font.render("←→ / A・D で移動、Space で弾を発射", True, (220, 220, 230))
screen.blit(msg, (20, HEIGHT - 40))
# 画面を更新
pygame.display.flip()
pygame.quit()
#このファイルを実行したときだけmain()を起動
if __name__ == "__main__":
main()
手順①:Bulletクラスで「弾1発」の情報をまとめる
class Bullet:
def __init__(self, x, y):
self.x, self.y = x, y
self.r = 4
self.alive = True
「Bulletクラス」は、画面上に存在する弾1発の情報を持つためのクラスです。
弾の位置は「x」「y」、大きさは「r」(半径)、まだ画面内に生きているかどうかを「alive」で管理します。
「弾1発」のクラスを作成しておくことで、リストでたくさんの弾を扱うときに管理しやすくなります。
手順②:弾の当たり判定用の四角形を作る
def rect(self):
return pygame.Rect(int(self.x - self.r), int(self.y - self.r), self.r*2, self.r*2)
Pygameの「Rect」は四角形を表すクラスで、後のSTEPで「敵に当たったか」を判定するときに使います。
手順③:弾を上方向へ移動させる
def update(self):
self.y += BULLET_SPEED
if self.y < -10:
self.alive = False
「update」は1フレームごとに弾の状態を更新する関数です。
「BULLET_SPEED」はマイナスの値(-10)なので、y座標は毎フレーム上方向へ移動します。画面の上端より少し外まで行ったら「alive」をFalseにして、後でリストから消せるようにしています。
こうしておくことで、画面外に弾がたまり続けることを防止しています。
手順④:弾を画面に描く
def draw(self, surface):
pygame.draw.circle(surface, (255, 255, 120), (int(self.x), int(self.y)), self.r)
「draw」は画面に弾を描く関数です。「circle」を使って小さな丸として描画しています。
このように、見た目の処理は「draw」、動きの処理は「update」と役割を分けています。
手順⑤:メインループで弾を管理する準備
player = Player()
bullets = [] # ★STEP3追加:発射された弾を入れておくリスト
メインループの冒頭で、弾を入れておくためのリストを用意しています。
弾は1発だけでなく、連続で発射される可能性があります。そのため、「弾のリスト」を持っておき、画面に存在する全ての弾をまとめて管理します。
手順⑥:スペースキーで弾を発射する
if keys[pygame.K_SPACE] and player.can_shoot():
player.shoot(bullets)
「get_pressed」で取得したキー情報から、「スペースキーが押されているかどうか」を調べています。
「can_shoot」は「弾を撃てる状態かどうか」を判定する関数で、クールダウンを設定することで連射しすぎないようにしています。
条件を満たしていれば、「player.shoot(bullets)」で弾を1つ生成し、「bullets」リストに追加します。
手順⑦:弾を更新して、不要な弾を片付ける
for b in bullets:
b.update()
bullets = [b for b in bullets if b.alive]
まず全ての弾に対して「update」を呼び出し、位置を進めます。
その後、「alive」がFalseになった弾(画面外に出た弾)をリストから取り除きます。
手順⑧:弾を描画する
for b in bullets:
b.draw(screen)
player.draw(screen)
背景を塗ったあと、弾を先に描画し、その上にプレイヤーを描画しています。
描画の順番は「奥にあるもの → 手前にあるもの」の順に行うと分かりやすくなります。
STEP3までで、「自機を左右に動かしながらスペースキーで弾を撃つ」ことができるようになります。

STEP4:敵キャラとスコアを追加して「ゲーム」にする
ここでは、上から落ちてくる敵キャラを追加し、弾が当たったら消えてスコアが加算されるようにします。画面に目的が生まれ、シューティングとして遊べる形になります。
import pygame # 2Dゲームや描画を簡単に行えるライブラリ
import random # ★STEP2追加:ランダムな数値を作る標準モジュール
#画面サイズとフレームレート
WIDTH, HEIGHT = 800, 600 # 幅と高さ
FPS = 60 # 1秒あたりの更新回数
#ゲームの基本パラメータ
PLAYER_SPEED = 6
BULLET_SPEED = -10
ENEMY_SPEED_MIN, ENEMY_SPEED_MAX = 2, 4
SPAWN_EVERY_FRAMES = 30
SHOOT_COOLDOWN_FRAMES = 8
#Pygameの初期化とウィンドウ作成
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Simple Shooting - Minimal")
clock = pygame.time.Clock()
#テキスト描画用のフォント
font = pygame.font.SysFont("meiryo", 28)
#プレイヤー(自機)を表すクラス
class Player:
def __init__(self):
self.w, self.h = 36, 28
self.x, self.y = WIDTH // 2, HEIGHT - 70
self.cooldown = 0
self.alive = True
def rect(self):
return pygame.Rect(int(self.x - self.w//2), int(self.y - self.h//2), self.w, self.h)
def update(self, keys):
if keys[pygame.K_LEFT] or keys[pygame.K_a]:
self.x -= PLAYER_SPEED
if keys[pygame.K_RIGHT] or keys[pygame.K_d]:
self.x += PLAYER_SPEED
# 画面外に出ないように左右の位置を制限
self.x = max(self.w//2, min(WIDTH - self.w//2, self.x))
if self.cooldown > 0:
self.cooldown -= 1
def can_shoot(self):
return self.cooldown == 0 and self.alive
def shoot(self, bullets):
bullets.append(Bullet(self.x, self.rect().top))
self.cooldown = SHOOT_COOLDOWN_FRAMES
def draw(self, surface):
top = (self.x, self.y - self.h//2)
left = (self.x - self.w//2, self.y + self.h//2)
right = (self.x + self.w//2, self.y + self.h//2)
# 本体
pygame.draw.polygon(surface, (80, 180, 255), [left, right, top])
# 外枠
pygame.draw.polygon(surface, (200, 230, 255), [left, right, top], 2)
#弾(プレイヤーのショット)を表すクラス
class Bullet:
def __init__(self, x, y):
self.x, self.y = x, y
self.r = 4
self.alive = True
def rect(self):
return pygame.Rect(int(self.x - self.r), int(self.y - self.r), self.r*2, self.r*2)
def update(self):
self.y += BULLET_SPEED
if self.y < -10:
self.alive = False
def draw(self, surface):
pygame.draw.circle(surface, (255, 255, 120), (int(self.x), int(self.y)), self.r)
# ★STEP4追加:敵キャラを表すクラス
class Enemy:
def __init__(self):
self.w, self.h = 28, 20
self.x = random.randint(40, WIDTH - 40) # 画面上部のランダムな位置に出現
self.y = -30
self.speed = random.uniform(ENEMY_SPEED_MIN, ENEMY_SPEED_MAX)
self.alive = True
base = random.randint(130, 220)
self.color = (base, 60, 80)
def rect(self):
return pygame.Rect(int(self.x - self.w//2), int(self.y - self.h//2), self.w, self.h)
def update(self):
self.y += self.speed
if self.y > HEIGHT + 40:
self.alive = False # 画面外まで落ちたら削除対象
def draw(self, surface):
pygame.draw.rect(surface, self.color, self.rect(), border_radius=4)
pygame.draw.rect(surface, (240, 160, 170), self.rect(), 2, border_radius=4)
# ★STEP4変更:プレイヤー・弾・敵・スコアを管理するメインループ
def main():
player = Player()
bullets, enemies = [], [] # ★STEP4変更:敵リストを追加
score, frame = 0, 0 # ★STEP4追加:スコアとフレームカウンタ
game_over = False # ★STEP4追加:ゲームオーバー中かどうか
running = True
while running:
clock.tick(FPS)
# イベント処理:ウィンドウの×ボタンなど
for e in pygame.event.get():
if e.type == pygame.QUIT:
running = False
# ★STEP4追加:ゲームオーバー中にEnterキーでリスタート
if e.type == pygame.KEYDOWN and game_over and e.key == pygame.K_RETURN:
player = Player()
bullets.clear()
enemies.clear()
score, frame, game_over = 0, 0, False
# キーボード入力の状態をまとめて取得
keys = pygame.key.get_pressed()
# ★STEP4追加:ゲームオーバーでない時の処理のインデント
if not game_over:
# プレイヤーの更新
player.update(keys)
# スペースキーでショット
if keys[pygame.K_SPACE] and player.can_shoot():
player.shoot(bullets)
# 弾の更新
for b in bullets:
b.update()
bullets = [b for b in bullets if b.alive]
# ★STEP4追加:一定間隔で敵を出現させる
frame += 1
if frame % SPAWN_EVERY_FRAMES == 0:
enemies.append(Enemy())
# ★STEP4追加:敵の更新
for en in enemies:
en.update()
enemies = [en for en in enemies if en.alive]
# ★STEP4追加:衝突判定(弾→敵)
for b in bullets:
for en in enemies:
if b.alive and en.alive and b.rect().colliderect(en.rect()):
b.alive = False
en.alive = False
score += 10 # 敵を倒したらスコア加算
bullets = [b for b in bullets if b.alive]
enemies = [en for en in enemies if en.alive]
# ★STEP4追加:衝突判定(敵→自機)
for en in enemies:
if en.rect().colliderect(player.rect()):
player.alive = False
game_over = True
break
# 背景を塗りつぶす
screen.fill((15, 18, 25))
# 弾を描画
for b in bullets:
b.draw(screen)
# ★STEP4追加:敵を描画
for en in enemies:
en.draw(screen)
# プレイヤーを描画
if player.alive:
player.draw(screen)
# ★STEP4追加:スコア表示
screen.blit(font.render(f"Score: {score}", True, (230, 230, 240)), (12, 10))
# ★STEP4追加:ゲームオーバー表示
if game_over:
msg1 = pygame.font.SysFont(None, 64).render("GAME OVER", True, (255, 220, 220))
msg2 = font.render("Press Enter to Restart", True, (210, 210, 220))
screen.blit(msg1, msg1.get_rect(center=(WIDTH//2, HEIGHT//2 - 16)))
screen.blit(msg2, msg2.get_rect(center=(WIDTH//2, HEIGHT//2 + 28)))
pygame.display.flip()
pygame.quit()
#このファイルを実行したときだけmain()を起動
if __name__ == "__main__":
main()
手順①:Enemyクラスで「敵1体」を定義する
class Enemy:
def __init__(self):
self.w, self.h = 28, 20
self.x = random.randint(40, WIDTH - 40)
self.y = -30
self.speed = random.uniform(ENEMY_SPEED_MIN, ENEMY_SPEED_MAX)
self.alive = True
base = random.randint(130, 220)
self.color = (base, 60, 80)
敵キャラ1体分の情報を「Enemyクラス」としてまとめています。
- 「w, h」…敵の幅と高さ
- 「x, y」…位置(最初は画面の上の見えないところからスタート)
- 「speed」…下方向に落ちる速さ
- 「alive」…まだ画面に存在しているかどうか
- 「color」…少しランダムに変化する色
「random.randint」や「random.uniform」はランダムな数を作る関数です。これによって、敵が毎回違う位置・スピードで出現し、単調にならないようにしています。
手順②:敵の動きと削除条件
def update(self):
self.y += self.speed
if self.y > HEIGHT + 40:
self.alive = False
「update」は毎フレーム呼び出され、敵の位置を下方向へ進めます。
画面の下(HEIGHT)よりさらに少し下まで落ちたら、「alive」をFalseにして、あとでリストから消せるようにしています。
こうすることで、見えなくなった敵を放置せず、メモリや処理の無駄を減らせます。
手順③:敵を画面に描画する
def draw(self, surface):
pygame.draw.rect(surface, self.color, self.rect(), border_radius=4)
pygame.draw.rect(surface, (240, 160, 170), self.rect(), 2, border_radius=4)
敵は四角形(rect)として描いています。
手順④:メインループで敵を出現させる
bullets, enemies = [], []
score, frame = 0, 0
...
if not game_over:
...
frame += 1
if frame % SPAWN_EVERY_FRAMES == 0:
enemies.append(Enemy())
- 「enemies」リスト…画面上に存在する敵をまとめて管理
- 「frame」…フレーム数(whileループが何回回ったか)
を使って、一定間隔で新しい敵を追加しています。
「SPAWN_EVERY_FRAMES」が小さいほど、敵の出現頻度が高くなり、ゲームが難しくなります。
手順⑤:弾と敵の衝突判定でスコアを加算する
for b in bullets:
for en in enemies:
if b.alive and en.alive and b.rect().colliderect(en.rect()):
b.alive = False
en.alive = False
score += 10
2重ループで「すべての弾」と「すべての敵」の組み合わせをチェックしています。
「colliderect」はPygameの四角形同士の衝突判定関数で、「ぶつかったかどうか」を簡単に調べることができます。
当たっていたら、その弾と敵を両方「alive = False」にして、スコアを10点増やします。
手順⑥:敵と自機がぶつかったらゲームオーバーにする
for en in enemies:
if en.rect().colliderect(player.rect()):
player.alive = False
game_over = True
break
「全ての敵」について、自機の四角形と重なっているかを調べています。
1体でも当たればゲームオーバーなので、「game_over」をTrueにしてループを抜けています。
ゲームオーバー後は「if not game_over:」の中の更新処理が止まるため、プレイヤーや敵の動きが停止します。
手順⑦:スコアとゲームオーバー表示
screen.blit(font.render(f"Score: {score}", True, (230, 230, 240)), (12, 10))
if game_over:
msg1 = pygame.font.SysFont(None, 64).render("GAME OVER", True, (255, 220, 220))
msg2 = font.render("Press Enter to Restart", True, (210, 210, 220))
screen.blit(msg1, msg1.get_rect(center=(WIDTH//2, HEIGHT//2 - 16)))
screen.blit(msg2, msg2.get_rect(center=(WIDTH//2, HEIGHT//2 + 28)))
スコアは左上に表示しています。
ゲームオーバー時には、中央に「GAME OVER」と「Press Enter to Restart」という2行のメッセージを表示します。
手順⑧:Enterキーでゲームをリスタートする
if e.type == pygame.KEYDOWN and game_over and e.key == pygame.K_RETURN:
player = Player()
bullets.clear()
enemies.clear()
score, frame, game_over = 0, 0, False
ゲームオーバー中にEnterキーが押されたとき、プレイヤー・弾・敵・スコア・フレーム数を初期化して最初の状態に戻します。
このSTEP4までで、「敵を撃ってスコアを稼ぎ、当たるとゲームオーバー、Enterで再挑戦」という、一通り遊べる形のシューティングゲームが完成しています。

STEP5:コード全体を整理して読みやすくする(リファクタリング)
ここでは、ゲームの動きを変えずに「構造」と「見通し」を良くするためのリファクタリングを行います。
import pygame
import random
# ===== 基本設定 =====
WIDTH, HEIGHT = 800, 600
FPS = 60
PLAYER_SPEED = 6
BULLET_SPEED = -10
ENEMY_SPEED_MIN, ENEMY_SPEED_MAX = 2, 4
SPAWN_EVERY_FRAMES = 30
SHOOT_COOLDOWN_FRAMES = 8
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Simple Shooting - Minimal")
clock = pygame.time.Clock()
font = pygame.font.SysFont("meiryo", 28)
# ===== プレイヤー =====
class Player:
def __init__(self):
self.w, self.h = 36, 28
self.x, self.y = WIDTH // 2, HEIGHT - 70
self.cooldown = 0
self.alive = True
def rect(self):
return pygame.Rect(int(self.x - self.w//2), int(self.y - self.h//2), self.w, self.h)
def update(self, keys):
if keys[pygame.K_LEFT] or keys[pygame.K_a]:
self.x -= PLAYER_SPEED
if keys[pygame.K_RIGHT] or keys[pygame.K_d]:
self.x += PLAYER_SPEED
self.x = max(self.w//2, min(WIDTH - self.w//2, self.x))
if self.cooldown > 0:
self.cooldown -= 1
def can_shoot(self):
return self.cooldown == 0 and self.alive
def shoot(self, bullets):
bullets.append(Bullet(self.x, self.rect().top))
self.cooldown = SHOOT_COOLDOWN_FRAMES
def draw(self, surface):
top = (self.x, self.y - self.h//2)
left = (self.x - self.w//2, self.y + self.h//2)
right = (self.x + self.w//2, self.y + self.h//2)
pygame.draw.polygon(surface, (80, 180, 255), [left, right, top])
pygame.draw.polygon(surface, (200, 230, 255), [left, right, top], 2)
# ===== 弾 =====
class Bullet:
def __init__(self, x, y):
self.x, self.y = x, y
self.r = 4
self.alive = True
def rect(self):
return pygame.Rect(int(self.x - self.r), int(self.y - self.r), self.r*2, self.r*2)
def update(self):
self.y += BULLET_SPEED
if self.y < -10:
self.alive = False
def draw(self, surface):
pygame.draw.circle(surface, (255, 255, 120), (int(self.x), int(self.y)), self.r)
# ===== 敵 =====
class Enemy:
def __init__(self):
self.w, self.h = 28, 20
self.x = random.randint(40, WIDTH - 40)
self.y = -30
self.speed = random.uniform(ENEMY_SPEED_MIN, ENEMY_SPEED_MAX)
self.alive = True
base = random.randint(130, 220)
self.color = (base, 60, 80)
def rect(self):
return pygame.Rect(int(self.x - self.w//2), int(self.y - self.h//2), self.w, self.h)
def update(self):
self.y += self.speed
if self.y > HEIGHT + 40:
self.alive = False
def draw(self, surface):
pygame.draw.rect(surface, self.color, self.rect(), border_radius=4)
pygame.draw.rect(surface, (240, 160, 170), self.rect(), 2, border_radius=4)
# ===== メインゲーム =====
def main():
player = Player()
bullets, enemies = [], []
score, frame = 0, 0
game_over = False
running = True
while running:
clock.tick(FPS)
for e in pygame.event.get():
if e.type == pygame.QUIT:
running = False
if e.type == pygame.KEYDOWN and game_over and e.key == pygame.K_RETURN:
player = Player()
bullets.clear()
enemies.clear()
score, frame, game_over = 0, 0, False
keys = pygame.key.get_pressed()
if not game_over:
player.update(keys)
if keys[pygame.K_SPACE] and player.can_shoot():
player.shoot(bullets)
for b in bullets:
b.update()
bullets = [b for b in bullets if b.alive]
frame += 1
if frame % SPAWN_EVERY_FRAMES == 0:
enemies.append(Enemy())
for en in enemies:
en.update()
enemies = [en for en in enemies if en.alive]
# 衝突判定(弾→敵)
for b in bullets:
for en in enemies:
if b.alive and en.alive and b.rect().colliderect(en.rect()):
b.alive = False
en.alive = False
score += 10
bullets = [b for b in bullets if b.alive]
enemies = [en for en in enemies if en.alive]
# 衝突判定(敵→自機)
for en in enemies:
if en.rect().colliderect(player.rect()):
player.alive = False
game_over = True
break
# 描画
screen.fill((15, 18, 25))
for b in bullets:
b.draw(screen)
for en in enemies:
en.draw(screen)
if player.alive:
player.draw(screen)
# スコア表示
screen.blit(font.render(f"Score: {score}", True, (230, 230, 240)), (12, 10))
# ゲームオーバー表示
if game_over:
msg1 = pygame.font.SysFont(None, 64).render("GAME OVER", True, (255, 220, 220))
msg2 = font.render("Press Enter to Restart", True, (210, 210, 220))
screen.blit(msg1, msg1.get_rect(center=(WIDTH//2, HEIGHT//2 - 16)))
screen.blit(msg2, msg2.get_rect(center=(WIDTH//2, HEIGHT//2 + 28)))
pygame.display.flip()
pygame.quit()
if __name__ == "__main__":
main()
手順①:大きな役割単位でコメントを修正
# ===== 基本設定 =====
# ...
# ===== プレイヤー =====
# ...
# ===== 弾 =====
# ...
# ===== 敵 =====
# ...
# ===== メインゲーム =====
役割ごとに、
- 「基本設定」
- 「プレイヤー」
- 「弾」
- 「敵」
- 「メインゲーム」
という単位で、コメントの見出しをそろえています。
加えて不要なコメントを削除しています。
これにより、初めてコードを読む人でも「どこを見ればプレイヤーの処理なのか」「どこがメインループなのか」をすぐに探せるようにしていますなります。
今回のリファクタリングはコメントのみのため、これでコードが完成しました。
完成
以上でPythonで作る「シューティングゲーム」の完成です。
ぜひ、コードをコピペするのではなく、実際にコードを打って作ってみてください。

お疲れさまでした。
