pygameでRPGをつくってみる４　テキスト


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

# GIF（会話）
<details><summary>ここをクリックしてください</summary>

![無題の動画 ‐ Clipchampで作成 (4).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3874121/cb889a6c-f67c-f5a4-2bb4-d7e0fb51aecc.gif)

</details>

# GIF（宝箱）
<details><summary>ここをクリックしてください</summary>

![無題の動画 ‐ Clipchampで作成 (5).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3874121/54f57741-51fd-1ae1-d433-4c754009bbb0.gif)

</details>

# GIF（宿屋）
<details><summary>ここをクリックしてください</summary>

![無題の動画 ‐ Clipchampで作成 (6).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3874121/4703d946-c015-0836-be84-b3003b194d4e.gif)

</details>

# この記事の目的

　会話ができるようにしたいと思います。

# 図形と枠

　この記事ではメッセージの背景を作るために使います。

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

```python:図形
import pygame
import sys

pygame.init()
screen = pygame.display.set_mode((320, 320))
pygame.display.set_caption('screen only')
clock = pygame.time.Clock()
running = True
while running:
    clock.tick(60)
    screen.fill((255, 255, 255))
    pygame.draw.rect(screen,(0,0,0),(140,140,40,40))
    pygame.display.update()
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
```

pygame.draw.rect(screen,(0,0,0),(140,140,40,40))
(0,0,0)色の指定をします。
(140,140,40,40) 座標と大きさです。(横、縦、幅、高さ)



# 枠

　この記事ではテキストウィンドゥの枠のためです。
 

```python:枠
import pygame
import sys

pygame.init()
screen = pygame.display.set_mode((320, 320))
pygame.display.set_caption('rect')
clock = pygame.time.Clock()
running = True
while running:
    clock.tick(60)
    screen.fill((255, 255, 255))
    pygame.draw.rect(screen,(0,0,0),(140,140,40,40),5)
    pygame.display.update()
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
```

pygame.draw.rect(screen,(0,0,0),(140,140,40,40),5)
最後の５が枠の太さです。

</details>


# テキスト改行

この記事では使いませんが、長いセリフでは改行が必要になります。

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

　￥n 環境によっては、\nでテキストを改行できます。

```python:
import pygame
pygame.init()

# 画面の設定
screen = pygame.display.set_mode((320, 160))
pygame.display.set_caption("Pygame Text Wrapping Example")

# フォントの設定
#ここにフォントのパスを貼り付ける
font = pygame.font.Font(パス, 20)

# テキストを改行して描画する関数
def draw_text(surface, text, pos, color=(255, 255, 255)):
    lines = text.split('\n')
    x, y = pos
    for line in lines:
        text_surface = font.render(line, True, color)
        surface.blit(text_surface, (x, y))
        y += text_surface.get_height()

# メインループ
clock = pygame.time.Clock()
running = True
while running:
    clock.tick(60)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    screen.fill((0, 0, 0))
    draw_text(screen, "ここは\nはじまりのむらです", (50, 50))
    pygame.display.flip()

pygame.quit()


```

指定したsurfaceに複数行のテキストを描画する関数。
def draw_text(surface, message, pos, font, color=(255, 255, 255)):
引数:
        surface: 描画対象
        message: 描画する文字列（改行 `\n` で分割可能）
        pos    : 描画する座標（タプルで指定）
        font   : 使用するフォント
        color  : テキストカラー ,デフォルトは白
        
    lines = message.split('\n')
文字列を区切り文字で分割してリストに入れます。
"\n"が区切り文字です。
"ゆうしゃのこうげき\nモンスターに　～のダメージ"
["ゆうしゃのこうげき","モンスターに　～のダメージ"]

    for line in lines:
        text_surface = font.render(line, True, color)
リストの要素を繰り返します。
一回目のlineは、ゆうしゃのこうげき
二回目のlineは、モンスターに　～のダメージ

        y += text_surface.get_height() + 9
.get_height()で文字列の高さを取得。+９で行間を9ピクセル追加
次の行のタテ座標を調整しています。


</details>


# 会話する

会話のためのコードを書いていきます。

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

Zキーを押すと会話します。

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

GS = 32
DOWN,LEFT,RIGHT,UP = 0,1,2,3

def split_image(image):
    """128x128のキャラクターイメージを32x32の16枚のイメージに分割
    分割したイメージを格納したリストを返す"""
    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,200))
pygame.display.set_caption('会話する')
#ここにフォントのパスを貼り付ける
font = pygame.font.Font(パス, 20) 
#ここに一人目のパスを貼り付ける
playerImgList = split_image(pygame.image.load(パス))
#ここに二人目のパスを貼り付ける
playerImgList2 = split_image(pygame.image.load(パス))

text_window = False
chara_dict  = {(3,2):"girl"}

x2,y2 = 3,2
x,y = 0,0
cx,cy = 0,0
chara_pos = 0
a = 0
direction = DOWN
direction2 = DOWN
animcycle = 24  # アニメーション速度
frame = 0
clock = pygame.time.Clock()
 
while True:
    clock.tick(60)
    screen.fill((128,128,128))
    # 経過フレーム数に応じて表示する画像を変える
    frame += 1
    player = playerImgList[int(direction*4+frame/animcycle%4)]
    player2 = playerImgList2[int(direction2*4+frame/animcycle%4)]
    screen.blit(player, (x*GS, y*GS))
    screen.blit(player2, (x2*GS, y2*GS))
    if a == 0:
        message = "わたし？"
    if text_window == True and chara_pos in chara_dict:
        text = font.render(message, True, (255,255,255))
        pygame.draw.rect(screen, (0, 0, 0), Rect(10, 130, 140, 50))
        pygame.draw.rect(screen, (255, 255, 255), Rect(10, 130, 140, 50), 3)
        screen.blit(text, (15, 140))
    pygame.display.update()
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
        if event.type==KEYDOWN:
            if event.key==K_SPACE:
                pygame.quit()
                sys.exit()

            elif  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

            elif event.key == K_z:
                if  direction == DOWN:
                    cx = x
                    cy = y + 1
                elif  direction == UP:
                    cx = x
                    cy = y - 1
                elif  direction == RIGHT:
                    cx = x + 1
                    cy = y
                elif  direction == LEFT:
                    cx = x - 1
                    cy = y

                chara_pos = (cx,cy)
                if chara_pos in chara_dict:
                    text_window = True
                    if direction == DOWN:
                        direction2 = UP
                    if direction == UP:
                        direction2 = DOWN
                    if direction == RIGHT:
                        direction2 = LEFT
                    if direction == LEFT:
                        direction2 = RIGHT
                    if chara_dict[chara_pos] == "girl":
                        if a == 0:
                            a += 1
                        elif a == 1:                
                            message = "うん"
                            a = 2
                        elif a == 2:                
                            message = "一緒に行く"
                            a = 3
                        elif a == 3:   
                            text_window = False
                            a = 0


```


text_window = False
最初からテキストウィンドウがあると変なので消しておきます。

chara_dict  = {(3,2):"girl"}
座標をキーに指定して、キャラクター名を取り出すための辞書です。

x2,y2 = 3,2
二人目のキャラクターの座標です。

x,y = 0,0
プレイヤーの座礁です。

cx,cy = 0,0
プレイヤーの隣の座標を入れるための変数です。

chara_pos = 0
cxとcyをまとめて、()に入れるための変数です。

a = 0
セリフを変更するための変数です。

direction = DOWN
プレイヤーの最初の向きです。
direction2 = DOWN
二人目のキャラクターの最初の向きです。

if a == 0:
    message = "わたし？"
これがないと二回目以降に最初のセリフが出てきません。

            elif event.key == K_z:
                if  direction == DOWN:
                    cx = x
                    cy = y + 1
                elif  direction == UP:
                    cx = x
                    cy = y - 1
                elif  direction == RIGHT:
                    cx = x + 1
                    cy = y
                elif  direction == LEFT:
                    cx = x - 1
                    cy = y

隣の座標をcxとcyに入れています。

chara_pos = (cx,cy)
cxとcyを一つにしています。

if chara_pos in chara_dict:
辞書の中に隣の座標が存在するか調べてます。
これがないと辞書にないキー(隣の座標)を指定したときにエラーが出ます。
                    
text_window = True
テキストウィンドウを表示します。
                    if direction == DOWN:
                        direction2 = UP
                    if direction == UP:
                        direction2 = DOWN
                    if direction == RIGHT:
                        direction2 = LEFT
                    if direction == LEFT:
                        direction2 = RIGHT

二人目のキャラクターの向きを変えます。

if chara_dict[chara_pos] == "girl":
辞書にないキー(隣の座標)を指定するとここでエラーが出ます。
ゲームの画面ではキャラクターがいないところでＺキーを押すとエラーが出ます。

細かい修正
辞書のキーには文字列以外も指定できるということで、キーを文字列から変更しました。f文字列を使う必要がなくなりました。

</details>



# 宝箱を開ける

会話するのとあまり変わらないのでここに書きました。



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

宝箱の画像はこのサイトからお借りするとよいです。
[ぴぽや倉庫　ここからダウンロード](https://pipoya.net/sozai/assets/charachip/character-chip-1/)

キャラチップ＋.zipをダウンロードしましょう。

![スクリーンショット (694) - コピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3874121/e11f0830-385f-b9de-f153-7f6b99f6964a.png)


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

GS = 32
DOWN,LEFT,RIGHT,UP = 0,1,2,3

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,200))
pygame.display.set_caption('宝箱')
chara_dict  = {"2,1":"treasure"}
#ここにキャラクターのパスを貼り付ける
playerImgList = split_image(pygame.image.load(パス))
#ここに宝箱のパスを貼り付ける
treasure = pygame.image.load(パス)  

x2,y2 = 2,1
x,y = 2,4
cx,cy = 0,0
chara_pos = 0
a = (64,0,GS,GS)
 
direction = DOWN
animcycle = 24  # アニメーション速度
frame = 0
clock = pygame.time.Clock()
 
while True:
    clock.tick(60)
    screen.fill((0,0,0))
    # 経過フレーム数に応じて表示する画像を変える
    frame += 1
    player = playerImgList[int(direction*4+frame/animcycle%4)]
    screen.blit(player, (x*GS, y*GS))
    screen.blit(treasure,(x2*GS,y2*GS), a)
    pygame.display.update()
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
        if event.type==KEYDOWN:
            if event.key==K_SPACE:
                pygame.quit()
                sys.exit()

            elif  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

            elif event.key == K_z:
                if  direction == DOWN:
                    cx = x
                    cy = y + 1
                elif  direction == UP:
                    cx = x
                    cy = y - 1
                elif  direction == RIGHT:
                    cx = x + 1
                    cy = y
                elif  direction == LEFT:
                    cx = x - 1
                    cy = y

                chara_pos = f"{cx},{cy}"
                if chara_pos in chara_dict:
                    print(cx,cy)
                    if chara_dict[chara_pos] == "treasure":
                        a = (64,64,GS,GS)


```

screen.blit(treasure,(x2\*GS,y2\*GS), a)

a = (64,0,GS,GS)からa = (64,64,GS,GS)に変更してます。
(画像の横座標、画像の縦座標、横サイズ、縦サイズ)

![white - コピー.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3874121/e5086695-269a-685d-87fd-ec4e2579bd76.jpeg)


</details>

# 宿屋

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

矢印キーで移動して、左クリックで話しかけます。
はい、いいえを選ぶのもセリフを進めるのも左クリックです。


必要なもの
- フォントファイル
- プレイヤー画像
- 宿屋のキャラクター画像
- BGMのファイル
- ボタンの音のファイル
- 泊まった時のメロディのファイル

以下のサイトからダウンロードできます。

[一般社団法人文字情報技術促進協議会(ここから、フォントファイルをダウンロード)](https://moji.or.jp/ipafont/ipaex00301/)
[キャラクター画像](https://pipoya.net/sozai/assets/charachip/character-chip-1/)
[ピ！(ボタンの音)](https://on-jin.com/sound/ta.php?bunr=%E3%82%A4%E3%83%A1%E3%83%BC%E3%82%B8&kate=%E6%93%AC%E9%9F%B3%E3%83%BB%E3%82%AA%E3%83%8E%E3%83%9E%E3%83%88%E3%83%9A)
[ほのぼの０１(泊まった時のメロディ)](https://on-jin.com/sound/muci.php?bunr=%E3%81%BB%E3%81%AE%E3%81%BC%E3%81%AE&kate=%E3%82%B8%E3%83%B3%E3%82%B0%E3%83%AB)
[BGM(ひとやすみ)](https://musmus.main.jp/music_game.html)
[BGM(石畳の下で)](https://musmus.main.jp/music_game_02.html)



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

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

GS = 32
DOWN,LEFT,RIGHT,UP = 0,1,2,3
Gold = 300
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

# テキストを改行して描画する関数
def draw_text(surface, message, pos, font, color=(255, 255, 255)):
    lines = message.split('\n')
    x, y = pos
    for line in lines:
        text_surface = font.render(line, True, color)
        surface.blit(text_surface, (x, y))
        y += text_surface.get_height()

pygame.init()
screen = pygame.display.set_mode((320,250))
pygame.display.set_caption('宿屋')
text_window = False
chara_dict  = {"3,0":"girl"}
#ここにフォントのパスを貼り付ける
font = pygame.font.Font(パス, 20)
# プレイヤーの画像のパスを貼り付ける
playerImgList = split_image(pygame.image.load(パス))
# 宿屋のキャラクター画像のパスを貼り付ける
playerImgList2 = split_image(pygame.image.load(パス))
# BGMのパスを貼り付ける
pygame.mixer.music.load(パス)
pygame.mixer.music.play(-1)
# ボタンの音のパスを貼り付ける
sound = pygame.mixer.Sound(パス)
# 泊まった時のメロディのパスを貼り付ける
sound2 = pygame.mixer.Sound(パス)
x2,y2 = 3,0
x,y = 3,2
mx,my = 0,0
cx,cy = 0,0
chara_pos = 0
a = 0
choose = 0
shop = 0
direction = DOWN
direction2 = DOWN
animcycle = 24  
frame = 0
action_number44 = 0
action_number45 = 0
Max_HP = 80
player_HP = 10
change = 0
inn_melody = 0
clock = pygame.time.Clock()
walking = False
# 半透明のSurfaceを作成
# はい、いいえ
transparent_surface3 = pygame.Surface((70, 20), pygame.SRCALPHA)
# RGBA (白, 透明度128)
transparent_surface3.fill((255, 255, 255, 128))  
# 半透明のSurfaceを作成
transparent_surface = pygame.Surface((320, 250), pygame.SRCALPHA)
transparent_surface.fill((0, 0, 0, change))  
 
while True:
    clock.tick(60)
    screen.fill((128,128,128))
    # 経過フレーム数に応じて表示する画像を変える
    frame += 1
    player = playerImgList[int(direction*4+frame/animcycle%4)]
    player2 = playerImgList2[int(direction2*4+frame/animcycle%4)]
    screen.blit(player, (x*GS, y*GS))
    screen.blit(player2, (x2*GS, y2*GS))
    #"はい"と"いいえ"の枠
    if choose == 1:
        pygame.draw.rect(screen, (0, 0, 0), Rect(250, 5, 70, 55))
        pygame.draw.rect(screen, (255, 255, 255), Rect(250, 5, 70, 55), 3)
        if mx >= 258 and mx <= 298:
            if my >= 10  and my <= 30:
                screen.blit(transparent_surface3, (258, 10))
                action_number44 = 1
            else:
                action_number44 = 0
                
            if my >=34  and my <= 54:
                screen.blit(transparent_surface3, (258, 34))
                action_number45 = 1
            else:
                action_number45 = 0
        else:
            action_number44 = 0
            action_number45 = 0
        text = font.render("はい", True, (255,255,255))                
        screen.blit(text, (258, 10))
        text1 = font.render("いいえ", True, (255,255,255))                
        screen.blit(text1, (258, 33))

    if a == 0:
        message = "ここはやどやです。\n５ゴールドに　なりますが？"
    if text_window == True:
        pygame.draw.rect(screen, (0, 0, 0), Rect(10, 130, 270, 80))
        pygame.draw.rect(screen, (255, 255, 255), Rect(10, 130, 270, 80), 3)
        draw_text(screen , message, (18,135), font)
    if shop == 1 and a == 3 or a == 4:
        if change < 255 and a == 3:
            if inn_melody == 0:
                pygame.mixer.music.pause()
                sound2.play()
                inn_melody = 1
            change += 2
            time.sleep(0.01)
            if change >= 243:
                change = 255
            if change == 255:
                a = 4
        elif change > 0 and a == 4:
            change -= 2
            time.sleep(0.01)
            message = "またおこしくださいませ"
            if change == 1:
                change = 0
            if change == 0:
                a = 5
        transparent_surface.fill((0, 0, 0, change))  
        #透明のSurfaceを描画
        screen.blit(transparent_surface, (0, 0))
    if change == 0 and a == 5:
        player_HP = Max_HP
        choose = 0
        a = 7
    pygame.display.update()
    for event in pygame.event.get():
        mouse_pressed = pygame.mouse.get_pressed()
        mouse_pos = pygame.mouse.get_pos()
        mx = mouse_pos[0]
        my = mouse_pos[1]
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
        elif mouse_pressed[0]:
            if shop == 0:
 
                if  direction == DOWN:
                    cx = x
                    cy = y + 1
                elif  direction == UP:
                    cx = x
                    cy = y - 1
                elif  direction == RIGHT:
                    cx = x + 1
                    cy = y
                elif  direction == LEFT:
                    cx = x - 1
                    cy = y

                chara_pos = f"{cx},{cy}"
                if chara_pos in chara_dict:
                    text_window = True
                    if direction == DOWN:
                        direction2 = UP
                    elif direction == UP:
                        direction2 = DOWN
                    elif direction == RIGHT:
                        direction2 = LEFT
                    elif direction == LEFT:
                        direction2 = RIGHT
                    if chara_dict[chara_pos] == "girl":
                        shop = 1
                        choose = 1

     #宿屋
            elif shop == 1:
                if choose == 1 and a == 0:
                    a = 1
                # はい　お金ある
                elif action_number44 == 1 and a == 1 and Gold >= 5:
                    sound.play()
                    message = "ごゆっくり　どうぞ"                           
                    a = 2                                
                    choose = 0
                    Gold -= 5                               
                elif shop == 1 and a == 2:
                    a = 3
                # はい　お金ない    
                elif action_number44 == 1 and a == 1 and Gold <= 5:
                    message = "お金が足りません"
                    sound.play()
                    a = 6
                    choose = 0
                # いいえ 閉じる   
                elif action_number45 == 1 and a == 1:
                    sound.play()
                    text_window = False
                    choose = 0
                    a = 0
                    shop = 0

                # またおこし    
                elif a == 7:
                    pygame.mixer.music.unpause()
                    message = "またおこしくださいませ"
                    a = 8
                    time.sleep(0.3)
                # おかねがある　閉じる
                elif a == 8:
                    sound.play()
                    a = 0
                    text_window = False
                    shop  = 0
                    inn_melody = 0
                # お金がない　閉じる
                elif a == 6:
                    sound.play()
                    a = 0
                    text_window = False
                    shop = 0

        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
```
</details>