前の頁へ   次の頁へ

老い学の捧げ物 Part Ⅰ

5.プレーヤーの移動

いよいよ動きを伴う画面を作成します。これまで使っていたプレーヤーの画像は正面向きの一つだけでした。これを動かしてもあまり面白みがありません。そこで師匠のサイトにある「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()

方向キーを一回押すとプレーヤーがその方向へ一歩だけ歩きます。止まっていても足踏みは続けています。でも、壁や荷物があってもすり抜けてしまいますね。次は壁や荷物に衝突した場合の処理を考えましょう。

前の頁へ   次の頁へ