スプライト1

今回はスプライトを使用します.
スプライトはゲーム背景とは滅に動く画像(キャラクター, ボール,ブロック等)のことです.
スプライトを使用すると画像のバウンドや衝突の処理がカンタンにでき, ゲームを作成するのに必須となります.
また画像の処理をまとめて行うのでオブジェクト指向になります.
今まではひとつの画像のみの処理を書いていたので, 処理をClassでまとめます.

import pygame
from pygame.locals import *
import sys

SCR_RECT = Rect(0, 0, 1000, 700)

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()
        colorkey = self.image.get_at((0,0))  # 左上の色を透明色に
        self.image.set_colorkey(colorkey, RLEACCEL)
        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("スプライトの使い方")

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

    while True:
        clock.tick(60)  # 60fps
        backImg = pygame.image.load("hakusuiko.jpg").convert()
        screen.blit(backImg, (0,0))
        # スプライトを更新
        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関数とMySpriteクラスで成り立っています.
main関数は,

if __name__ == "__main__":
    main()

で呼び出します.
実行すると__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()
        colorkey = self.image.get_at((0,0))  # 左上の色を透明色に
        self.image.set_colorkey(colorkey, RLEACCEL)
        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__()を呼び出す必要があります.
コンストラクタは__init__()メソッドとして定義します。第一引数が self で、それぞれのインスタンスをさします。第二引数は自分で定義したパラメータが入ります.
Spriteでは3つの定義をする必要があります.
更新, 描写, 衝突判定をするためです.

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

今回は, filenameの画像をロードしてself.imageにセットしています.
その画像の余白を透明にする処理も行います.
次に画像の幅(width)と高さ(height)を設定します.
その画像のサイズをself.rectにセットします.
スプライトの場合は, biltでバウント処理と実装するのではなく,
update()は, バウンドの処理,
draw()によって実装します.
self.rectによって最初の位置が設定されています.

python1 = MySprite("kappa3.png", 0, 0, 3, 6)
python2 = MySprite("kappa3.png", 10, 10, 5, 5)
python3 = MySprite("kappa3.png", 320, 240, -2, 3)

ここで最初の位置と速度とベクトルの指定をしています,
MySpriteというクラスはオブジェクトの雛形です.
update(), draw()によって実装します.

キーイベント2

前回のキーイベントはキーを一回押すごとに上下左右に動きました.
今回は, キーを押している分だけ動作するプログラムです.
これはインベーダーゲームでビームを連続で発射するとき等に使うことができます.

import pygame
from pygame.locals import *
import sys

SCREEN_SIZE = (1000, 700)

pygame.init()
screen = pygame.display.set_mode(SCREEN_SIZE)
pygame.display.set_caption("キーイベント2")
backImg = pygame.image.load("hakusuiko.jpg").convert()
pythonImg2 = pygame.image.load("kappa3.png").convert()
colorkey = pythonImg2.get_at((0,0))  # 左上の色を透明色に
pythonImg2.set_colorkey(colorkey, RLEACCEL)
pythonImg2_rect = pythonImg2.get_rect()
pythonImg2_rect.center = (500, 350)

vx = vy = 1  # キーを押したときの移動距離

while True:
    screen.blit(backImg, (0,0))
    # 押されているキーをチェック
    pressed_keys = pygame.key.get_pressed()
    # 押されているキーに応じて画像を移動
    if pressed_keys[K_LEFT]:
        pythonImg2_rect.move_ip(-vx, 0)
    if pressed_keys[K_RIGHT]:
        pythonImg2_rect.move_ip(vx, 0)
    if pressed_keys[K_UP]:
        pythonImg2_rect.move_ip(0, -vy)
    if pressed_keys[K_DOWN]:
        pythonImg2_rect.move_ip(0, vy)


    screen.blit(pythonImg2,pythonImg2_rect)
    pygame.display.update()

    for event in pygame.event.get():
        if event.type == QUIT: sys.exit()
        if event.type == KEYDOWN:  # キーを押したとき
            # ESCキーならスクリプトを終了
            if event.key == K_ESCAPE:
                sys.exit()

これもwhile文から説明します.
pygame.key.get_pressed()関数はキーを押した話したの瞬間の動作ではなく, 関数が呼び出した時にどのキーが押されているか検出するものなので, 押されている間はずっと検出され指定した動作を続けることができます. また, この関数はどのキーが押されたかわかります.

get_pressed()のタプルをpressed_keyに格納して, pressed_keys[]を調べます.
押されてないときは0でFalse, 押されているときは1でTrueとなり, if文の条件分岐上で検出され動作します.

キーイベント1

マウスイベント同様にキーイベントについても説明します.

import pygame
from pygame.locals import *
import sys

SCREEN_SIZE = (1000,700)

pygame.init()
screen = pygame.display.set_mode(SCREEN_SIZE)
pygame.display.set_caption("キーイベント")

backImg = pygame.image.load("hakusuiko.jpg").convert()
pythonImg2 = pygame.image.load("kappa3.png").convert()
colorkey = pythonImg2.get_at((0,0))  # 左上の色を透明色に
pythonImg2.set_colorkey(colorkey, RLEACCEL)

pythonImg2_rect = pythonImg2.get_rect()
pythonImg2_rect.center = (500,350)

vx = vy = 10  # キーを押したときの移動距離

while True:
    screen.blit(backImg, (0,0))
    screen.blit(pythonImg2, pythonImg2_rect)
    pygame.display.update()

    for event in pygame.event.get():
        if event.type == QUIT: sys.exit()
        if event.type == KEYDOWN:  # キーを押したとき
            # ESCキーならスクリプトを終了
            if event.key == K_ESCAPE:
                sys.exit()
            # 矢印キーなら画像を移動
            if event.key == K_LEFT:
                pythonImg2_rect.move_ip(-vx, 0)
            if event.key == K_RIGHT:
                pythonImg2_rect.move_ip(vx, 0)
            if event.key == K_UP:
                pythonImg2_rect.move_ip(0, -vy)
            if event.key == K_DOWN:
                pythonImg2_rect.move_ip(0, vy)

while文からキーイベントの操作なので, そこから説明します.

for event in pygame.event.get():
        if event.type == QUIT: sys.exit()
        if event.type == KEYDOWN:  # キーを押したとき
            # ESCキーならスクリプトを終了
            if event.key == K_ESCAPE:
                sys.exit()
            # 矢印キーなら画像を移動
            if event.key == K_LEFT:
                pythonImg2_rect.move_ip(-vx, 0)
            if event.key == K_RIGHT:
                pythonImg2_rect.move_ip(vx, 0)
            if event.key == K_UP:
                pythonImg2_rect.move_ip(0, -vy)
            if event.key == K_DOWN:
                pythonImg2_rect.move_ip(0, vy)

最初は終了のイベントとして, QUITとESCAPEキーが押されたとき終了する設定です.
次にrect.move_ip()によって上下左右それぞれのキーが押された場合, 前に指定したピクセル分だけ動きます.

バウンド処理2

バウンド処理の違う方法について説明します.
バウンド時の速度の工夫をします.

import pygame
from pygame.locals import *
import sys

SCR_WIDTH,SCR_HEIGHT = 1000,700

pygame.init()
screen = pygame.display.set_mode((SCR_WIDTH,SCR_HEIGHT))
pygame.display.set_caption("画像の移動と跳ね返り処理2")

backImg = pygame.image.load("hakusuiko.jpg").convert()
pythonImg2 = pygame.image.load("kappa3.png").convert()
colorkey = pythonImg2.get_at((0,0))  # 左上の色を透明色に
pythonImg2.set_colorkey(colorkey, RLEACCEL)
pythonImg2_rect = pythonImg2.get_rect()

vx = vy = 120  # 1秒間の移動ピクセル
clock = pygame.time.Clock()

while True:
    screen.blit(backImg, (0,0))
    time_passed = clock.tick(60)  # 60fpsで前回からの経過時間を返す(ミリ秒)
    time_passed_seconds = time_passed / 1000.0  # ミリ秒を秒に変換

    # 画像の移動
    pythonImg2_rect.x += vx * time_passed_seconds
    pythonImg2_rect.y += vy * time_passed_seconds
    # 跳ね返り処理
    if pythonImg2_rect.left < 0 or pythonImg2_rect.right > SCR_WIDTH:
        vx = -vx
    if pythonImg2_rect.top < 0 or pythonImg2_rect.bottom > SCR_HEIGHT:
        vy = -vy


    screen.blit(pythonImg2, pythonImg2_rect)
    pygame.display.update()
    for event in pygame.event.get():
        if event.type == QUIT: sys.exit()
        if event.type == KEYDOWN and event.key == K_ESCAPE: sys.exit()

移動の処理だけ異なります.
バウンドすると速度が変わるのですが, その仕組みがまだ把握していないのでまた更新します.

バウンド処理1

キャラクターをウィンドウの中でバウンド処理をさせます.

import pygame
from pygame.locals import *
import sys
SCR_WIDTH,SCR_HEIGHT = (1000,700)

pygame.init()
screen = pygame.display.set_mode((SCR_WIDTH,SCR_HEIGHT))
pygame.display.set_caption("画像の移動と跳ね返り処理")

backImg = pygame.image.load("hakusuiko.jpg").convert()
pythonImg2 = pygame.image.load("kappa3.png").convert()
colorkey = pythonImg2.get_at((0,0))  # 左上の色を透明色に
pythonImg2.set_colorkey(colorkey, RLEACCEL)
pythonImg2_rect = pythonImg2.get_rect()

vx =vy =2# 1フレームの移動ピクセル
clock = pygame.time.Clock()

while True:
    screen.blit(backImg, (0,0))
    clock.tick(60)  # 60fps

    # 画像の移動
    pythonImg2_rect.move_ip(vx, vy)
    # 跳ね返り処理
    if pythonImg2_rect.left < 0 or pythonImg2_rect.right > SCR_WIDTH:
        vx = -vx
    if pythonImg2_rect.top < 0 or pythonImg2_rect.bottom > SCR_HEIGHT:
        vy = -vy


    screen.blit(pythonImg2,pythonImg2_rect)

    pygame.display.update()

    for event in pygame.event.get():
        if event.type == QUIT: sys.exit()
        if event.type == KEYDOWN and event.key == K_ESCAPE: sys.exit()

ウィンドウの大きさの設定が今までとは異なります.
左上を(0,0)とし右下までの大きさを設定します.
バウンド処理をさせる際に, 高さと幅の境界値によって判定させるためです.
境界値に達したらキャラクターの進むベクトルが変わる処理をしてあげます.

キャラクターが動いているように見えるためには, アニメのように少しずつ絵をずらして何枚も順に画面に表示させます.
この要領でプログラムも作成します.

vx = vy=2 # 1フレームの移動ピクセル
clock = pygame.time.Clock()

while True:
    screen.blit(backImg, (0,0))
    clock.tick(60)  # 60fps

    # 画像の移動
    pythonImg2_rect.move_ip(vx, vy)

画像の位置を少しだけ(vx,vy)ずらして, while文で繰り返します.
次に一秒間の画像の更新回数であるFPSの説明です.
pygameではFPSの設定が非常に簡単です.
pygame.time.Clockのtick()関数で設定できます.
今回は一秒間に60回繰り返します.

次に画像のバウンド処理について具体的に見ていきます.
先ほど, 移動ピクセルの設定をしました. この値を正の値にすれば右に進み, 負の値にすれば左に進みます.
x座標の動きでは, キャラクターの画像の左端が0より小さくなった場合もしくは画像の右端がSCR_WIDTHより大きくなったら画像が進むベクトルを逆にするようにします.
y座標も同様です.

マウスイベント2

もうひとつのマウスイベントをします.
pygame.mouseの関数を使用します.

pygame.mouse.get_pressed()

この関数を使うと, マウスのどのボタンが押されているか知ることができます.

#pygame.mouseを使用
import pygame
from pygame.locals import *
import sys

SCREEN_SIZE = (1000, 700)

pygame.init()
screen = pygame.display.set_mode(SCREEN_SIZE)
pygame.display.set_caption("マウスイベント2")

backImg = pygame.image.load("hakusuiko.jpg").convert()
pythonImg2 = pygame.image.load("kappa3.png").convert()
colorkey = pythonImg2.get_at((0,0))  # 左上の色を透明色に
pythonImg2.set_colorkey(colorkey, RLEACCEL)

cur_pos = (500,350)     # 蛇の位置
pythons_pos = []   # コピーした蛇の位置リスト

while True:
    screen.blit(backImg, (0,0))

    # マウスクリックで蛇をコピー
    mouse_pressed = pygame.mouse.get_pressed()
    if mouse_pressed[0]:  # 左クリック
        x, y = pygame.mouse.get_pos()
        x -= pythonImg2.get_width() / 2
        y -= pythonImg2.get_height() / 2
        pythons_pos.append((x,y))  # 蛇の位置を追加

    # マウス移動で蛇を移動
    x, y = pygame.mouse.get_pos()
    x -= pythonImg2.get_width() / 2
    y -= pythonImg2.get_height() / 2
    cur_pos = (x,y)

    # 蛇を表示
    screen.blit(pythonImg2, cur_pos)
    for i, j in pythons_pos:
        screen.blit(pythonImg2, (i,j))
    pygame.display.update()

    for event in pygame.event.get():
        if event.type == QUIT:
            sys.exit()
pygame.mouse.get_pos()

この関数で押している時の位置情報を常にx,yに代入させます.

マウスイベント1

マウスという入力インターフェースを用いて, クリック時のプリントの実装します.
Pygameでマウス入力を検出する方法は, 2つあります.
今回は1つ目のイベントハンドラでマウスイベントを補足する方法です.
イベントハンドラは今までQUITイベントで使っていました.
つまりクリック時の処理の設定です.

import pygame
from pygame.locals import *
import sys

SCREEN_SIZE =  (1000, 700)

pygame.init()
screen = pygame.display.set_mode(SCREEN_SIZE)
pygame.display.set_caption("マウスイベント")

backImg = pygame.image.load("hakusuiko.jpg").convert()
pythonImg2 = pygame.image.load("kappa3.png").convert()
colorkey = pythonImg2.get_at((0,0))  # 左上の色を透明色に
pythonImg2.set_colorkey(colorkey, RLEACCEL)

cur_pos = (500,350)    # カッパの位置
pythons_pos = []   # コピーしたカッパのリスト
while True:
    screen.blit(backImg, (0,0))

    for event in pygame.event.get():
        if event.type == QUIT:
            sys.exit()
        # マウスクリックでカッパをコピー
        if event.type == MOUSEBUTTONDOWN and event.button == 1:
            x, y = event.pos
            x -= pythonImg2.get_width() / 2
            y -= pythonImg2.get_height() / 2
            pythons_pos.append((x,y))  # カッパの位置を追加
        # マウス移動でカッパを移動
        if event.type == MOUSEMOTION:
            x, y = event.pos
            x -= pythonImg2.get_width() / 2
            y -= pythonImg2.get_height() / 2
            cur_pos = (x,y)

    # カッパを表示
    screen.blit(pythonImg2, cur_pos)
    for i, j in pythons_pos:
        screen.blit(pythonImg2, (i,j))
    pygame.display.update()

上の部分は今までと変わりません.

cur_pos = (500,350)    # カッパの位置
pythons_pos = []   # コピーしたカッパのリスト

これで最初のカッパの位置と, マウスイベントを起こした時のカッパの位置の登録を行います.

これからクリック時の挙動イベントハンドラのプログラムです.
イベントハンドラは今まで, QUITイベントが来た時にスクリプトを終了していただけですが, 他にもたくさんのことに応用できます.
イベントには3種類あります.
一つ目に MOUSEBUTTONDOWN です.
マウスが押された時に一回だけ発生します. 長押ししても一回しか発生しません.
二つ目に MOUSEBUTTONUP です.
これはマウスが押されて離れた時に一回だけ発生します.
最後に MOUSEMOTION です.
マウスカーソルを移動させた時に発生します.
前の2つのイベントよりも大量に発生させることができます.
実際に見てみます.

for event in pygame.event.get():
        if event.type == QUIT:
            sys.exit()
        # マウスクリックでカッパをコピー
        if event.type == MOUSEBUTTONDOWN and event.button == 1:
            x, y = event.pos
            x -= pythonImg2.get_width() / 2
            y -= pythonImg2.get_height() / 2
            pythons_pos.append((x,y))  # カッパの位置を追加
        # マウス移動でカッパを移動
        if event.type == MOUSEMOTION:
            x, y = event.pos
            x -= pythonImg2.get_width() / 2
            y -= pythonImg2.get_height() / 2
            cur_pos = (x,y)

まず最初はQUITの操作です.
QUITが押されたら終了. これは前と変わりません.
次に, MOUSEBUTTONDOWN なのでクリック時に発生します.
どのイベントが発生したのかはEventオブジェクトのtypeで判断できます
またボタンの種類なのですが今回は1となっています.
数字はマウスのボタンの箇所を表していて, 1は左クリック,2は中クリック, 3は右クリックを表します.
posはマウスイベントが発生した座標です.
位置の追加をしていくことによって, 一番下のプログラムで位置に沿って表示させます.
次は MOUSEMOTION なので, 矢印ポインタに合わせて設定したキャラクターが映っています.

最後に表示させるプログラムです.

screen.blit(pythonImg2, cur_pos)
    for i, j in pythons_pos:
        screen.blit(pythonImg2, (i,j))
    pygame.display.update()

カッパの位置に沿って画像を貼っていきます.
最後にウィンドウの更新をします.