pygameでRPG（キャラクター移動）


# お知らせ


[記事全体の目次へ移動](https://qiita.com/hijpa/items/056acaa13e935590fdc2)

# GIF

<details><summary>ここをクリックしてください</summary>

![walking_video.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3874121/72fc40c4-2925-5823-d4bf-6aa2abe9b491.gif)

</details>

キャラクターについて書いていこうと思いますが、aidiaryさんのコードを参考にした方が早いかもしれませんね。
[人工知能に関する断創録](https://aidiary.hatenablog.com/archive/category/Pygame?page=3)

ぼくのコードはaidiaryさんのコードをちょっと短くしただけです。

# キャラクターの描画

<details><summary>ここをクリックしてください</summary>

まずはキャラクターの画像をダウンロードしましょう。
キャラチップ.zipを選びましょう。
[ぴぽや倉庫　ここからダウンロード](https://pipoya.net/sozai/assets/charachip/character-chip-1/)

![manto_12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3874121/998eafca-5b90-e91b-f9dc-e1add456c5eb.png)

FileNotFoundError
というエラーが出たら、rを"パス"の前につけましょう。。

```python:キャラクター
import pygame
import sys
from pygame.locals import *

pygame.init()
screen = pygame.display.set_mode((160, 160))
pygame.display.set_caption('chara only')
# ここにキャラクター画像ののパスを貼り付ける
player = pygame.image.load(パス)
clock = pygame.time.Clock()
running = True
while running:
    clock.tick(60)
    screen.fill((0, 0, 0))
    screen.blit(player,(64,64),(0,0,32,32))
    pygame.display.update()
    for event in pygame.event.get():
        if event.type == QUIT:
            pygame.quit()
            sys.exit()
```

![スクリーンショット (661).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3874121/c84d34f8-912b-48eb-a877-af3de5232646.png)

screen.blit(player,(64,64),(0,0,32,32))
(64,64)は画像を中央に描画するための座標です。
画面サイズ / 2 - 画像サイズ / 2
160 / 2 - 32 / 2 = 64

![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3874121/0f02aff3-6b55-b36c-efdb-a19222e11902.png)

(0,0,32,32) 
(キャラ画像の横座標、キャラ画像の縦座標、横の大きさ、縦の大きさ)

</details>


# キャラクターの移動

<details><summary>ここをクリックしてください</summary>

```python:キャラクターの移動
import pygame
from pygame.locals import *
import sys

pygame.init()
screen = pygame.display.set_mode((160,160))
pygame.display.set_caption('chara_move')
x,y = 2,2
GS = 32
#ここにキャラのパスを貼り付ける
player = pygame.image.load(パス)
clock = pygame.time.Clock()
running = True
while running:
    clock.tick(60)
    screen.fill((0,0,0))
    screen.blit(player, (x*GS, y*GS), (0, 0, GS, GS))
    pygame.display.update()
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()

        elif event.type==KEYDOWN:
            if event.key==K_DOWN:
                y += 1      
            elif event.key==K_UP:
                y -= 1
            elif event.key==K_RIGHT:
                x += 1
            elif event.key==K_LEFT:
                x -= 1
```

x,y = 0,0
マス単位の座標です。
ここを変えることでキャラクターの初期位置を変えることができます。

GS = 32
マスの大きさです。

screen.blit(player, (x\*GS, y\*GS))
マス単位の座標にマスの大きさをかけています。
x,y = 0, 0
(0\*32, 0\*32) つまり (0, 0)

キャラクターの描画では(64, 64)でしたね。これをここと同じように書くと
x,y = 2, 2
(2\*32, 2\*32) つまり (64, 64)

図で位置を表してみました。赤が(0, 0) 青が(64, 64)

![マス - コピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3874121/1db27026-0109-8e67-0efc-5aac990d71d3.png)

if event.type==KEYDOWN:
何かキーが押された時。

elif event.key==K_DOWN:
ダウンキーが押されたとき。

y += 1
yを変更しています。

x,y = 0, 0 だと x,y = 0, 1 に変更。
screen.blit(player, (x\*GS, y\*GS))だから
(0\*GS, 0\*GS) だと (0\*GS, 1\*GS) に変更。
ピクセル単位の座標にすると (0, 0) が (0, 32) になります。
つまり、キャラクターが下に移動します。

![マス - コピー (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3874121/976988ed-c241-f7dc-e59c-8396d78194b8.png)


</details>

# キャラアニメ

<details><summary>ここをクリックしてください</summary>

[ぴぽや倉庫　サイトへ移動](https://pipoya.net/sozai/assets/charachip/character-chip-1/)

ここからキャラクターチップをダウンロードできます。

↓画像を追加するコードです。
[画像を追加](https://qiita.com/drafts/38158786126c63122023/edit)


キャラが、その場足踏みしているように見えるコードです。
aidiaryさんのコードを短くしたコードです。

背景が透明な画像用に書き換えました。

```python:キャラアニメ
import pygame
from pygame.locals import *
import sys


def split_img(img):
    split_list = []
    for i in range(0, 128, GS):
        surface = pygame.Surface((GS, GS), pygame.SRCALPHA)
        surface.blit(img, (0, 0), (i, 0, GS, GS))
        split_list.append(surface)
    return split_list


pygame.init()
screen = pygame.display.set_mode((160,160))
pygame.display.set_caption('chara anime')
x,y = 2,2
GS = 32
#ここにキャラのパスを貼り付ける
playerImgList = split_img(pygame.image.load(パス))
# アニメーション速度
animcycle = 24  
frame = 0
clock = pygame.time.Clock()
 
while True:
    screen.fill((255,255,255))
    clock.tick(60)    
    # 経過フレーム数に応じて表示する画像を変える
    frame += 1
    player = playerImgList[int(frame/animcycle%4)]
    screen.blit(player, (x*GS, y*GS),(0,0,GS,GS))
    pygame.display.update()
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()

```

animcycle = 24 
ずっと２４のままです。

frame += 1
数字が一つずつ増えていきます。

player = playerImgList[frame//animcycle%4)]
キャラクター画像はリストに入っている。
計算式でインデックスを決めている。

playerImgList[0]
playerImgList[1]
playerImgList[2]
playerImgList[3]


# [frame//animcycle%4]の計算(分かりやすくしてみました)

a = 0//2%2　0割る２で0　　　　　0％２余り0
b = 1//2%2　１割る２で0.５（整数除算で小数点以下が切り捨てられ0）
　　　　　　0％２余り0

c = 2//2%2　２割る２で１　　　　　１％２余り１
d = 3//2%2　３割る２で１.５（整数除算で小数点以下が切り捨てられ１）
　　　　　　１％２余り１

e = 4//2%2　４割る２で２　　　　　２％２余り0
f = 5//2%2　５割る２で２.５（整数除算で小数点以下が切り捨てられ２）
　　　　　　２％２余り0


### `split_img` 関数の説明

```python
import pygame

def split_img(img, GS):
    split_list = []
    for i in range(0, 128, GS):
        surface = pygame.Surface((GS, GS), pygame.SRCALPHA)
        surface.blit(img, (0, 0), (i, 0, GS, GS))
        split_list.append(surface)
    return split_list
```

### 説明
```markdown
### `split_img` 関数の説明

```python
def split_img(img, GS):
```
`img` にはロードした画像が入ります。`GS` はグリッドサイズ（ピクセル単位）を指定します。

```python
split_list = []
```
空のリストです。ここに分割した画像を入れていきます。

```python
for i in range(0, 128, GS):
```
繰り返し処理です。0～128の範囲で `GS` 間隔の数字を作ります（例: `GS` が32の場合、0, 32, 64, 96）。
ただし、128は含まれません。range()で作られるのは指定した数未満までです。

```python
surface = pygame.Surface((GS, GS), pygame.SRCALPHA)
```
透明なサーフェイスの生成です。
(サーフェイスとは、画像やテキストのことです)

```python
((GS,GS))
```
サーフェイスのサイズです。`GS` は32です。

```python
pygame.SRCALPHA
```
サーフェイスを透明にします。

```python
surface.blit(img, (0, 0), (i, 0, GS, GS))
```
サーフェイスに分割した画像を描画します。

- `img`: キャラクター画像です。
- `(0,0)`: 描画する座標です。サーフェイスと分割した画像は同じサイズなので、ぴったり合わさります。
- `(i,0,GS,GS)`: `i` は横の座標です。`range` 関数で生成された 0, 32, 64, 96 が順に入ります。`GS` は32なので 32x32 の大きさです。

```python
split_list.append(surface)
```
透明なサーフェイスに分割したキャラクター画像を貼り付けて、できた画像をリストに追加します。

```python
return split_list
```
繰り返し処理が終わり、4つの画像が入っているリストを返します。

</details>

# 向きを変えながら歩く

<details><summary>ここをクリックしてください</summary>

```python:向きを変える
import pygame
from pygame.locals import *
import sys


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


pygame.init()
screen = pygame.display.set_mode((160,160))
pygame.display.set_caption('walking')
x,y = 2,2
GS = 32
DOWN,LEFT,RIGHT,UP = 0,1,2,3
#ここキャラのパスを貼り付ける
playerImgList = split_image(pygame.image.load(パス))  # プレイヤーx,y = 1,1
direction = DOWN
animcycle = 24  # アニメーション速度
frame = 0
clock = pygame.time.Clock()
 
while True:
    clock.tick(60)
    screen.fill((255,255,255))
    # 経過フレーム数に応じて表示する画像を変える
    frame += 1
    player = playerImgList[int(direction*4+frame/animcycle%4)]
    screen.blit(player, (x*GS, y*GS))
    pygame.display.update()
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
        elif event.type==KEYDOWN:
            if event.key==K_DOWN:
                direction = DOWN
                y += 1      
            elif event.key==K_UP:
                direction = UP
                y -= 1
            elif event.key==K_RIGHT:
                direction = RIGHT
                x += 1
            elif event.key==K_LEFT:
                direction = LEFT
                x -= 1

```

def split_image(image):の説明
概要
キャラクター画像を１６個に分割してリストに入れる。
補足
キャラクター画像の大きさは１２８ｘ１２８
GSは３２

def split_image(image):
imageはキャラクター画像。
![player.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3874121/8c53e5d8-665d-c204-7b9d-67d3b361cfbc.png)

imageList = []
空のリストを作る。

for i in range(0, 128, GS):
0から128の範囲でGS(32)間隔の数字を作る。
ただし、128は含まれません。range()で作られるのは指定した数未満までです。
0 32 64 96

for j in range(0, 128, GS):
0から128の範囲でGS(32)間隔の数字を作る。
ただし、128は含まれません。range()で作られるのは指定した数未満までです。
0 32 64 96

surface = pygame.Surface((GS,GS))
サーフェイスを作る。
たとえるなら、ペイントのキャンパスのようなもの。
色はデフォルトで、黒。
((GS,GS))は大きさ。
![surface.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3874121/d7fee935-09ca-ad6f-9797-54d82e2fe785.png)


surface.blit(image, (0,0), (j, i,GS,GS))
作ったサーフェイスに画像の一部を貼り付ける。
imageはキャラクター画像。
(0,0)はサーフェイスの座標。
j,iはキャラクター画像の座標。
GS,GSは切り取る大きさ。
![surface.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3874121/d7fee935-09ca-ad6f-9797-54d82e2fe785.png)　
![split.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3874121/db4f2572-13e9-1a7b-2802-0cd83b244ae4.png)
![surface - コピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3874121/177e554d-9aee-fa02-9c6e-f5bcff892e54.png)


surface.set_colorkey(surface.get_at((0,0)), RLEACCEL)
.set_colorkeyは指定した色を透明にする。
.get_atは指定した座標の色を取得する。
ここでは(0,0)の色は黒なので黒がすべて透明になる。
ぼくは最初背景が白の画像を使っていたので、マントが透明になってました。
RLEACCELは描画を高速化する。

surface.convert()
サーフェイスを最適化する。

imageList.append(surface)
リストに追加する。

return imageList
結果を返します。

**おまけ**
set_alpha: サーフェス全体の透明度を設定します。透明度は0から255までの範囲で指定し、サーフェス全体が均一に透明になります。

set_colorkey: 特定の色を透明に設定します。サーフェス上の指定した色が透明になります。
</details>

# スクロール

<details><summary>ここをクリックしてください</summary>

　キャラクターの移動処理にはスクロールというものがあるのですが、ぼくは理解してないので紹介だけしておきます。
 (aidiaryさんの記事です)
[タイルスクロール](https://aidiary.hatenablog.com/entry/20080607/1275790963)
変更箇所(すべて変更済みのコードです)
pygame.quit()をつけてます。
```python:
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()#ここ
                sys.exit()

```

()をつけたり、isを==に変えたりしてます。

```python:
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 == -1:#ここ
            colorkey = image.get_at((0,0))
        image.set_colorkey(colorkey, RLEACCEL)
    return image

```

int()をつけてます。

```python:
    def draw(self, screen, offset):
        """マップを描画する"""
        offsetx, offsety = offset
        # マップの描画範囲を計算
        startx = int(offsetx / GS)#ここ
        endx = int(startx + SCR_RECT.width/GS + 1)#ここ
        starty = int(offsety / GS)#ここ
        endy = int(starty + SCR_RECT.height/GS + 1)#ここ

```

/ を // にします。

```python:
    def update(self):
        # キャラクターアニメーション（frameに応じて描画イメージを切り替える）
        self.frame += 1
        self.image = self.images[self.direction*4+self.frame//self.animcycle%4]#ここ

```

(aidiaryさんの記事です)
[ピクセルスクロール](https://aidiary.hatenablog.com/entry/20080614/1275791398)
変更箇所。(変更済みのコードです)
/ を // にしてます。

```python:
        # プレイヤーの移動処理
        if self.moving == True:
            # ピクセル移動中ならマスにきっちり収まるまで移動を続ける
            self.rect.move_ip(self.vx, self.vy)
            if self.rect.left % GS == 0 and self.rect.top % GS == 0:  # マスにおさまったら移動完了
                self.moving = False
                self.x = self.rect.left // GS#ここ
                self.y = self.rect.top // GS#ここ

```
これ以外はタイルベーススクロールと同じところを変更してください。
</details>


 
# セーブとロード

<details><summary>ここをクリックしてください</summary>

Sキーを押すと、座標を保存します。別の場所に移動して、Lキーを押してみてください。座標を保存した場所に戻ります。

```python:
import pygame
from pygame.locals import *
import sys
import pickle

pygame.init()
screen = pygame.display.set_mode((160,160))
pygame.display.set_caption('save load')
x,y = 2,2
GS = 32
#ここにキャラクター画像のパスを貼り付ける
player = pygame.image.load(パス)
clock = pygame.time.Clock()
running = True
while running:
    clock.tick(60)
    screen.fill((0,0,0))
    screen.blit(player, (x*GS, y*GS))
    pygame.display.update()
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()

        elif event.type==KEYDOWN:
            if event.key==K_DOWN:
                y += 1      
            elif event.key==K_UP:
                y -= 1
            elif event.key==K_RIGHT:
                x += 1
            elif event.key==K_LEFT:
                x -= 1

            elif event.key==K_s:
                # ゲームの状態を辞書として定義
                save_data = {
                    "x": x,"y": y           
                }

                # ゲームの状態をファイルに保存
                with open('save_data.pickle', 'wb') as file:
                    pickle.dump(save_data, file)
                    print("セーブしました")
            elif event.key==K_l:
                # セーブデータを読み込む
                with open('save_data.pickle', 'rb') as file:
                    save_data = pickle.load(file)
                    x = save_data["x"]
                    y = save_data["y"]
                    print("ロードしました")

```


</details>
 
# pickleファイルの中身を表示
セーブやロードで、エラーが起きた時に本当に保存されてるか確かめる必要があります。

<details><summary>ここをクリックしてください</summary>

```python:
import pickle

# データを読み込む関数
def load_pickle_data(file_path):
    with open(file_path, 'rb') as file:
        data = pickle.load(file)
    return data

# 読み込んだデータを表示する関数
def display_data(data):
    print("読み込んだデータ:")
    print(data)

# 使用例
file_path =   # 保存されたpickleファイルのパス
data = load_pickle_data(file_path)
display_data(data)


```
</details>