Pygameを使って RPGを作る（6. 衝突判定後のPlayerの移動を制限する）

# 概要
プレイヤーの動きと衝突処理を実装

---

### **クラス`Player`の説明**

#### **`__init__`メソッド**  
プレイヤーオブジェクトの初期化を行います。  
1. **イメージの読み込み**
   ```python
   self.surface = pg.image.load(IMAGE_PATH).convert()
   ```
   `IMAGE_PATH`で指定された画像を読み込んで、プレイヤーの外見として使用します。

2. **位置情報**
   ```python
   self.pos = pos
   ```
   プレイヤーの初期位置を`pos`で指定します。

3. **速度**
   ```python
   self.key_speed = 10
   ```
   プレイヤーが移動する速さを設定します。

4. **矩形（rect）の設定**
   ```python
   self.rect = self.surface.get_frect(topleft=pos)
   ```
   読み込んだ画像の矩形情報を取得し、プレイヤーの位置情報に基づいて初期位置を設定します。

5. **衝突関連の初期値**
   ```python
   self.collision_direction = ''
   self.hit_box_rect = self.rect.inflate(0, 0)
   self.direction = pg.Vector2(0, 0)
   ```
   - `collision_direction`：衝突方向（水平か垂直か）を記録します。
   - `hit_box_rect`：衝突判定用の矩形。`inflate`を使用してサイズ調整も可能です。
   - `direction`：プレイヤーの移動方向（`Vector2`は2Dベクトル）。

---

#### **`input_with_rect`メソッド**  
プレイヤーの移動処理を行います。
1. **キー入力の検出**
   ```python
   keys = pg.key.get_pressed()
   ```
   キーボードの現在の入力状態を取得します。

2. **方向ごとの処理**
   - 左方向（`K_LEFT`）：
     ```python
     self.rect.x -= 1 * dt * self.key_speed
     ```
     x座標を負の方向に移動します。
   - 右方向（`K_RIGHT`）：
     ```python
     self.rect.x += 1 * dt * self.key_speed
     ```
     x座標を正の方向に移動します。
   - 下方向（`K_DOWN`）：
     ```python
     self.rect.y += 1 * dt * self.key_speed
     ```
     y座標を正の方向に移動します。
   - 上方向（`K_UP`）：
     ```python
     self.rect.y -= 1 * dt * self.key_speed
     ```
     y座標を負の方向に移動します。

3. **衝突方向の記録**
   ```python
   self.collision_direction = 'horizontal' または 'vertical'
   ```
   水平方向（左右）または垂直方向（上下）のどちらに移動したかを記録します。

4. **移動ベクトルの更新**
   ```python
   self.direction.x または self.direction.y
   ```
   移動方向を記録します（-1：負方向、1：正方向）。

---

#### **`collision`メソッド**  
プレイヤーとブロックの衝突処理を行います。
1. **衝突判定**
   ```python
   if block.rect.colliderect(self.rect):
   ```
   プレイヤーとブロックの矩形が重なった場合に衝突処理を実行します。

2. **衝突処理**
   - 水平方向の衝突：
     ```python
     if self.collision_direction == 'horizontal':
         if self.direction.x > 0: self.rect.right = block.rect.left
         if self.direction.x < 0: self.rect.left = block.rect.right
     ```
     プレイヤーが右に移動中（`self.direction.x > 0`）なら、ブロックの左端にプレイヤーの右端を合わせます。逆に左移動中なら、ブロックの右端にプレイヤーの左端を合わせます。

   - 垂直方向の衝突：
     ```python
     elif self.collision_direction == 'vertical':
         if self.direction.y > 0: self.rect.bottom = block.rect.top
         if self.direction.y < 0: self.rect.top = block.rect.bottom
     ```
     プレイヤーが下に移動中（`self.direction.y > 0`）なら、ブロックの上端にプレイヤーの下端を合わせます。逆に上移動中なら、ブロックの下端にプレイヤーの上端を合わせます。

---

### **要約**
- `Player`クラスは、プレイヤーの位置と移動、さらにブロックとの衝突判定を管理します。
- 移動はキーボード入力に基づき、矩形（`rect`）を操作します。
- 衝突時には、移動方向に応じてプレイヤーの位置を調整し、ブロックを通り抜けないようにします。

```py
from settings import * 
import pygame as pg

class Player:
    def __init__(self, pos):
        # イメージの読み込み
        self.surface = pg.image.load(IMAGE_PATH).convert()
        # 位置
        self.pos = pos
        # speed
        self.key_speed = 10
        # rect
        self.rect = self.surface.get_frect(topleft=pos)
        self.collision_direction = ''

        self.hit_box_rect = self.rect.inflate(0,0)
        self.direction = pg.Vector2(0,0)

    def input_with_rect(self, dt):
        # キーボード押されているかをキャッチしてくれる（キーボードイベントリスナー）
        keys = pg.key.get_pressed()
        if keys[pg.K_LEFT]:
            self.rect.x -= 1 * dt * self.key_speed
            self.collision_direction ='horizontal'
            self.direction.x = -1
        elif keys[pg.K_RIGHT]:
            self.rect.x += 1 * dt * self.key_speed
            self.collision_direction ='horizontal'
            self.direction.x = 1
        elif keys[pg.K_DOWN]:
            self.rect.y += 1 * dt * self.key_speed
            self.collision_direction ='vertical'
            self.direction.y = 1
        elif keys[pg.K_UP]:
            self.rect.y -= 1 * dt * self.key_speed
            self.collision_direction ='vertical'
            self.direction.y = -1

    def collision(self, block_list):
        for block in block_list:
            if block.rect.colliderect(self.rect):
                if self.collision_direction == 'horizontal':
                    if self.direction.x > 0: self.rect.right = block.rect.left
                    if self.direction.x < 0: self.rect.left = block.rect.right
                elif self.collision_direction == 'vertical':
                    if self.direction.y > 0: self.rect.bottom = block.rect.top
                    if self.direction.y < 0: self.rect.top = block.rect.bottom
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/576081/4f6c352a-4d69-1336-8639-b6295c67577f.png)

最終的な調整で以下のようにオブジェクト同士がぎりぎりのところで壁になっている
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/576081/9f9cbc37-b2dc-926d-b47f-c073af95031b.png)

# バグ修正
追記：
上記のプログラムには、斜め移動する時に、ワープしてしまうバグがありました。
修正版は以下になります。
```py
   def handle_input(self):
        """キーボード入力で移動処理を行う"""
        keys = pg.key.get_pressed()
        self.direction = pg.Vector2(0, 0)  # 初期化

        if keys[pg.K_LEFT]:
            self.direction.x = -1
        if keys[pg.K_RIGHT]:
            self.direction.x = 1

        if keys[pg.K_UP]:
            self.direction.y = -1
        if keys[pg.K_DOWN]:
            self.direction.y = 1
        
        self.direction = self.direction.normalize() if self.direction else self.direction

    def collision(self, block_list, dt):
        """ブロックとの衝突判定と位置調整を行う"""
        self.hit_box_rect.x += self.direction.x * self.key_speed *dt
        for block in block_list:
            if block.rect.colliderect(self.hit_box_rect):
                print(f"Collision detected: {self.hit_box_rect}, {block.rect}")
                # if self.collision_direction == 'horizontal':
                if self.direction.x > 0 :
                    self.hit_box_rect.right = block.rect.left
                if self.direction.x < 0 :
                    self.hit_box_rect.left = block.rect.right
                self.direction.x = 0

        self.hit_box_rect.y += self.direction.y * self.key_speed *dt
        for block in block_list:
            if block.rect.colliderect(self.hit_box_rect):
                print(f"Collision detected: {self.hit_box_rect}, {block.rect}")    
                # if self.collision_direction == 'vertical':
                if self.direction.y > 0: self.hit_box_rect.bottom = block.rect.top
                if self.direction.y < 0: self.hit_box_rect.top = block.rect.bottom
                self.direction.y = 0
        
        # メイン矩形をヒットボックスに同期
        self.rect.center = self.hit_box_rect.center
```