読者です 読者をやめる 読者になる 読者になる

人工知能に関する断創録

人工知能、認知科学、心理学、ロボティクス、生物学などに興味を持っています。このブログでは人工知能のさまざまな分野について調査したことをまとめています。最近は、機械学習・Deep Learningに関する記事が多いです。



スプライトの使い方

Pygame

スプライトとは、おいしい炭酸飲料ではなくて、ゲーム背景とは別に動く画像を指す言葉です。たとえば、RPGのキャラクター、ブロック崩しのブロック、ボール、シューティングの飛行機、ミサイル、爆弾などは全部スプライトです。Pygameにはスプライトを表す便利なモジュールpygame.spriteがあります。pygame.spriteを使うとスプライトの管理、描画、衝突判定が簡単にできます。画像の移動と跳ね返り処理(2008/5/9)のパイソンも定義的にはスプライトなのですがただの画像でした。ここでは、単なる画像ではなく、pygame.spriteを使って実装してみます。

ここから、オブジェクト指向を使うのでPythonのオブジェクト指向の知識がないと少しわかりにくいかもしれません。もし、class、 self、__init__などの構文の意味がまったくわからないならば、Pythonの入門書かチュートリアルサイトを読むことをおすすめします。

sprite_test.zip
f:id:aidiary:20100605101605p:plain

サンプルスクリプト

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import pygame
from pygame.locals import *
import sys
 
SCR_RECT = Rect(0, 0, 640, 480)
 
class MySprite(pygame.sprite.Sprite):
    def __init__(self, filename, x, y, vx, vy):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load(filename).convert_alpha()
        width = self.image.get_width()
        height = self.image.get_height()
        self.rect = Rect(x, y, width, height)
        self.vx = vx
        self.vy = vy
        
    def update(self):
        self.rect.move_ip(self.vx, self.vy)
        # 壁にぶつかったら跳ね返る
        if self.rect.left < 0 or self.rect.right > SCR_RECT.width:
            self.vx = -self.vx
        if self.rect.top < 0 or self.rect.bottom > SCR_RECT.height:
            self.vy = -self.vy
        # 画面からはみ出ないようにする
        self.rect = self.rect.clamp(SCR_RECT)
    
    def draw(self, screen):
        screen.blit(self.image, self.rect)
 
def main():
    pygame.init()
    screen = pygame.display.set_mode(SCR_RECT.size)
    pygame.display.set_caption(u"スプライトの使い方")
    
    # スプライトを作成
    python1 = MySprite("python.png", 0, 0, 2, 2)
    python2 = MySprite("python.png", 10, 10, 5, 5)
    python3 = MySprite("python.png", 320, 240, -2, 3)
    
    clock = pygame.time.Clock()
    
    while True:
        clock.tick(60)  # 60fps
        
        screen.fill((0,0,255))
        
        # スプライトを更新
        python1.update()
        python2.update()
        python3.update()
        
        # スプライトを描画
        python1.draw(screen)
        python2.draw(screen)
        python3.draw(screen)
        
        pygame.display.update()
        
        for event in pygame.event.get():
            if event.type == QUIT:
                sys.exit()
 
if __name__ == "__main__":
    main()

main関数の定義

このスクリプトは、1つのクラスとmain関数から成り立っています。今までPygameの初期化などはグローバル領域に書いていたのですが、 main関数にまとめてみました。こっちの方が一般的な書き方です。main関数は、

if __name__ == "__main__":
    main()

で呼び出されます。これはPythonの特殊な書き方ですが頻繁に出てきます。スクリプトをpythonコマンドで実行したり、ダブルクリックで実行すると__name__に__main__という文字列が代入されます。if文がTrueになり、main()が自動的に始まります。

スプライトクラス

class MySprite(pygame.sprite.Sprite):
    def __init__(self, filename, x, y, vx, vy):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load(filename).convert_alpha()
        width = self.image.get_width()
        height = self.image.get_height()
        self.rect = Rect(x, y, width, height)
        self.vx = vx
        self.vy = vy
    def update(self):
        self.rect.move_ip(self.vx, self.vy)
        # 壁にぶつかったら跳ね返る
        if self.rect.left < 0 or self.rect.right > SCR_RECT.width:
            self.vx = -self.vx
        if self.rect.top < 0 or self.rect.bottom > SCR_RECT.height:
            self.vy = -self.vy
        # 画面からはみ出ないようにする
        self.rect = self.rect.clamp(SCR_RECT)
    def draw(self, screen):
        screen.blit(self.image, self.rect)

スプライトクラスは、pygame.sprite.Spriteです。オリジナルのスプライトを作りたいときはこのクラスを継承して作ります。サンプルスクリプトでは、Spriteを継承してMySpriteというオリジナルスプライトクラスを実装しています。Spriteを継承する場合は、__init__()(コンストラクタ)でpygame.sprite.Sprite.__init__()を呼び出す必要があります。

Spriteでは、以下の3つを定義(オーバーライド)する必要があります。Spriteはこれらの情報を用いてスプライトの更新、描画、衝突判定を行うからです。

  • self.image(スプライトの画像)
  • self.rect(スプライトの位置とサイズを表すRect)
  • update()(スプライトの1フレームでの更新処理)

例では、filenameの画像をロードしてself.imageにセットしています。スプライトの位置は(x,y)、サイズは画像のサイズを取得しself.rectにセットしています。update()は、スプライトを移動させ、壁にぶつかったら跳ね返る処理です。これは画像の移動と跳ね返り処理(2008/5/9)で解説したのと同じです。サンプルでは、描画関数 draw()も実装していますが、これはスプライトグループを使うと必要なくなります。詳しくはスプライトグループの使い方(2008/5/17)を見てください。

スプライトの使い方

    # スプライトを作成
    python1 = MySprite("python.png", 0, 0, 2, 2)
    python2 = MySprite("python.png", 10, 10, 5, 5)
    python3 = MySprite("python.png", 320, 240, -2, 3)

ここでは、座標と速度を変えて3つのパイソンオブジェクトを作成しています。MySpriteというクラスはオブジェクトのひな型です。このようにクラスからはたくさんオブジェクトを簡単に作り出すことができます。これは、オブジェクト指向の大きな利点です。

        # スプライトを更新
        python1.update()
        python2.update()
        python3.update()
        
        # スプライトを描画
        python1.draw(screen)
        python2.draw(screen)
        python3.draw(screen)

ゲームループでは、3つのスプライトのupdate()を呼び出して位置を更新し、draw()を呼び出して描画しています。実行してみるとパイソンが3匹画面内を跳ね回ってますね!

上記のスクリプトを見ていて気がついたかもしれませんが、パイソンが3匹ならまだしも100匹作りたいって場合は非常にめんどくさいことになります。これは次に説明するスプライトグループを使うことで解消されます。スプライトグループを使うとたくさんのスプライトをまとめて更新、描画できるようになります。