pygameでRPG(モンスターの描画)



[新RPGの目次](https://qiita.com/hijpa/items/31900e327315fa8e4eea)　

# モンスターの描画

モンスター画像のサイズは、１００x１００を想定しています。

<details><summary>ここにコードがあります</summary>

```python:

import pygame
import sys
from pygame.locals import *
import random

pygame.init()
width, height = 640, 480
screen = pygame.display.set_mode((width, height))
pygame.display.set_caption("モンスター描画")

# ロード
# ここにモンスター画像のパスを貼り付ける
monsterA_image = pygame.image.load()
# ここにモンスター画像のパスを貼り付ける
monsterB_image = pygame.image.load()
# ここにモンスター画像のパスを貼り付ける
monsterC_image = pygame.image.load()


# モンスター
nameA = ""
nameB = ""
nameC = ""
# モンスターのグループ
# 全角スペースで区切ります
# ex) "スライム　ウルフ"
# ひとグループ３体までです
groupA = ""
groupB = ""
groupC = ""
monster_list = [groupA,groupB,groupC]
mon = random.choice(monster_list)
mon_group_list = mon.split()

monster_images_dict = {nameA:monsterA_image,
                       nameB:monsterB_image,nameC:monsterC_image}

# モンスターの位置を定義
monster_positions = {
    1: [0.5],  # 1体
    2: [0.4, 0.6],  # 2体
    3: [0.3, 0.5, 0.7],  # 3体
}

# モンスターの位置を設定
monster_rects = [
    monster_images_dict[mon_group_list[i]].get_rect(center=(width * monster_positions[len(mon_group_list)][i], height * 0.5))
    for i in range(len(mon_group_list))
]

# 色
black = (0, 0, 0)

clock = pygame.time.Clock()
while True:
    clock.tick(60)
    screen.fill(black)
    for i in range(len(mon_group_list)):
        screen.blit(monster_images_dict[mon_group_list[i]], (monster_rects[i].topleft))

    
    pygame.display.update()
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()

```

</details>

<details><summary>ここにコードの説明があります</summary>
　　　　　　
---

**コードの説明です。**

🎮 **Pythonでモンスターを配置するコード！** 🐺🦅

### **🦾 モンスターの位置を設定**
```python
monster_rects = [
    monster_images_dict[mon_group_list[i]].get_rect(center=(width * monster_positions[len(mon_group_list)][i], height * 0.5))
    for i in range(len(mon_group_list))
]
```
X座標を変更しつつ、モンスターの数だけレクトを作成する処理です！ 🔥  

---

### **📌 リスト内包表記とは？**
リストを簡潔に作成できるPythonの便利な構文！  
基本形はこちら👇
```python
新しいリスト = [式 for 要素 in イテラブル]
```
✅ **短い処理ならスッキリ記述可能**  
⚠️ **長くなりすぎる場合は `for` ループに切り替え**

---

### **🛠 実際の処理**
```python
mon_group_list = ["ウルフ", "ウルフ", "バード"]
monster_positions = {
    1: [0.5],  
    2: [0.4, 0.6],  
    3: [0.3, 0.5, 0.7],  
}

monster_images_dict[mon_group_list[i]].get_rect(center=(width * monster_positions[len(mon_group_list)][i], height * 0.5))
```
💡 **例:**  
- `mon_group_list[0]` は `"ウルフ"` → monster_images_dictから"ウルフ"をキーとして、ウルフの画像を取得
- - モンスター数が `3` なので、`monster_positions[3] = [0.3, 0.5, 0.7]`
- 一回目の処理は、iが0なので、`monster_positions[3][0] = 0.3`
- 結果: **ウルフの画像を `center=(width * 0.3, height * 0.5)` に配置するレクトを取得！**
ウルフのレクト　[<rect(142, 192, 101, 97)>]

    for i in range(len(mon_group_list)):
        screen.blit(monster_images_dict[mon_group_list[i]], (monster_rects[i].topleft))

monster_images_dict {'スライム': <Surface(95x105x32 SW)>, 'ウルフ': <Surface(101x97x32 SW)>, 'バード': <Surface(106x107x32 SW)>} 

mon_group_list  ['ウルフ', 'スライム', 'バード']

moster_rects　[<rect(142, 192, 101, 97)>, <rect(273, 188, 95, 105)>, <rect(395, 187, 106, 107)>]
monster_rects[0].topleft (142, 192)
monster_rects[1].topleft (273, 188)
monster_rects[2].topleft (395, 187)

---


</details>

# モンスターを消す

上下キーで選択
Zキーで消す

<details><summary>ここにコードがあります</summary>

```python:

import pygame
import sys
from pygame.locals import *
import random

pygame.init()
width, height = 640, 480
screen = pygame.display.set_mode((width, height))
pygame.display.set_caption("モンスターを消す")

# ロード
# ここにフォントのパスを貼り付ける
font = pygame.font.Font(, 20)
# ここにモンスター画像のパスを貼り付ける
monsterA_image = pygame.image.load()
# ここにモンスター画像のパスを貼り付ける
monsterB_image = pygame.image.load()
# ここにモンスター画像のパスを貼り付ける
monsterC_image = pygame.image.load()
# ここにカーソル画像のパスを貼り付ける
cursor_img = pygame.image.load()

# モンスター
nameA = ""
nameB = ""
nameC = ""
# モンスターのグループ
# 全角スペースで区切ります
# ex) "スライム　ウルフ"
# ひとグループ３体までです
groupA = ""
groupB = ""
groupC = ""
monster_list = [groupA,groupB,groupC]
mon = random.choice(monster_list)
mon_group_list = mon.split()

monster_images_dict = {nameA:monsterA_image,
                       nameB:monsterB_image,nameC:monsterC_image}

# モンスターの位置を定義（奇数・偶数で異なる）
monster_positions = {
    1: [0.5],  # 1体
    2: [0.4, 0.6],  # 2体
    3: [0.3, 0.5, 0.7],  # 3体
}

# モンスターの位置を設定
monster_rects = [
    monster_images_dict[mon_group_list[i]].get_rect(center=(width * monster_positions[len(mon_group_list)][i], height * 0.5))
    for i in range(len(mon_group_list))
]

arrow_index = 0  # 選択カーソルの位置
window_rect = pygame.Rect(width * 0.4,height * 0.65,220,140)
rectx,recty = window_rect.topleft


# 色
black = (0, 0, 0)
white = (255,255,255)

clock = pygame.time.Clock()
while True:
    clock.tick(60)
    screen.fill(black)
    pygame.draw.rect(screen,white,window_rect,3,5)
    # モンスター画像の描画
    for i in range(len(mon_group_list)):
        screen.blit(monster_images_dict[mon_group_list[i]], (monster_rects[i].topleft))
    # モンスター名の描画
    if mon_group_list:
        for i, mon in enumerate(mon_group_list):
            text = font.render(mon, True, white)
            screen.blit(text, ((rectx + 14) + 30, (recty + 14) + 34 * i))
        # カーソル描画
        screen.blit(cursor_img, ((rectx + 14), (recty + 14) + 34 * arrow_index))

    
    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_UP and mon_group_list:
                arrow_index = (arrow_index - 1) % len(mon_group_list)  # 上へ移動

            elif event.key == K_DOWN and mon_group_list:
                arrow_index = (arrow_index + 1) % len(mon_group_list)  # 下へ移動

            elif event.key == K_z and mon_group_list:
                del mon_group_list[arrow_index]
                del monster_rects[arrow_index]
                if mon_group_list:
                    arrow_index = min(arrow_index, len(mon_group_list) - 1)  # 削除後のカーソル位置調整
                else:
                    arrow_index = 0  # リストが空ならカーソルをリセット


```

</details>

<details><summary>ここにコードの説明があります</summary>

---

## 🎮 **Pygameでモンスター選択ウィンドウを作成！**
### **🛠 ウィンドウの座標と描画**
```python
window_rect = pygame.Rect(width * 0.4, height * 0.65, 220, 140)
rectx, recty = window_rect.topleft
```
✅ **`width * 0.4, height * 0.65` の計算結果は `int` に変換**  
✅ **(`Rect`) を作成し、その座標 (`topleft`) を取得**  
✅ **枠内にモンスター名を描画するための基準座標**

---

### **📌 モンスター名の描画処理**
```python
if mon_group_list:
    for i, mon in enumerate(mon_group_list):
        text = font.render(mon, True, (255, 255, 255))
        screen.blit(text, ((rectx + 14) + 30, (recty + 14) + 34 * i))
    screen.blit(cursor_img, ((rectx + 14), (recty + 14) + 34 * arrow_index))  # カーソル描画
```
💡 **`enumerate()` を使うと…**
- `i` → **インデックス**
- `mon` → **モンスター名**

🔹 **例:**
```
インデックス: 0, モンスター名: ウルフ
インデックス: 1, モンスター名: ウルフ
インデックス: 2, モンスター名: バード
```

🔹 **描画位置の計算**
- `rectx, recty` → **枠の描画座標**
- `14` → **枠の端からの距離**
- `30` → **カーソル分、横にずらす**
- `34` → **フォントサイズ + 行間** （モンスター名が重ならないよう調整）
- `arrow_index` → **カーソルの縦位置を変更**


---

### **🎯 カーソル移動処理 (`↑↓キー`)**
```python
elif event.type == KEYDOWN:
    if event.key == K_UP and mon_group_list:
        arrow_index = (arrow_index - 1) % len(mon_group_list)  # 上へ移動
    elif event.key == K_DOWN and mon_group_list:
        arrow_index = (arrow_index + 1) % len(mon_group_list)  # 下へ移動
```
✅ **カーソルを上下に移動**  
✅ **リストをループさせる (`% len(mon_group_list)`)**

📌 **負の数の `%` 計算例**
| 方向キー　  |  ↑ |  ↑ |  ↑ |   | ↓ | ↓ | ↓ |
|------------|----|----|----|---|---|---|---|
| 割られる数 | -3 | -2 | -1 | 0 | 1 | 2 | 3 |
| 割る数     |  3 |  3 |  3 | 3 | 3 | 3 | 3 |
| 余り       | 0  | 1  | 2  | 0 | 1 | 2 | 0 |

---

### **✨ まとめ**
🐍 **Python + Pygame でモンスター選択画面を作成！**  
✅ **ウィンドウを描画し、モンスター名を枠内に表示**  
✅ **カーソル移動機能 (`↑↓キー`) を実装**  
✅ **選択式 UI を簡潔なコードで実現！**  

---

```python
elif event.key == K_z and mon_group_list:
    del mon_group_list[arrow_index]
    del monster_rects[arrow_index]

    for i in range(len(mon_group_list)):
        screen.blit(monster_images_dict[mon_group_list[i]], (monster_rects[i].topleft))
```

mon_group_list  ['ウルフ', 'スライム', 'バード']
moster_rects　[\<rect(142, 192, 101, 97)>, \<rect(273, 188, 95, 105)>, \<rect(395, 187, 106, 107)>]
monster_rects[0].topleft (142, 192)
monster_rects[1].topleft (273, 188)
monster_rects[2].topleft (395, 187)
 　　　　　　↓
arrow_index[1]でZキーを押すと  
 　　　　　　↓
mon_group_list  ['ウルフ','バード']
moster_rects　[\<rect(142, 192, 101, 97)>, \<rect(395, 187, 106, 107)>]
monster_rects[0].topleft (142, 192)
monster_rects[1].topleft (395, 187)

真ん中の画像が消えます。

---
### `min()` 関数の活用例：カーソルの範囲調整

```python
arrow_index = min(arrow_index, len(mon_group_list) - 1)
```

このコードは、リスト `mon_group_list` から要素が削除された際に、カーソル (`arrow_index`) がリストの範囲外に出ないように調整するためのものです。

---

### `min()` 関数について

`min()` は Python の組み込み関数で、与えられた値の中から最小値を返します。

#### 基本的な使い方：
```python
x = min(5, 2, 8)
print(x)  # 出力: 2
```
この場合、`min()` は `5, 2, 8` の中から最も小さい `2` を返します。

---

### カーソル位置の調整（例）

#### **モンスターが 3 体いる場面**
モンスターが 3 体いる状態で、1 体倒したときの `arrow_index` の変化を説明します。

- **上から 2 番目のモンスターを選んでいた場合**  
  - `arrow_index = 1`
  - `len(mon_group_list)` は 3 体から 2 体になり、`len(mon_group_list) - 1 = 1`
  - `min(1,1) = 1` となるので、カーソル位置の変化なし。

- **上から 3 番目のモンスターを選んでいた場合**  
  - `arrow_index = 2`
  - `len(mon_group_list)` は 3 体から 2 体になり、`len(mon_group_list) - 1 = 1`
  - `min(2,1) = 1` となるので、`arrow_index` が `2` から `1` に変化あり。

この処理により、カーソルのタテ座標が 2 から 1 に減り、描画位置が上に移動します。  
このコードを使うことで、カーソルがリストの範囲外に飛び出すのを防げます。

変化なし
![スクリーンショット (1412) - コピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3874121/16d621c1-6fda-45d7-9516-ad4a9598fe18.png)

変化あり
![スクリーンショット (1414) - コピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3874121/533b08a6-164b-4d43-95c0-f5309c3bd344.png)


---

</details>

# ダメージエフェクト

上下キーで選択
Zキーでダメージエフェクト

<details><summary>ここにコードがあります</summary>

```python:

import pygame
import sys
from pygame.locals import *
import random


pygame.init()
width, height = 640, 480
screen = pygame.display.set_mode((width, height))
pygame.display.set_caption("ダメージエフェクト")

# ロード
# ここにフォントのパスを貼り付ける
font = pygame.font.Font(, 20)
# ここにモンスター画像のパスを貼り付ける
monsterA_image = pygame.image.load()
# ここにモンスター画像のパスを貼り付ける
monsterB_image = pygame.image.load()
# ここにモンスター画像のパスを貼り付ける
monsterC_image = pygame.image.load()
# ここにダメージ音のパスを貼り付ける
sound2 = pygame.mixer.Sound()
# ここにカーソル画像のパスを貼り付ける
cursor_img = pygame.image.load()
# ここに背景のパスを貼り付ける
bg = pygame.image.load()


# モンスター
nameA = ""
nameB = ""
nameC = ""
# モンスターのグループ
# 全角スペースで区切ります
# ex) "スライム　ウルフ"
# ひとグループ３体までです
groupA = ""
groupB = ""
groupC = ""
monster_list = [groupA,groupB,groupC]
mon = random.choice(monster_list)
mon_group_list = mon.split()

monster_images_dict = {nameA:monsterA_image,
                       nameB:monsterB_image,nameC:monsterC_image}

# モンスターの位置を定義（奇数・偶数で異なる）
monster_positions = {
    1: [0.5],  # 1体
    2: [0.4, 0.6],  # 2体
    3: [0.3, 0.5, 0.7],  # 3体
}

# モンスターの位置を設定
monster_rects = [
    monster_images_dict[mon_group_list[i]].get_rect(center=(width * monster_positions[len(mon_group_list)][i], height * 0.5))
    for i in range(len(mon_group_list))
]

arrow_index = 0  # 選択カーソルの位置
window_rect = pygame.Rect(width * 0.4,height * 0.66,220,140)
rectx,recty,_,_ = window_rect

# 黒い画像（ダメージエフェクトのため）
m_width, m_height = 110,110
black_image = pygame.Surface((m_width, m_height))

# 色
black = (0, 0, 0)
white = (255,255,255)

effect = 0
flash = 0
visible = 0

clock = pygame.time.Clock()
while True:
    clock.tick(60)
    screen.fill(black)
    screen.blit(bg,(0,0))
    pygame.draw.rect(screen,white,window_rect,3,5)
    for i in range(len(mon_group_list)):
        if arrow_index == i and visible == 0:
            screen.blit(monster_images_dict[mon_group_list[i]], (monster_rects[i].topleft))
        if arrow_index != i:
            screen.blit(monster_images_dict[mon_group_list[i]], (monster_rects[i].topleft))
    # モンスターがいる場合のみ描画
    if mon_group_list:
        for i, mon in enumerate(mon_group_list):
            text = font.render(mon, True, (255, 255, 255))
            screen.blit(text, ((rectx + 14) + 30, (recty + 14) + 34 * i))
        screen.blit(cursor_img, ((rectx + 14), (recty + 14) + 34 * arrow_index))  # カーソル描画

    pygame.display.update()
    if effect == 1:
        if flash > 0:
            visible = pygame.time.get_ticks() // 90 % 2
            flash -= 1
            if flash == 19:
                sound2.play()
            if flash == 1:
                if mon_group_list:
                    del mon_group_list[arrow_index]
                    del monster_rects[arrow_index]
                    arrow_index = min(arrow_index, len(mon_group_list) - 1)  # 削除後のカーソル位置調整
                else:
                    arrow_index = 0  # リストが空ならカーソルをリセット
                effect = 0
                visible = 0
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()

        elif effect != 1:
            if event.type == KEYDOWN:
                if event.key == K_UP and mon_group_list:
                    arrow_index = (arrow_index - 1) % len(mon_group_list)  # 上へ移動

                elif event.key == K_DOWN and mon_group_list:
                    arrow_index = (arrow_index + 1) % len(mon_group_list)  # 下へ移動

                elif event.key == K_z and mon_group_list:  # 決定
                    effect = 1
                    flash = 20
                elif event.key == K_x:
                    print(monster_rects,monster_images_dict,mon_group_list)



```
</details>

<details><summary>ここにコードの説明があります</summary>


---

### モンスター画像の点滅処理

モンスターの画像を **90ミリ秒ごと** に点滅させる処理を `pygame.time.get_ticks()` を使って実装。

#### **処理の概要**
- `pygame.time.get_ticks()` は、プログラム開始からの経過時間をミリ秒単位で取得
- `pygame.time.get_ticks() // 90 % 2` を使い、**偶数のときは表示（`visible = 0`）、奇数のときは非表示（`visible = 1`）**
- `90` の数値を大きくすると点滅速度がゆっくりになるが、**`flash = 20` に対して大きすぎるとあまり変化がない**

#### **コードの動作**
```python
for i in range(len(mon_group_list)):
    if arrow_index == i and visible == 0:
        screen.blit(monster_images_dict[mon_group_list[i]], (monster_rects[i].topleft))
    if arrow_index != i:
        screen.blit(monster_images_dict[mon_group_list[i]], (monster_rects[i].topleft))

if effect == 1:
    if flash > 0:
        visible = pygame.time.get_ticks() // 90 % 2
```
- `flash` を減少させながら、点滅を制御
- `flash == 19` のタイミングで `sound2.play()` を再生
- **`flash == 20` だと音が再生されない**ので注意

#### **キー操作制限**
```python
elif effect != 1:
    if event.type == KEYDOWN:
```
- **点滅中にキー操作ができないよう制限**

#### **モンスター選択時の処理**
```python
elif event.key == K_z and mon_group_list:
    effect = 1
    flash = 20  # 点滅時間を設定
```
- `flash` の値が大きいほど点滅時間が長くなる


---
あまり正確ではない数値ですが、参考までに…

flashが１回につき0.015秒
２０回だと0.3秒
// 90だと0.06秒間隔

---

</details>