人工知能に関する断創録

このブログでは人工知能のさまざまな分野について調査したことをまとめています(更新停止: 2019年12月31日)

足踏みさせる

前回のプレイヤーは移動はできますが足踏みしてませんね。足踏みってのは言うまでもないと思いますが

f:id:aidiary:20100605231252g:plain

のことです。こういう足踏みはキャラクターアニメーションって言います。基礎編のキャラクターアニメーション(2008/5/18)でも詳しく解説しているので参考にしてください。

pyrpg05.zip
f:id:aidiary:20100606103042p:plain

サンプルスクリプト

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import pygame
from pygame.locals import *
import sys
import os
 
SCR_RECT = Rect(0, 0, 640, 480)
ROW,COL = 15,20
GS = 32
map = [[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
       [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
       [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
       [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
       [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
       [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
       [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
       [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
       [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
       [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
       [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
       [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
       [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
       [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],
       [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]]
 
def load_image(filename, colorkey=None):
    filename = os.path.join("data", filename)
    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, GS):
        surface = pygame.Surface((GS,GS))
        surface.blit(image, (0,0), (i,0,GS,GS))
        surface.set_colorkey(surface.get_at((0,0)), RLEACCEL)
        surface.convert()
        imageList.append(surface)
    return imageList
 
def draw_map(screen):
    """マップを描画する"""
    for r in range(ROW):
        for c in range(COL):
            if map[r][c] == 0:
                screen.blit(grassImg, (c*GS,r*GS))
            elif map[r][c] == 1:
                screen.blit(waterImg, (c*GS,r*GS))
 
def is_movable(x, y):
    """(x,y)は移動可能か?"""
    # マップ範囲内か?
    if x < 0 or x > COL-1 or y < 0 or y > ROW-1:
        return False
    # マップチップは移動可能か?
    if map[y][x] == 1:  # 水は移動できない
        return False
    return True
 
pygame.init()
screen = pygame.display.set_mode(SCR_RECT.size)
pygame.display.set_caption(u"PyRPG 05 足踏みさせる")
 
# イメージロード
playerImgList = split_image(load_image("player4.png"))  # プレイヤー
grassImg = load_image("grass.png")         # 草地
waterImg = load_image("water.png")         # 水
 
x,y = 1,1  # プレイヤーの位置(単位:マス)
animcycle = 24  # アニメーション速度
frame = 0
 
clock = pygame.time.Clock()
 
while True:
    clock.tick(60)
    
    # 経過フレーム数に応じて表示する画像を変える
    frame += 1
    playerImg = playerImgList[frame/animcycle%4]
    
    draw_map(screen)  # マップ描画
    screen.blit(playerImg, (x*GS,y*GS))  # プレイヤー描画
    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()
        
        # プレイヤーの移動処理
        if event.type == KEYDOWN and event.key == K_DOWN:
            if is_movable(x, y+1):
                y += 1
        if event.type == KEYDOWN and event.key == K_LEFT:
            if is_movable(x-1, y):
                x -= 1
        if event.type == KEYDOWN and event.key == K_RIGHT:
            if is_movable(x+1, y):
                x += 1
        if event.type == KEYDOWN and event.key == K_UP:
            if is_movable(x, y-1):
                y -= 1

イメージの分割

アニメーションはちょっとずつ違った画像を連続して表示することで動いているように見せかけてます。たとえば、

f:id:aidiary:20100605231252g:plain

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

f:id:aidiary:20100606103045p:plain

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

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

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

# イメージロード
playerImgList = split_image(load_image("player4.png"))  # プレイヤー

load_image()はイメージを描画する(2008/5/5)で作成した関数です。player4.pngは先の図の4つのプレイヤーが並んだ画像です。load_image()で読み込んだSurfaceをsplit_image()に渡して4つのSurfaceに分割しリスト playerImgListに格納するわけです。

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

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

while True:
    clock.tick(60)
    
    # 経過フレーム数に応じて表示する画像を変える
    frame += 1
    playerImg = playerImgList[frame/animcycle%4]
    
    draw_map(screen)  # マップ描画
    screen.blit(playerImg, (x*GS,y*GS))  # プレイヤー描画
    pygame.display.update()

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

    playerImg = playerImgList[frame/animcycle%4]

です。これは、分割した画像のリストから表示する画像を選択しています。ここでは、frameの値によって描画する画像を変えているのがわかりますね。問題は、

 frame/animcycle%4

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

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

  # 24〜47フレームの24フレーム間は1番目の画像を描画
  frame=24   → 24/24=1  → 1%4=1
  frame=25   → 25/24=1  → 1%4=1
  ..
  frame=47   → 47/24=1  → 1%4=1

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

  # 72〜95フレームの24フレーム間は3番目の画像を描画
  frame=72   → 72/24=3  → 3%4=3
  ...
  frame=95   → 95/24=3  → 3%4=3

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