いよいよ動きを伴う画面を作成します。これまで使っていたプレーヤーの画像は正面向きの一つだけでした。これを動かしてもあまり面白みがありません。そこで師匠のサイトにある「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()
方向キーを一回押すとプレーヤーがその方向へ一歩だけ歩きます。止まっていても足踏みは続けています。でも、壁や荷物があってもすり抜けてしまいますね。次は壁や荷物に衝突した場合の処理を考えましょう。