いよいよ動きを伴う画面を作成します。これまで使っていたプレーヤーの画像は正面向きの一つだけでした。これを動かしてもあまり面白みがありません。そこで師匠のサイトにある「RPG プレーヤーの移動」(もしリンク先が開けなかったら「Ctrl」キーを押しながらクリックしてください。別タブで開きます)のように、移動方向によって画像を変え、さらに足踏みのアニメーションもさせたいと思います。師匠のサイトにある下記の連結画像データを拝借します。これをダウンロードしてimagesフォルダに保存しておいて下さい。名前は「players.png」としましょう。

この連結画像を読み込んでそれぞれの画像に分割するメソッドも、師匠のサイトで公開されています。これをそのまま使わせて頂きましょう。以下のメソッドです。このメソッドは汎用性があり、例えばトランプの連結画像もこれで一枚一枚に分割できます。したがってモジュールとして使いたいので、splitimage.pyとしてmoduleフォルダに保存しておいてください。
ファイル名:splitimage.py
import pygame
from pygame.locals import *
def splitImage(image, n, m):
"""一枚のイメージを n(x方向) * m(y方向)枚のイメージに分割したリストを返す"""
imageList = []
w = image.get_width()
h = image.get_height()
w1 = w//n # x方向の枚数
h1 = h//m # y方向の枚数
for i in range(0, h, h1):
for j in range(0, w, w1):
surface = pygame.Surface((w1, h1))
surface.blit(image, (0, 0), (j, i, w1, h1))
surface.set_colorkey(surface.get_at((0,0)), RLEACCEL) # (0,0)の色を透明色に
surface.convert()
imageList.append(surface)
return imageList
準備が整いましたので、まずプレーヤーのクラスを作成します。移動方向に応じて画像を変えるので、画面上の座標と移動方向をインスタンス変数とします。コンストラクタまでは、以下のようになります。一次元配列images[]に16個のイメージデータが格納され、現在表示するデータを変数imageとします。images[0]にこれまで使ってきた正面向きのデータが格納されていることになります。animcycleとframeは、足踏みアニメーション用の変数です。
import pygame
from pygame.locals import *
import sys
sys.path.append("C:\myPython\module")
import loadImage as LI # moduleの利用
import splitImage as SI # moduleの利用
class Player:
animcycle = 24 # アニメーション速度
frame = 0
def __init__(self, pos, dir):
self.images = SI.splitImage(LI.load_image("../images/player.png"),4,4)
self.image = self.images[0]
self.dir = dir
self.x = pos[0]
self.y = pos[1]
これにメソッドを追加し、足踏み状態を表示してみます。方向キーの入力によって各方向を向きその場で足踏みします。animcycleの数を12にすると駆け足になり、6にすると走ります。48にすると抜き足差し足になります。24くらいが丁度いい感じですね。まだ移動はできません。
ファイル名:test.py
001:import pygame
002:from pygame.locals import *
003:import sys
004:sys.path.append("C:\myPython\module")
005:import loadImage as LI # moduleの利用
006:import splitImage as SI # moduleの利用
007:
008:DOWN, LEFT, RIGHT, UP = 0, 1, 2, 3
009:SCR_RECT = Rect(0, 0, 800, 480) # 画面サイズ
010:GS = 32 # 1マスの大きさ[px]
011:screen = pygame.display.set_mode(SCR_RECT.size)
012:
013:class Player:
014: animcycle = 24 # アニメーション速度
015: frame = 0
016:
017: def __init__(self, pos, dir):
018: self.images = SI.splitImage(LI.load_image("../images/players.png"),4,4)
019: self.image = self.images[0] # 描画中のイメージ
020: self.dir = dir
021: self.x = pos[0]
022: self.y = pos[1]
023:
024: def animate(self):
025: self.frame += 1
026: self.image = self.images[int(self.dir*4 + self.frame/self.animcycle%4)]
027:
028: def move(self, dir):
029: if dir == DOWN:
030: self.dir = DOWN
031: elif dir == LEFT:
032: self.dir = LEFT
033: elif dir == RIGHT:
034: self.dir = RIGHT
035: elif dir == UP:
036: self.dir = UP
037:
038: def draw(self, screen): # playerの描画
039: screen.blit(self.image, (self.x*GS, self.y*GS))
040:
041: def main(self):
042: clock = pygame.time.Clock()
043: while (1):
044: clock.tick(60)
045: self.animate()
046: # イベント処理
047: for event in pygame.event.get():
048: # 終了用のイベント処理
049: if event.type == QUIT: # 閉じるボタンが押されたとき
050: pygame.quit()
051: sys.exit()
052: if event.type == KEYDOWN and event.key == K_ESCAPE:
053: pygame.quit()
054: sys.exit()
055: # プレイヤーの移動処理
056: if event.type == KEYDOWN and event.key == K_DOWN:
057: self.move(DOWN)
058: if event.type == KEYDOWN and event.key == K_LEFT:
059: self.move(LEFT)
060: if event.type == KEYDOWN and event.key == K_RIGHT:
061: self.move(RIGHT)
062: if event.type == KEYDOWN and event.key == K_UP:
063: self.move(UP)
064:
065: self.draw(screen) # playerを描画
066: pygame.display.update()
067: screen.fill((0,0,0))
068:
069:if __name__ == "__main__":
070: player = Player((8, 2), DOWN)
071: player.main()
このコードはそんなに難しくないと思います。26行目がアニメーションのキモですが、師匠のサイトに書かれていたそのままです。frameがインクリメントされ続けているのが少し気になりますが、一面を解いている間だけですから問題はないのでしょう。次は、方向キーの入力によって4方向へ歩くようにします。これは簡単です。28行からのmoveメソッドを以下のように修正するだけです。
def move(self, dir):
if dir == DOWN:
self.dir = DOWN
self.y += 1
elif dir == LEFT:
self.dir = LEFT
self.x -= 1
elif dir == RIGHT:
self.dir = RIGHT
self.x += 1
elif dir == UP:
self.dir = UP
self.y -= 1
プレーヤの動きが完成したので、選んだ面の中をプレーヤーが歩き回るようにしたいと思います。
souko.pyを修正します。先ずfrom players import PlayerとしてPlayerクラスをimportします。そしてmain()メソッドでPlayerクラスのインスタンスを作成します。しかしPlayerクラスのインスタンス化にあたっては、選ばれたstageにおけるplayerの初期座標が必要になります。そこでMapクラスにplayerの初期座標を取得するメソッドを作成しましょう。mapStage.pyに以下のメソッドgetPlayerPos()を追加します。stage[i][j]とpos(j,i)では「i」と「j」の順序が逆になるので要注意です。
def getPlayerPos(self, stage):
for i in range(len(stage)): # 行数
for j in range(len(stage[i])): # 文字数
if stage[i][j] =='@': # Player.posを get
return(j, i)
break
そしてsouko.pyのmain()メソッドでpos = map.getPlayerPos(map.stage)、player = Player(pos, DOWN)としてPlayerクラスのインスタンスを作成します。アニメーションのコードをtest.pyからSoukoクラスへ移動します。またkeyイベントはSoukoクラスで発生しますから、playerの移動処理をPlayerクラスからSoukoクラスへ移動し、player.pyを以下のように修正します。ずいぶん短くなりました。
ファイル名:player.py
import pygame
from pygame.locals import *
import sys
sys.path.append("C:\myPython\module")
import loadImage as LI # moduleの利用
import splitImage as SI # moduleの利用
DOWN, LEFT, RIGHT, UP = 0, 1, 2, 3
SCR_RECT = Rect(0, 0, 800, 480) # 画面サイズ
GS = 32 # 1マスの大きさ[px]
screen = pygame.display.set_mode(SCR_RECT.size)
class Player:
animcycle = 24 # アニメーション速度
frame = 0
def __init__(self, pos, dir):
self.images = SI.splitImage(LI.load_image("../images/players.png"),4,4)
self.image = self.images[0] # 描画中のイメージ
self.dir = dir
self.x = pos[0]
self.y = pos[1]
def animate(self):
# キャラクターアニメーション(frameに応じて描画イメージを切り替える)
self.frame += 1
self.image = self.images[int(self.dir*4 + self.frame/self.animcycle%4)]
def move(self, dir):
if dir == DOWN:
self.dir = DOWN
self.y += 1
elif dir == LEFT:
self.dir = LEFT
self.x -= 1
elif dir == RIGHT:
self.dir = RIGHT
self.x += 1
elif dir == UP:
self.dir = UP
self.y -= 1
def draw(self, screen): # playerの描画
screen.blit(self.image, (self.x*GS+200, self.y*GS))
プレーヤーの描画は他のキャラクタとは別になったので、前回のsouko.pyの27-28行を削除しました。さらに移動方向の変数を追加して、編集後のsouko.pyは以下のようになります。
ファイル名:souko.py
import pygame
from pygame.locals import*
import sys
sys.path.append("C:\myPython\module")
import loadImage as LI # moduleの利用
from mapStage import Map # Mapクラスのインポート
from players import Player
DOWN, LEFT, RIGHT, UP = 0, 1, 2, 3
SCR_RECT = Rect(0, 0, 800, 480)
GS = 32
class Souko:
pygame.init()
screen = pygame.display.set_mode(SCR_RECT.size)
def __init__(self):
pass
def drawChar(self, map, screen): # mapを渡す
for i in map.blocks:
screen.blit(map.blockImg, (i[0]*GS+200, i[1]*GS)) # Mapクラスの変数の利用
for i in map.bags:
screen.blit(map.bagImg, (i[0]*GS+200, i[1]*GS))
for i in map.places:
screen.blit(map.placeImg, (i[0]*GS+200, i[1]*GS))
for i in map.stored:
screen.blit(map.storedImg, (i[0]*GS+200, i[1]*GS))
pygame.display.update()
def main(self):
map = Map() # Mapクラスのインスタンス作成
map.getData() # mapdata[][]の作成
map.stage = map.getStage(len(map.mapdata)) # 面の選択
self.screen.fill((0,0,0)) # ボタン画面消去
map.setChars(map.stage) # 選択した面に画像をセット
pos = map.getPlayerPos(map.stage)
player = Player(pos, DOWN)
clock = pygame.time.Clock()
while (1):
clock.tick(60)
player.animate()
self.drawChar(map, self.screen)
player.draw(self.screen) # playerを描画
# イベント処理
for event in pygame.event.get():
# 終了用のイベント処理
if event.type == QUIT: # 閉じるボタンが押されたとき
pygame.quit()
sys.exit()
if event.type == KEYDOWN: # キーを押したとき
if event.key == K_ESCAPE: # Escキーが押されたとき
pygame.quit()
sys.exit()
# プレイヤーの移動処理
if event.type == KEYDOWN and event.key == K_DOWN:
player.move(DOWN)
if event.type == KEYDOWN and event.key == K_LEFT:
player.move(LEFT)
if event.type == KEYDOWN and event.key == K_RIGHT:
player.move(RIGHT)
if event.type == KEYDOWN and event.key == K_UP:
player.move(UP)
pygame.display.update()
self.screen.fill((0,0,0))
if __name__ == "__main__":
souko = Souko()
souko.main()
方向キーを一回押すとプレーヤーがその方向へ一歩だけ歩きます。止まっていても足踏みは続けています。でも、壁や荷物があってもすり抜けてしまいますね。次は壁や荷物に衝突した場合の処理を考えましょう。