【ゼロから始めるPython】Pythonで東方風の弾幕シューティングゲームを作ってみよう!

Python
この記事を書いた人
柊あい

生成AI研究家。新卒で一般企業に就職するも、日々の過酷な残業でメンタルを崩し、退職。
そんな中、生成AIに出会い、当時はまだ珍しかったAI活用フリーランサーとして活動。現在はAI活用を広めるインフルエンサーとして、本サイトの記事の執筆を担当。

公式SNSをフォローする

はじめに

今回は、Pygameを使用して東方風の縦スクロール型シューティングゲームを作成する方法を紹介します。シューティングゲームの基本要素を学びながら、プレイヤーキャラの移動、弾の発射、敵のランダム移動、弾幕攻撃などを実装していきます。

この記事では、Pythonを使用したゲーム開発に興味がある方に向けて、ステップバイステップで実装方法を解説します。完成したゲームは、プレイヤーが敵を倒すとクリア、逆に敵の弾幕に3回当たるとゲームオーバーとなる仕様です。

Pythonで制作できるゲーム一覧はこちらの記事で確認できます!


必要な機能

本記事で作成するシューティングゲームには、以下の機能を実装します。

  • スタート画面の表示
  • プレイヤーの移動 (上下左右の操作)
  • 弾の発射 (スペースキーで発射)
  • 敵キャラのランダム移動
  • 敵キャラの弾幕攻撃 (2秒ごとに同心円状に弾を発射)
  • 衝突判定 (プレイヤーの弾が敵に当たるとダメージ、敵の弾に当たるとライフ減少)
  • ゲームクリア条件 (敵に50発当てる)
  • ゲームオーバー条件 (プレイヤーが敵の弾に3回当たる)
  • ゲーム結果の表示 (クリア・ゲームオーバー画面)
  • プレイヤーと敵の体力をバーで表示

準備: 必要なライブラリのインストール

まず、Pygameをインストールしていない場合は、以下のコマンドでインストールしましょう。

pip install pygame

ステップ1: 基本設定と画面の準備

Pygameを初期化し、ゲームウィンドウを作成します。画面サイズを480×640に設定し、フレームレートを60FPSに固定します。

import pygame

# 画面サイズ
WIDTH, HEIGHT = 480, 640

# Pygameの初期化
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("東方風シューティング")
clock = pygame.time.Clock()

ステップ2: 色とキャラクター・ゲーム要素の設定

Pygameでは、色をRGB形式で指定します。ゲーム内のオブジェクトとして、プレイヤーと敵キャラをクラスとして定義します。

WHITE = (255, 255, 255)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
BLACK = (0, 0, 0)
GREEN = (0, 255, 0)

ここで、プレイヤーと敵キャラの基本的な設定を行います。

class Player(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image = pygame.Surface((30, 30))
        self.image.fill(BLUE)
        self.rect = self.image.get_rect(center=(WIDTH // 2, HEIGHT - 50))
        self.lives = 3
class Enemy(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image = pygame.Surface((40, 40))
        self.image.fill(RED)
        self.rect = self.image.get_rect(center=(WIDTH // 2, 100))
        self.health = 50

Pythonでのゲーム制作を学びたい人はこちらの書籍がおすすめです!

Amazon.co.jp: Pythonではじめるゲーム制作 超入門 知識ゼロからのプログラミング&アルゴリズムと数学 eBook : 廣瀬 豪: 本
Amazon.co.jp: Pythonではじめるゲーム制作 超入門 知識ゼロからのプログラミング&アルゴリズムと数学 eBook : 廣瀬 豪: 本

ステップ3: 移動と弾の発射

プレイヤーは上下左右に移動し、スペースキーで弾を発射できるようにします。

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(center=(x, y))
        self.speed = -8

ステップ4: ゲームループの構築

ゲームのメインループを作成し、プレイヤーの操作、敵の動き、弾の発射処理を実装します。

def game():
    player = Player()
    enemy = Enemy()
    bullets = pygame.sprite.Group()
    enemy_bullets = pygame.sprite.Group()
    running = True
    
    while running:
        screen.fill(BLACK)
        pygame.display.flip()
        clock.tick(60)

ステップ5: 敵の弾幕と当たり判定

敵が2秒ごとに同心円状の弾を発射し、プレイヤーと敵弾の衝突判定を行います。

class EnemyBullet(pygame.sprite.Sprite):
    def __init__(self, x, y, dx, dy):
        super().__init__()
        self.image = pygame.Surface((6, 6))
        self.image.fill(RED)
        self.rect = self.image.get_rect(center=(x, y))
        self.dx = dx
        self.dy = dy

ステップ6: 体力バーの描画

プレイヤーと敵の体力をバーとして表示する機能を追加します。

def draw_health_bar(surface, x, y, health, max_health):
    bar_length = 100
    bar_height = 10
    fill = (health / max_health) * bar_length
    pygame.draw.rect(surface, GREEN, pygame.Rect(x, y, fill, bar_height))

コード全体

import pygame
import random
import math

# 画面サイズ
WIDTH, HEIGHT = 480, 640

# 色定義
WHITE = (255, 255, 255)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
BLACK = (0, 0, 0)
GREEN = (0, 255, 0)

# 初期化
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("東方風シューティング")
clock = pygame.time.Clock()

# プレイヤークラス
class Player(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image = pygame.Surface((30, 30))
        self.image.fill(BLUE)
        self.rect = self.image.get_rect(center=(WIDTH // 2, HEIGHT - 50))
        self.speed = 5
        self.lives = 3

    def update(self, keys):
        if keys[pygame.K_LEFT] and self.rect.left > 0:
            self.rect.x -= self.speed
        if keys[pygame.K_RIGHT] and self.rect.right < WIDTH:
            self.rect.x += self.speed
        if keys[pygame.K_UP] and self.rect.top > 0:
            self.rect.y -= self.speed
        if keys[pygame.K_DOWN] and self.rect.bottom < HEIGHT:
            self.rect.y += self.speed

# 弾クラス
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(center=(x, y))
        self.speed = -8

    def update(self):
        self.rect.y += self.speed
        if self.rect.bottom < 0:
            self.kill()

# 敵クラス
class Enemy(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        self.image = pygame.Surface((40, 40))
        self.image.fill(RED)
        self.rect = self.image.get_rect(center=(WIDTH // 2, 100))
        self.speed = 3
        self.direction = 1
        self.health = 30
        self.shoot_timer = 0

    def update(self):
        self.rect.x += self.speed * self.direction
        if self.rect.left <= 0 or self.rect.right >= WIDTH:
            self.direction *= -1

    def shoot(self, enemy_bullets):
        for angle in range(0, 360, 30):
            radian = math.radians(angle)
            bullet = EnemyBullet(self.rect.centerx, self.rect.centery, math.cos(radian) * 3, math.sin(radian) * 3)
            enemy_bullets.add(bullet)

# 敵弾クラス
class EnemyBullet(pygame.sprite.Sprite):
    def __init__(self, x, y, dx, dy):
        super().__init__()
        self.image = pygame.Surface((6, 6))
        self.image.fill(RED)
        self.rect = self.image.get_rect(center=(x, y))
        self.dx = dx
        self.dy = dy

    def update(self):
        self.rect.x += self.dx
        self.rect.y += self.dy
        if self.rect.top > HEIGHT or self.rect.left < 0 or self.rect.right > WIDTH:
            self.kill()

# 体力バーを描画
def draw_health_bar(surface, x, y, health, max_health):
    bar_length = 100
    bar_height = 10
    fill = (health / max_health) * bar_length
    outline_rect = pygame.Rect(x, y, bar_length, bar_height)
    fill_rect = pygame.Rect(x, y, fill, bar_height)
    pygame.draw.rect(surface, GREEN, fill_rect)
    pygame.draw.rect(surface, WHITE, outline_rect, 2)

# ゲームループ
def game():
    player = Player()
    enemy = Enemy()
    bullets = pygame.sprite.Group()
    enemy_bullets = pygame.sprite.Group()
    all_sprites = pygame.sprite.Group(player, enemy)
    running = True
    last_shot_time = pygame.time.get_ticks()
    
    while running:
        screen.fill(BLACK)
        keys = pygame.key.get_pressed()
        
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                return
            if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE:
                bullet = Bullet(player.rect.centerx, player.rect.top)
                bullets.add(bullet)
                all_sprites.add(bullet)
        
        player.update(keys)
        enemy.update()
        bullets.update()
        enemy_bullets.update()
        
        # 敵の弾発射
        if pygame.time.get_ticks() - last_shot_time > 2000:
            enemy.shoot(enemy_bullets)
            last_shot_time = pygame.time.get_ticks()
        
        # 弾と敵の衝突判定
        for bullet in bullets:
            if enemy.rect.colliderect(bullet.rect):
                enemy.health -= 1
                bullet.kill()
                if enemy.health <= 0:
                    return "win"
        
        # プレイヤーと敵弾の衝突判定
        for bullet in enemy_bullets:
            if player.rect.colliderect(bullet.rect):
                player.lives -= 1
                bullet.kill()
                if player.lives <= 0:
                    return "lose"
        
        all_sprites.draw(screen)
        enemy_bullets.draw(screen)
        draw_health_bar(screen, 10, 10, player.lives, 3)
        draw_health_bar(screen, WIDTH - 110, 10, enemy.health, 50)
        pygame.display.flip()
        clock.tick(60)

# メインループ
def main():
    while True:
        screen.fill(BLACK)
        font = pygame.font.Font(None, 50)
        text = font.render("Press ENTER to Start", True, WHITE)
        screen.blit(text, (WIDTH//2 - text.get_width()//2, HEIGHT//2))
        pygame.display.flip()
        
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                return
            if event.type == pygame.KEYDOWN and event.key == pygame.K_RETURN:
                result = game()
                
                screen.fill(BLACK)
                if result == "win":
                    text = font.render("YOU WIN! Press ENTER to Restart", True, WHITE)
                else:
                    text = font.render("GAME OVER! Press ENTER to Restart", True, WHITE)
                screen.blit(text, (WIDTH//2 - text.get_width()//2, HEIGHT//2))
                pygame.display.flip()
                
                pygame.time.wait(2000)
                
if __name__ == "__main__":
    main()

プレイ画面

実際に制作したゲームをプレイする様子を撮影しましたのでご覧ください。

なお、制作していて気づいたのですが、敵の体力が50もあると時間が掛かりすぎるので、この動画では敵の体力を30に下方修正しています。


まとめ

さて、このチュートリアルでは、pygameを使った東方風シューティングゲームを作成する方法を学びました。このコードを改良することで、さらに複雑なゲームに挑戦できます。

Pythonでのゲーム制作ついて書籍でもっと学習したい方は、Kindleでの購入がおすすめです!

おすすめの書籍についてはこちらの記事で紹介しています!

ところで、ここでここまで記事を読んでいただいた皆さんにお知らせしなければならないことがあります…

実は、このプログラム、ChatGPTに全て書いてもらっているのです。

ChatGPTを使えば、今までエンジニアが数時間掛けて書いていたコードを、ものの3分で作成してくれます。

これからエンジニアとして活躍したい方、文系からエンジニアを目指したい方は、ぜひChatGPTの活用方法を学んで、高度なIT人材になりましょう!

ChatGPTを使ってPythonでゲームを制作したい方は、こちらのAIスクールがおすすめです!

AIで楽して稼ぐ副業スクール -イナズマ塾-
「副業のやり方がわからない」「時給が低い」そんな方々向けに、生成AIを使った副業の方法を0から学べる業界最安級のスクールです。eラーニング+コーチングで不労所得を実現する副業の方法を徹底解説します。

なお、当サイト限定の特典として、無料面談フォーム入力時に下記の招待コードを入力すると、受講料が20%OFFになります!!!

イナズマ塾に入塾を検討されている方は、ぜひ、こちらの割引をご利用ください!!

招待コード:STAIT2025