【Pygame】スペースインベーダーゲームを作成する方法

Pygameでスペースインベーダーゲームを作成する方法をソースコード付きで詳しく解説します。

【1】スペースインベーダーゲーム(簡易版)

Pygameでスペースインベーダーゲームを作成する方法を解説します。
まず、スペースインベーダーゲームの骨組みを理解するために、以下動画のようなシンプルな物から作成します。

【1-1】サンプルコード①

Sキーを押すとゲームが開始されます。
スペースキーでプレイヤー(緑)から弾が発射され、迫ってくるエイリアンに当たると撃ち落とすことができます。


【1-2】サンプルコード①の解説

上記コードの各部分について解説します。

1. 色の定義

WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
DARK_GREEN = (0, 40, 0)
  • ゲーム内で使用する色をRGB形式で定義しています。

2. プレイヤークラス

プレイヤーのスプライトを定義し、左右の移動を制御します。

class Player(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image = pygame.Surface((30, 30))
        self.image.fill(GREEN)
        self.rect = self.image.get_rect()
        self.rect.center = (400, 550)
        self.speed = 5

    def update(self):
        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT] and self.rect.left > 0:
            self.rect.x -= self.speed
        if keys[pygame.K_RIGHT] and self.rect.right < 800:
            self.rect.x += self.speed
  • __init__ メソッド: クラスのコンストラクタで、プレイヤーの初期設定を行います。
    • super().__init__(): 親クラス(pygame.sprite.Sprite)の初期化メソッドを呼び出します。
    • self.image = pygame.Surface((30, 30)): 30×30ピクセルのサーフェス(画像)を作成します。
    • self.image.fill(GREEN): サーフェスを緑色で塗りつぶします。
    • self.rect = self.image.get_rect(): サーフェスの矩形(rect)を取得します。スプライトの位置とサイズを管理するために使用されます。
    • self.rect.center = (400, 550): 矩形の中心を画面の指定位置(400, 550)に設定します。プレイヤーの初期位置を設定します。
    • self.speed = 5: プレイヤーの移動速度を5に設定します。
  • update メソッド: プレイヤーの位置を更新します。このメソッドは毎フレーム呼び出されます。
    • keys = pygame.key.get_pressed(): 現在押されているキーの状態を取得します。
    • if keys[pygame.K_LEFT] and self.rect.left > 0: 左矢印キーが押されていて、プレイヤーが画面の左端に達していない場合に条件が成立します。
      • self.rect.x -= self.speed: プレイヤーを左に移動させます。
    • if keys[pygame.K_RIGHT] and self.rect.right < 800: 右矢印キーが押されていて、プレイヤーが画面の右端に達していない場合に条件が成立します。
      • self.rect.x += self.speed: プレイヤーを右に移動させます。

3. エイリアンクラス

エイリアンのスプライトを定義し、左右に移動しながら画面端に達すると下に移動します。

class Alien(pygame.sprite.Sprite):
    def __init__(self, x, y):
        super().__init__()
        self.image = pygame.Surface((30, 30))
        self.image.fill(RED)
        self.rect = self.image.get_rect()
        self.rect.topleft = (x, y)
        self.speed = 2

    def update(self):
        self.rect.x += self.speed
        if self.rect.right >= 800 or self.rect.left <= 0:
            self.speed = -self.speed
        self.rect.y += 40
  • __init__ メソッド: クラスのコンストラクタで、エイリアンの初期設定を行います。
    • super().__init__(): 親クラス(pygame.sprite.Sprite)の初期化メソッドを呼び出します。
    • self.image = pygame.Surface((30, 30)): 30×30ピクセルのサーフェス(画像)を作成します。
    • self.image.fill(RED): サーフェスを赤色で塗りつぶします。
    • self.rect = self.image.get_rect(): サーフェスの矩形(rect)を取得します。スプライトの位置とサイズを管理するために使用されます。
    • self.rect.topleft = (x, y): 矩形の左上の位置を引数として渡されたxyに設定します。
    • self.speed = 2: エイリアンの移動速度を2に設定します。
  • update メソッド: エイリアンの位置を更新します。このメソッドは毎フレーム呼び出されます。
    • self.rect.x += self.speed: エイリアンを現在の速度で横方向に移動させます。
    • if self.rect.right >= 800 or self.rect.left <= 0: エイリアンが画面の右端(800px)に達するか、左端(0px)に達した場合(折り返し用)
      • self.speed = -self.speed: エイリアンの移動方向を反転させます(右に移動していた場合は左に、左に移動していた場合は右に)。
      • self.rect.y += 40: エイリアンを縦方向に40ピクセル下に移動させます。エイリアンが画面端に達したときに一段下に降りる動作をシミュレートします。

4. 弾クラス

class Bullet(pygame.sprite.Sprite):
    def __init__(self, x, y):
        super().__init__()
        self.image = pygame.Surface((5, 10))
        self.image.fill(WHITE)
        self.rect = self.image.get_rect()
        self.rect.center = (x, y)
        self.speed = -10

    def update(self):
        self.rect.y += self.speed
        if self.rect.bottom < 0:
            self.kill()
  • 弾のスプライトを定義し、self.speed=-10で上方向10pxずつ移動します。
  • updateメソッド内で、弾が画面の上端を超えた場合にself.kill()を呼び出して削除しています。

5. メインループ

ゲームの初期化、メインループ、イベント処理、スプライトの更新、描画、スコア表示、ゲーム終了判定を行います。

5-1. 初期化

    pygame.init()
  • Pygameライブラリを初期化します。

5-2. 画面設定

    screen = pygame.display.set_mode((800, 600))
    pygame.display.set_caption("Space Invaders")
  • ゲームウィンドウのサイズを800×600ピクセルに設定し、タイトルを「Space Invaders」にします。

5-3. フォント設定

    font = pygame.font.SysFont(None, 55)
  • スコアやメッセージを表示するためのフォントを設定します。

5-4. スプライトグループの作成

    all_sprites = pygame.sprite.Group()
    aliens = pygame.sprite.Group()
    bullets = pygame.sprite.Group()
  • ゲーム内のスプライト(プレイヤー、エイリアン、弾丸)を管理するためのグループを作成します。

それぞれのグループには以下の役割があります。

  • all_spritesグループ
    • ゲーム内のすべてのスプライトの更新と描画を管理します。例えば、all_sprites.update()all_sprites.draw(screen)を使うことで、全スプライトの更新や描画を一度に行えます。
  • aliensグループ
    • エイリアンのスプライトの操作や判定を行うためのグループです。例えば、弾丸との衝突判定を行う場合、pygame.sprite.groupcollide(bullets, aliens, True, True)を使うことで、弾丸とエイリアンの衝突を効率的に判定できます。
  • bulletsグループ
    • 弾丸のスプライトの操作や判定を行うためのグループです。例えば、弾丸が画面外に出た際に削除する処理や、エイリアンとの衝突判定を行う際に使用します。

5-5. プレイヤーとエイリアンの生成

    player = Player()
    all_sprites.add(player)
    for i in range(10):
        for j in range(3):
            alien = Alien(50 + i * 50, 70 + j * 80)
            all_sprites.add(alien)
            aliens.add(alien)
  • プレイヤーとエイリアンを生成し、それぞれのスプライトグループに追加します。

5-6. ゲームループ

ゲームが終了するまでのメインループです。イベント処理やスプライトの更新、描画を行います。

    # スコアの初期化
    score = 0

    # フラグ
    running = True
    game_over = False
    game_clear = False
    game_started = False

    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_SPACE and not game_over and not game_clear:
                    bullet = Bullet(player.rect.centerx, player.rect.top)
                    all_sprites.add(bullet)
                    bullets.add(bullet)
                if event.key == pygame.K_s:
                    game_started = True
  • スコアの初期化(score = 0): ゲーム開始時のスコアを0に設定します。
  • running: ゲームループを制御するためのフラグです。Trueの間はゲームが実行されます。
  • game_over: ゲームオーバーの状態を判定するためのフラグです。Falseの間はゲームが続行されます。
  • game_clear: ゲームクリアの状態を判定するためのフラグです。Falseの間はゲームが続行されます。
  • game_started: ゲームが開始されたかどうかを判定するためのフラグです。Falseの間はゲームが開始されていません。
  • while running:: runningTrueの間、ゲームループが実行されます。
  • pygame.event.get(): Pygameのイベントキューからイベントを取得します。
  • if event.type == pygame.QUIT:: ウィンドウの閉じるボタンが押された場合、runningFalseに設定してゲームループを終了します。
  • if event.type == pygame.KEYDOWN:: キーが押された場合の処理を行います。
  • event.key == pygame.K_SPACE and not game_over: スペースキーが押され、かつゲームオーバーでない場合、弾を発射します。
  • 弾の生成: bullet = Bullet(player.rect.centerx, player.rect.top)でプレイヤーの位置から弾を生成し、all_spritesおよびbulletsグループに追加します。
  • if event.key == pygame.K_s: Sキーが押された場合、game_startedTrueに設定し、ゲームを開始します。

5-7. 衝突判定とスコア更新:

        if not game_over and not game_clear and game_started:
            # 更新
            all_sprites.update()
            # 衝突判定
            hits = pygame.sprite.groupcollide(bullets, aliens, True, True)
            if hits:
                score += 10
  • ゲームオーバーでもゲームクリア状態でもなく、ゲームが開始状態であれば、全てのスプライトを更新します。
  • 弾丸とエイリアンの衝突を判定し、衝突があればスコアを更新します(10点加算)。
  • pygame.sprite.groupcollidemメソッドの引数True, Trueは、衝突した弾とエイリアンのスプライト両方を削除します。
pygame.sprite.groupcollide(group1, group2, dokill1, dokill2, collided=None)
  • group1: 最初のスプライトグループ(例:bullets)。
  • group2: 2番目のスプライトグループ(例:aliens)。
  • dokill1: Trueの場合、group1の衝突したスプライトを削除します。
  • dokill2: Trueの場合、group2の衝突したスプライトを削除します。
  • collided: 衝突判定に使用するカスタム関数(省略可能)。デフォルトでは矩形の衝突判定が使用されます。
  • 戻り値: 衝突したスプライトのペアを含む辞書を返します。辞書のキーはgroup1のスプライト、値はそれに衝突したgroup2のスプライトのリストです。

5-8. ゲームクリア判定:

            # ゲームクリア判定
            if not aliens:
                game_clear = True
            # ゲームオーバー判定(エイリアンがプレイヤーの位置まで到達した場合)
            for alien in aliens:
                if alien.rect.bottom >= player.rect.top:
                    game_over = True
  • すべてのエイリアンが倒されたらゲームクリアとします。
  • イリアンがプレイヤーの位置まで到達した場合、ゲームオーバーとします。

5-9. 描画と画面更新:

        # 描画
        screen.fill(DARK_GREEN)
        all_sprites.draw(screen)
        # スコア表示
        score_text = font.render(f"Score: {score}", True, WHITE)
        screen.blit(score_text, (10, 10))
        # ゲームオーバー表示
        if game_over:
            game_over_text = font.render("GAME OVER", True, WHITE)
            screen.blit(game_over_text, (300, 250))
            restart_text = font.render("Press 'R' to Restart", True, WHITE)
            screen.blit(restart_text, (250, 300))
            # 古いスプライトグループの削除
            all_sprites.empty()
            aliens.empty()
            bullets.empty()
        # ゲームクリア表示
        if game_clear:
            game_clear_text = font.render("GAME CLEAR", True, WHITE)
            screen.blit(game_clear_text, (300, 250))
  • 画面を塗りつぶし、スプライトを描画します。
  • スコアや判定フラグに応じてゲームオーバー、ゲームクリアメッセージを表示します。
  • ゲームオーバー時はスプライトグループを空にします。
  • 画面を更新し、フレームレートを設定します。

5-10. 終了処理:

    pygame.quit()
    sys.exit()
  • ゲームを終了し、システムを終了します。

【2】スペースインベーダーゲーム(簡易版②敵の弾幕を実装)

次に、エイリアンの弾幕を実装し、以下動画のように改良します。

【2-1】サンプルコード②

Sキーを押すとゲームが開始されます。
スペースキーでプレイヤー(緑)から弾が発射され、迫ってくるエイリアンに当たると撃ち落とすことができます。
エイリアンがプレイヤーの位置まで到達するか、エイリアンの弾にプレイヤーが当たるとゲームオーバーです。


【2-2】サンプルコード②の解説

サンプルコード①と異なる部分を中心に解説します。

エイリアンクラス

# エイリアンクラス
class Alien(pygame.sprite.Sprite):
    def __init__(self, x, y, all_sprites, alien_bullets):
        super().__init__()
        self.image = pygame.Surface((30, 30))
        self.image.fill(RED)
        self.rect = self.image.get_rect()
        self.rect.topleft = (x, y)
        self.speed = 2
        self.all_sprites = all_sprites
        self.alien_bullets = alien_bullets

    def update(self):
        self.rect.x += self.speed
        if self.rect.right >= 800 or self.rect.left <= 0:
            self.speed = -self.speed
            self.rect.y += 40
        if random.randint(1, 300) == 1:  # 1/300の確率で弾を発射
            bullet = AlienBullet(self.rect.centerx, self.rect.bottom)
            self.all_sprites.add(bullet)
            self.alien_bullets.add(bullet)
  • 初期化メソッド (__init__): エイリアンの初期設定を行います。
    • xyはエイリアンの初期位置です。
    • self.imageはエイリアンの見た目を定義するためのサーフェスです。サイズは30×30ピクセルで、色は赤(RED)に設定されています。
    • self.rectはエイリアンの位置とサイズを管理する矩形(rect)オブジェクトです。
    • self.rect.topleftはエイリアンの左上の位置を設定します。
    • self.speedはエイリアンの移動速度を設定します。
    • self.all_spritesself.alien_bulletsは、エイリアンが弾を発射する際に使用するスプライトグループです。
  • 更新メソッド (__init__): エイリアンの更新を行います。
    • self.rect.x += self.speedでエイリアンを左右に移動させます。
    • エイリアンが画面の右端(800ピクセル)に達すると、self.rect.right >= 800が真となり、速度の符号を反転させます(self.speed = -self.speed)。これにより、エイリアンは反対方向に移動します。
    • 同様に、エイリアンが画面の左端に達すると、同じ処理が行われます。
    • エイリアンが方向を変えるたびに、self.rect.y += 40でエイリアンを下に移動させます。
    • 弾の発射: random.randint(1, 300) == 1で1/300の確率で弾を発射します。
    • AlienBulletクラスのインスタンスを作成し、エイリアンの中心から弾を発射します。
    • エイリアンが弾を発射する度に、新しい弾をself.all_spritesself.alien_bulletsのスプライトグループに追加します。

エイリアンの弾クラス

このクラスにより、エイリアンが発射する弾は下方向に移動し、画面外に出た場合は自動的に削除されます。

# エイリアンの弾クラス
class AlienBullet(pygame.sprite.Sprite):
    def __init__(self, x, y):
        super().__init__()
        self.image = pygame.Surface((5, 10))
        self.image.fill(RED)
        self.rect = self.image.get_rect()
        self.rect.center = (x, y)
        self.speed = 5

    def update(self):
        self.rect.y += self.speed
        if self.rect.top > 600:
            self.kill()
  • 初期化メソッド (__init__): エイリアンの弾の初期設定を行います。
    • xyは弾の初期位置です。
    • self.imageは弾の見た目を定義するためのサーフェスです。サイズは5×10ピクセルで、色は赤(RED)に設定されています。
    • self.rectは弾の位置とサイズを管理する矩形(rect)オブジェクトです。
    • self.rect.centerは弾の中心位置を設定します。
    • self.speedは弾の移動速度を設定します。
  • **updateメソッド
    • 移動: self.rect.y += self.speedで弾を下方向に移動させます。
    • self.speedは正の値なので、弾は下に向かって移動します。
    • 画面外の処理: if self.rect.top > 600で弾が画面の下端(600ピクセル)を超えたかどうかをチェックします。
    • 画面外に出た弾はself.kill()で削除されます。これにより、メモリの無駄遣いを防ぎます。

エイリアンの弾用のスプライトグループ作成

main関数内でエイリアンが発射する弾を管理するためのスプライトグループを作成しています。

alien_bullets = pygame.sprite.Group()

弾の生成

エイリアンが弾を発射する際に、新しい弾をalien_bulletsグループに追加します。

if event.key == pygame.K_SPACE and not game_over:
    bullet = Bullet(player.rect.centerx, player.rect.top)
    all_sprites.add(bullet)
    bullets.add(bullet)

弾の更新

メインループ内で、alien_bulletsグループ内のすべての弾を更新します。

alien_bullets.update()

弾の描画

alien_bulletsグループ内のすべての弾を描画します。

alien_bullets.draw(screen)

衝突判定

プレイヤーとエイリアンの弾の衝突を判定し、弾が当たったらゲームオーバーとします。

player_hits = pygame.sprite.spritecollide(player, alien_bullets, True)
if player_hits:
    game_over = True

リスタート時の初期化

ゲームオーバー後、alien_bulletsのスプライトグループも空にします。

# スプライトグループを空にする
all_sprites.empty()
aliens.empty()
bullets.empty()
alien_bullets.empty()

【3】画像と音楽を付けた完成版

サンプルコード②に音と画像を入れて、以下動画のようにゲームらしくします。

【使用した音声ファイル】

  • shoot.mp3・・・8bitショット3をお借りました。
  • hit.mp3・・・8bit爆発2をお借りました。
  • clear.mp3・・・8bit勝利1をお借りました。
  • gameover.mp3・・・8bit失敗3をお借りました。
  • gameplay.mp3・・・SF2をお借りました。

【使用した画像ファイル】

【3-1】サンプルコード③


【3-2】サンプルコード③の解説

サンプルコード②と異なる部分を中心に解説します。

画像と音声ファイルのパス

ゲーム内で画像や音声を読み込む際に使用します。

# 画像ファイルのパス
PLAYER_IMG_PATH = "/Users/github/sample/python/pygame/invader/sample03/assets/img/player.png"
ALIEN_IMG_PATH = "/Users/github/sample/python/pygame/invader/sample03/assets/img/alien.png"

# 音声ファイルのパス
SHOOT_SOUND_PATH = "/Users/github/sample/python/pygame/invader/sample03/assets/sound/shoot.mp3"
HIT_SOUND_PATH = "/Users/github/sample/python/pygame/invader/sample03/assets/sound/hit.mp3"
CLEAR_SOUND_PATH = "/Users/github/sample/python/pygame/invader/sample03/assets/sound/clear.mp3"
GAMEOVER_SOUND_PATH = "/Users/github/sample/python/pygame/invader/sample03/assets/sound/gameover.mp3"
GAMEPLAY_SOUND_PATH = "/Users/github/sample/python/pygame/invader/sample03/assets/sound/gameplay.mp3"
  • PLAYER_IMG_PATHは、プレイヤーキャラクターの画像ファイルのパスです。
  • ALIEN_IMG_PATHは、エイリアンキャラクターの画像ファイルのパスです。
  • SHOOT_SOUND_PATHは、プレイヤーが弾を発射する際の音声ファイルのパスです。
  • HIT_SOUND_PATHは、弾がエイリアンに当たったときの音声ファイルのパスです。
  • CLEAR_SOUND_PATHは、ゲームクリア時の音声ファイルのパスです。
  • GAMEOVER_SOUND_PATHは、ゲームオーバー時の音声ファイルのパスです。
  • GAMEPLAY_SOUND_PATHは、ゲームプレイ中に流れる音楽のファイルのパスです。

プレイヤークラスの定義

class Player(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image = pygame.Surface((50, 50))
        self.image.fill(WHITE)
        self.rect = self.image.get_rect()
        self.rect.center = (400, 550)

    def update(self):
        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT]:
            self.rect.x -= 5
        if keys[pygame.K_RIGHT]:
            self.rect.x += 5
  • self.imageは、プレイヤーの画像を読み込みます。convert_alpha()を使うことで、画像の透明部分を処理できます。
  • self.rectは、画像の矩形(四角形)領域を取得します。これにより、画像の位置や衝突判定を管理できます。
  • self.rect.centerで、プレイヤーの初期位置を設定します。(400, 550)は、プレイヤーの中心座標です。

エイリアンクラスの定義

# エイリアンクラス
class Alien(pygame.sprite.Sprite):
    def __init__(self, x, y, all_sprites, alien_bullets):
        super().__init__()
        self.image = pygame.image.load(ALIEN_IMG_PATH).convert_alpha()
        self.rect = self.image.get_rect()
        self.rect.topleft = (x, y)
        self.speed = 2
        self.all_sprites = all_sprites
        self.alien_bullets = alien_bullets
  • self.imageは、エイリアンの画像を読み込みます。convert_alpha()を使うことで、画像の透明部分を処理できます。
  • self.rectは、画像の矩形(四角形)領域を取得します。これにより、画像の位置や衝突判定を管理できます。
  • self.rect.topleftで、エイリアンの初期位置を設定します。(x, y)は、エイリアンの左上の座標です。

音声ファイルのロード

    gameplay_sound_played = False
    shoot_sound = pygame.mixer.Sound(SHOOT_SOUND_PATH)
    hit_sound = pygame.mixer.Sound(HIT_SOUND_PATH)
    clear_sound = pygame.mixer.Sound(CLEAR_SOUND_PATH)
    gameover_sound = pygame.mixer.Sound(GAMEOVER_SOUND_PATH)
    gameplay_sound = pygame.mixer.Sound(GAMEPLAY_SOUND_PATH)

    shoot_sound.set_volume(0.2)
    hit_sound.set_volume(0.2)
  • pygame.mixer.Soundを使って、各音声ファイルをロードします。
    • shoot_soundは、弾を発射する際の音声です。
    • hit_soundは、弾がエイリアンに当たったときの音声です。
    • clear_soundは、ゲームクリア時の音声です。
    • gameover_soundは、ゲームオーバー時の音声です。
    • gameplay_soundは、ゲームプレイ中に流れる音楽です。
  • set_volumeメソッドを使って、shoot_soundとhit_soundの音量を設定します。ここでは、0.2(最大音量の20%)で音量を設定します。

音声ファイルの再生

if not gameplay_sound_played and not game_over and not game_clear and game_started:
            gameplay_sound.play(-1)
            gameplay_sound_played = True
  • ゲーム中音楽がまだ再生されていない、ゲームが終了していない、ゲームがクリアされていない、ゲームが開始されているという4つの条件を全て満たしている場合、gameplay_sound.play(-1)メソッドを呼び出すことで、ゲーム中用の音楽を無限ループで再生します。-1は無限ループを意味します。
if event.key == pygame.K_SPACE and not game_over:
    shoot_sound.play()
  • play()メソッドを呼び出すことで、弾を発射する音を再生します。
if hits:
    hit_sound.play()
  • play()メソッドを呼び出すことで、弾がエイリアンに当たった音を再生します。
if player_hits:
    gameover_sound.play()
  • play()メソッドを呼び出すことで、自機に弾が当たったときにゲームオーバーの音を再生します。
if alien.rect.bottom >= player.rect.top:
    gameover_sound.play()
  • play()メソッドを呼び出すことで、エイリアンが自機の位置まで到達したときにゲームオーバーの音を再生します。
if not aliens and not game_clear:
    clear_sound.play()
  • play()メソッドを呼び出すことで、エイリアンを全て撃墜したときにゲームクリアの音を再生します。
if game_over:
    gameplay_sound.stop()
(略)
if game_clear:
    gameplay_sound.stop()
  • stop()メソッドを呼び出すことで、ゲームオーバーもしくはゲームクリア時に、ゲームプレイ中のBGMを停止します。

関連ページ

Pygameの使い方については以下ページで解説しています。

【Pygame超入門】使い方とサンプルゲームを解説
Pythonモジュール「Pygame」で2Dゲームを簡単に制作する方法を入門者向けに解説します。

Python全般については以下ページで解説しています。

【Python超入門】使い方とサンプル集
Pythonの基礎文法から応用例まで入門者向けに解説します。

コメント