【Pygame】スプライトの使い方とグループ化による効率的な管理方法

Pygameのスプライト機能を用いて、複数の画像オブジェクトを効率よく管理する方法とソースコードを解説します。

【1】スプライトの基本的な使い方

Pygameのスプライトは、ゲーム内のグラフィカルな要素(プレイヤーや敵など)を管理するための非常に便利な機能です。スプライトを使用することで、例えば以下のように複数のゲームオブジェクトの描画、更新、衝突判定などを効率的に管理できます。

【今回使用する画像】

Pygameでスプライトを扱うには、以下のようにpygame.sprite.Spriteクラスを継承します。

import pygame

class Player(pygame.sprite.Sprite):
    def __init__(self, image_path, pos):
        super().__init__()
        self.image = pygame.image.load(image_path).convert_alpha()
        self.rect = self.image.get_rect(topleft=pos)
    
    # update()`メソッドを使用して、オブジェクトの状態を更新します。
    # 通常、位置の変更やアニメーションの更新などを行います。
    def update(self):
        # ここに更新ロジックを追加
        # オブジェクトの画像を画面上に描画
        screen.blit(self.image, self.rect)

動画解説版

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

サンプルプログラムのソースコードです。5人の女の子が動き回ります。


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

このコードは、Pygameを使ってスプライト(画像)を画面上で動かすシンプルなプログラムです。以下に各部分の詳細な解説を行います。

インポートと初期設定

# -*- coding: utf-8 -*-
import sys
import pygame
from pygame.locals import *
  • sysモジュールをインポートしています。
  • pygameモジュールとそのローカル定数をインポートしています。

画面サイズと画像ファイルパスの設定

SCREEN = Rect(0, 0, 600, 400)   # 画面サイズ

GIRL1_IMG_PATH = "/Users/github/sample/python/pygame/sprite/girl1.png"
GIRL2_IMG_PATH = "/Users/github/sample/python/pygame/sprite/girl2.png"
GIRL3_IMG_PATH = "/Users/github/sample/python/pygame/sprite/girl3.png"
GIRL4_IMG_PATH = "/Users/github/sample/python/pygame/sprite/girl4.png"
GIRL5_IMG_PATH = "/Users/github/sample/python/pygame/sprite/girl5.png"
  • SCREENは画面のサイズを設定するための矩形オブジェクトです。
  • 各スプライト画像のファイルパスを定義しています。

クラスの定義と初期化(スプライト)

Pygameを使ってスプライト(画像オブジェクト)を管理するクラス Girl を定義しています。

class Girl(pygame.sprite.Sprite):
    def __init__(self, filepath, pos, vxy, angle=0):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load(filepath).convert_alpha()
        if angle != 0: 
            self.image = pygame.transform.rotate(self.image, angle)
  • Girl クラスは pygame.sprite.Sprite を継承しています。これにより、Pygameのスプライト機能を利用できます。
  • __init__ メソッドは、スプライトの初期化を行います。
    • filepath は画像ファイルのパスです。
    • pos はスプライトの初期位置(x, y)です。
    • vxy はスプライトの移動速度(vx, vy)です。
    • angle はスプライトの回転角度です(デフォルトは0)。
  • self.image に画像を読み込み、必要に応じて回転させます。

矩形オブジェクトの作成

        x, y = pos
        vx, vy = vxy
        w = self.image.get_width()
        h = self.image.get_height()
        self.rect = Rect(x, y, w, h)
  • pos から位置を、vxy から速度を取得します。
  • 画像の幅 (w) と高さ (h) を取得し、Rect オブジェクトを作成します。Rect はスプライトの位置とサイズを管理します。

移動速度と角度の設定

        self.vx = vx
        self.vy = vy
        self.angle = angle
  • スプライトの移動速度 (vx, vy) と回転角度 (angle) を設定します。

更新メソッド

    def update(self):
        self.rect.move_ip(self.vx, self.vy)
        if self.rect.left < 0 or self.rect.right > SCREEN.width:
            self.vx = -self.vx
        if self.rect.top < 0 or self.rect.bottom > SCREEN.height:
            self.vy = -self.vy
        self.rect = self.rect.clamp(SCREEN)
  • update メソッドはスプライトの位置を更新します。
    • move_ip メソッドでスプライトを移動させます。
    • スプライトが画面の端に達した場合、速度の符号を反転させて跳ね返ります。
    • clamp メソッドでスプライトが画面外に出ないようにします。

描画メソッド

    def draw(self, screen):
        screen.blit(self.image, self.rect)
  • draw メソッドはスプライトを画面に描画します。
    • screen.blit メソッドで画像を指定された位置 (self.rect) に描画します。

メイン関数

def main():
    pygame.init()
    screen = pygame.display.set_mode(SCREEN.size)

    # スプライトの作成
    girl1 = Girl(GIRL1_IMG_PATH,(200, 200), (2, 0), 0)
    girl2 = Girl(GIRL2_IMG_PATH,(200, 200), (0, 2), -20)
    girl3 = Girl(GIRL3_IMG_PATH,(200, 200), (2, 3), 0)
    girl4 = Girl(GIRL4_IMG_PATH,(200, 200), (1, 2), 20)
    girl5 = Girl(GIRL5_IMG_PATH,(200, 200), (2, 1), 0)

    clock = pygame.time.Clock()
    running = True

    while running:
        clock.tick(30)
        screen.fill((0, 20, 0)) 

        # 各スプライトの更新
        girl1.update()
        girl2.update()
        girl3.update()
        girl4.update()
        girl5.update()

        # 各スプライトの描画
        girl1.draw(screen)
        girl2.draw(screen)
        girl3.draw(screen)
        girl4.draw(screen)
        girl5.draw(screen)

        pygame.display.update()

        # イベント処理
        for event in pygame.event.get():
            if event.type == QUIT: 
                running = False
            if event.type == KEYDOWN:
                if event.key == K_ESCAPE:   
                    running = False

if __name__ == "__main__":
    main()
  • main関数でPygameを初期化し、画面を設定します。
  • Girlクラスのインスタンスを作成し、それぞれ異なる画像、位置、速度、角度を設定します。
  • メインループ内でフレームレートを設定し、画面を更新します。
  • updateメソッドで各スプライトの位置を更新し、drawメソッドで画面に描画します。
  • イベント処理でウィンドウの閉じるボタンやEscキーが押された場合にループを終了します。

【2】スプライトのグループ化

スプライトをグループ化すると、複数のスプライトを効率的に管理できます。
グループ化する方法はいくつかありますが、今回はpygame.sprite.RenderUpdates()を使う方法を解説します。
pygame.sprite.RenderUpdates()は、Pygameでスプライトの描画を効率的に管理するためのクラスです。このクラスは、スプライトの更新と描画を行う際に、変更された部分だけを再描画することでパフォーマンスを向上させます。

基本的な使い方

  1. スプライトグループの作成
    group = pygame.sprite.RenderUpdates()
    
    • RenderUpdatesクラスのインスタンスを作成します。このグループにスプライトを追加することで、効率的な描画が可能になります。
  2. スプライトの追加
    group.add(sprite)
    
    • 作成したスプライトをグループに追加します。複数のスプライトを一度に追加することもできます。
  3. スプライトの更新
    group.update()
    
    • グループ内のすべてのスプライトの位置や状態を更新します。各スプライトのupdateメソッドが呼び出されます。
  4. スプライトの描画
    dirty_rects = group.draw(screen)
    pygame.display.update(dirty_rects)
    
    • グループ内のすべてのスプライトを画面に描画し、変更された領域(矩形)のリストを返します。このリストを使って、画面の更新を効率的に行います。 この手法は、画面更新時、全体を描き換えずにスプライトが更新された一部分だを描き換えることでパフォーマンスを向上させます。全体を書き換えたい場合は「pygame.display.update()」とします(dirty_rectsを与えない)。

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

サンプルコード①をpygame.sprite.RenderUpdates()でスプライトをグループ化すると以下のようになります。


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

サンプルコード①と異なる部分のみ解説します。

スプライトグループの作成と追加

    group = pygame.sprite.RenderUpdates()
    group.add(girl1)
    group.add(girl2)
    group.add(girl3)
    group.add(girl4)
    group.add(girl5)
  • pygame.sprite.RenderUpdates():スプライトを管理するグループを作成します。
  • group.add(...):作成したスプライトをグループに追加します。

メインループ

    running = True

    while running:
        clock.tick(30)
        screen.fill((0, 20, 0)) 
        group.update()
        # スプライトグループを描画
        dirty_rects = group.draw(screen)

        # 画面更新
        pygame.display.update(dirty_rects)
  • group.update():スプライトグループの位置を更新します。
  • group.draw(screen):スプライトグループを画面に描画します。変数dirty_rectsには、変更された領域(矩形)のリストが格納されます。
  • リストdirty_rectspygame.display.updateに渡し、変更された部分だけを再描画することで、画面の更新を効率的に行います。

【3-1】スプライトのグループ化 その2

Pygameでスプライトグループ化するとき、pygame.sprite.Sprite.__init__(self, self.containers)を使う方法があります。
これは、Pygameのスプライトクラスの初期化メソッドに、スプライトが所属するグループを指定するためのものです。これにより、スプライトを作成する際に自動的に指定されたグループに追加されます
この方法のメリットは以下のとおりです。

  • コードの簡潔化:スプライトを作成するたびにgroup.add(…)`でグループに追加する必要がなくなります。
  • 一貫性の確保:スプライトが常に指定されたグループに追加されるため、管理が容易になります。

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

サンプルコード②をpygame.sprite.Sprite.__init__(self, self.containers)でスプライトをグループ化すると以下のようになります。


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

サンプルコード②との変更箇所を解説します。

1. スプライトの初期化

Girlクラスの初期メソッドの以下の箇所です。

pygame.sprite.Sprite.__init__(self, self.containers)
  • pygame.sprite.Sprite.__init__(self, self.containers)は、スプライトクラスの親クラスであるpygame.sprite.Spriteの初期化メソッドを呼び出します。
  • self.containersは、スプライトが所属するグループを指定するための引数です。この引数には、スプライトグループ(例えばpygame.sprite.Grouppygame.sprite.RenderUpdates)が渡されます。

2. スプライトグループの設定

# スプライトグループの作成
girl_group = pygame.sprite.RenderUpdates()

# Girlクラスにスプライトグループを割り当てる
Girl.containers = girl_group
  • girl_group = pygame.sprite.RenderUpdates():スプライトグループを作成します。
  • Girl.containers = girl_groupGirlクラスのcontainers属性に、作成したスプライトグループ(girl_group)を割り当てます。これにより、Girlクラスのインスタンスが作成されると、自動的にこのグループに追加されます。

3. スプライトの作成と追加

girl1 = Girl(GIRL1_IMG_PATH, (200, 200), (2, 0), 0)
  • girl1 = Girl(GIRL1_IMG_PATH, (200, 200), (2, 0), 0)Girlクラスのインスタンスを作成します。このとき、__init__メソッド内でpygame.sprite.Sprite.__init__(self, self.containers)が呼び出され、girl1は自動的にgirl_groupに追加されます。

Pygameでスプライト同士の衝突判定と跳ね返りを実装する方法をソースコード付きで詳しく解説します。

【4】スプライト同士の衝突判定と跳ね返り

今のままだと、画像オブジェクト同士の衝突時、そのまますり抜けてしまいます。
Pygameでは、self.rect.colliderect を使ってスプライト同士の衝突判定と跳ね返りを実装できます。

self.rect.colliderect(other_rect) は、2つの矩形(Rect オブジェクト)が衝突しているかどうかを判定するメソッドです。
衝突している場合は True を返し、そうでない場合は False を返します。

【4-1】サンプルコード④

5つの画像オブジェクトが衝突したら跳ね返るサンプルコードです。


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

上記コードのうち、衝突判定と跳ね返りを実装している部分は以下のとおりです。

def update(self):
    self.rect.move_ip(self.vx, self.vy)
    if self.rect.left < 0 or self.rect.right > SCREEN.width:
        self.vx = -self.vx
    if self.rect.top < 0 or self.rect.bottom > SCREEN.height:
        self.vy = -self.vy
    self.rect = self.rect.clamp(SCREEN)

    for sprite in self.containers:
        if sprite != self and self.rect.colliderect(sprite.rect):
            self.vx = -self.vx
            self.vy = -self.vy
  • update メソッドでは、スプライトの位置を更新し、画面の端に達した場合や他のスプライトと衝突した場合に速度を反転させます。つまり、スプライトは画面の端や他のスプライトに衝突すると跳ね返る動きをします。
  • self.rect.move_ip(self.vx, self.vy):スプライトの位置を現在の速度(vx, vy)に基づいて更新します。
  • if self.rect.left < 0 or self.rect.right > SCREEN.width:
    • スプライトが画面の左端または右端に達した場合、vxの符号を反転させて反射させます。
  • if self.rect.top < 0 or self.rect.bottom > SCREEN.height:
    • スプライトが画面の上端または下端に達した場合、vyの符号を反転させて反射させます。
  • self.rect = self.rect.clamp(SCREEN):
    • スプライトの位置を画面内に制限します。
  • 他のスプライトとの衝突処理:
    • for sprite in self.containers:で同じコンテナ内の他のスプライトをループします。つまり、 self.containers 内の他のスプライトをループし、自分自身以外のスプライトと衝突しているかを self.rect.colliderect(sprite.rect) で判定します。
    • if sprite != self and self.rect.colliderect(sprite.rect):自分自身以外のスプライトと衝突した場合、vxvyの符号を反転させて反射させます。
【Pygame】スプライト同士の衝突判定と跳ね返りを実装する方法
Pygameでスプライト同士の衝突判定と跳ね返りを実装する方法をソースコード付きで詳しく解説します。

関連ページ

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

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

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

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

コメント

  1. 匿名 より:

    11行目のところが構文が違うと返されます。

    • 管理人 より:

      ※匿名様
      コメントありがとうございます。
      Python3だとエラーが出るようなので修正いたしました。

  2. 匿名 より:

    変更ありがとうございます。
    変更したあとのものを実行すると下のエラーが起きます。
    ‘Sprite’ object has no attribute ‘img’
    これはpyhotn3にはないのでしょうか?

    • 管理人 より:

      ※匿名様
      コメントありがとうございます。
      当方の環境でも同様のエラーが出ましたので修正致しました。