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

人工知能に関する断創録

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



キャラクターアニメーション

Pygame

キャラクターアニメーションの作り方を紹介します。画像の移動と跳ね返り処理(2008/5/9)で紹介したような位置が変わるアニメーションではなく、こういうやつ

f:id:aidiary:20100605231252g:plain

です。他にもシューティングの爆発なども同じ方法でできます。画像はDotWorldさん(リンク切れ)からお借りしました。RPGでもぜひ使わせてもらおうと思ってます。

chara_anime.zip
f:id:aidiary:20100605231253p:plain

サンプルスクリプト

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import pygame
from pygame.locals import *
import sys
 
SCR_RECT = Rect(0, 0, 640, 480)
 
def load_image(filename, colorkey=None):
    try:
        image = pygame.image.load(filename)
    except pygame.error, message:
        print "Cannot load image:", filename
        raise SystemExit, message
    image = image.convert()
    if colorkey is not None:
        if colorkey is -1:
            colorkey = image.get_at((0,0))
        image.set_colorkey(colorkey, RLEACCEL)
    return image
 
def split_image(image):
    """32x128のキャラクターイメージを32x32の4枚のイメージに分割
    分割したイメージを格納したリストを返す"""
    imageList = []
    for i in range(0, 128, 32):
        surface = pygame.Surface((32,32))
        surface.blit(image, (0,0), (i,0,32,32))
        surface.set_colorkey(surface.get_at((0,0)), RLEACCEL)
        surface.convert()
        imageList.append(surface)
    return imageList
 
class Character(pygame.sprite.Sprite):
    animcycle = 12  # アニメーション速度
    frame = 0
    def __init__(self, filename, x, y):
        pygame.sprite.Sprite.__init__(self, self.containers)
        self.images = split_image(load_image(filename))
        self.image = self.images[0]
        self.rect = self.image.get_rect(topleft=(x,y))
    def update(self):
        # キャラクターアニメーション
        self.frame += 1
        self.image = self.images[self.frame/self.animcycle%4]
 
def main():
    pygame.init()
    screen = pygame.display.set_mode(SCR_RECT.size)
    pygame.display.set_caption(u"キャラクターアニメーション")
 
    all = pygame.sprite.RenderUpdates()
    Character.containers = all
    
    player = Character("player4.png", 0, 0)
    king = Character("king4.png", 32, 0)
    soldier = Character("soldier4.png", 64, 0)
    
    clock = pygame.time.Clock()
    while True:
        clock.tick(60)
        screen.fill((0,0,255))
        all.update()
        all.draw(screen)
        pygame.display.update()
        for event in pygame.event.get():
            if event.type == QUIT:
                sys.exit()
 
if __name__ == "__main__":
    main()

イメージの分割

画像の移動と跳ね返り処理(2008/5/9)でも解説しましたが、アニメーションはちょっとずつ違った画像を連続して表示することで動いているように見せかけてます。たとえば、

f:id:aidiary:20100605231252g:plain

も下の4つのキャラクターを連続して表示することで作っています。

f:id:aidiary:20100605231254p:plain

まず上のように複数の画像が描かれた一枚絵をばらして4つのSurfaceを作る関数から作ります。下のは 32x128ピクセルのイメージを読み込み、32x32の4枚のイメージに分割し、分割したイメージを格納したリストを返す関数です。32x32の空の Surfaceを作成してそこに一枚絵の一部を描画して作っています。

def split_image(image):
    """32x128のキャラクターイメージを32x32の4枚のイメージに分割
    分割したイメージを格納したリストを返す"""
    imageList = []
    for i in range(0, 128, 32):
        surface = pygame.Surface((32,32))
        surface.blit(image, (0,0), (i,0,32,32))
        surface.set_colorkey(surface.get_at((0,0)), RLEACCEL)
        surface.convert()
        imageList.append(surface)
    return imageList

この関数は下のように使います。

# 分割したイメージをロード
self.images = split_image(load_image(filename))

load_image()はイメージを描画する(2008/5/5)で作成した関数です。load_image()で読み込んだ Surfaceをsplit_image()に渡して4つのSurfaceに分割するわけです。

キャラクターアニメーション

    animcycle = 12  # アニメーション速度
    frame = 0

    def update(self):
        # キャラクターアニメーション
        self.frame += 1
        self.image = self.images[self.frame/self.animcycle%4]

キャラクターアニメーションは、読み込んだ4つの画像をフレームごとに次々に切り替えて表示することで実現できます。まずanimcycleでアニメーション速度を定義します。これは、1つの画像を何フレーム表示するかを表しています。たとえば、下の例では12なので12フレームは同じ画像が表示され、12フレーム経過すると次の画像に切り替わります。当然、値が小さいほど速いアニメーションになります。何フレームたったか知るためにframeという変数を用意し、update()で更新しています。このframeの値を用いて表示する画像を選択するわけです。今回、一番重要なのは

        self.image = self.images[self.frame/self.animcycle%4]

です。これは、分割した画像のリストから表示する画像を選択しています。Spriteはself.imageにセットした画像が描画されるのを思い出してください。スプライトの使い方(2008/5/17)。ここでは、self.frameの値によって描画する画像を変えているのがわかりますね。問題は、

 self.frame/self.animcycle%4

が何を意味しているかですが、実際に下のように計算してみると案外簡単にわかります。ヒントを言うと%4の4はアニメーションに使う画像がいくつあるかを表しています。

  # 0〜11フレームの12フレーム間は0番目の画像を描画
  frame=0     → 0/12=0  → 0%4=0
  frame=1     → 1/12=0  → 0%4=0
  ...
  frame=11   → 11/12=0  → 0%4=0

  # 12〜23フレームの12フレーム間は1番目の画像を描画
  frame=12   → 12/12=1  → 1%4=1
  frame=13   → 13/12=1  → 1%4=1
  ..
  frame=23   → 23/12=1  → 1%4=1

  # 24〜35フレームの12フレーム間は2番目の画像を描画
  frame=24   → 24/12=2  → 2%4=2
  ...
  frame=35   → 35/12=2  → 2%4=2

  # 36〜47フレームの12フレーム間は3番目の画像を描画
  frame=36   → 36/12=3  → 3%4=3
  ...
  frame=47   → 47/12=3  → 3%4=3

  # 48〜59フレームの12フレーム間は最初に戻って0番目の画像を描画
  frame=48    → 48/12=4  → 4%4=0
  ...
  frame=59    → 59/12=4  → 4%4=0