フレームレートを理解する

## フレームレートとは？

Unityを使ったゲーム開発において、フレームレートの理解は非常に重要です。

フレームレートは、オブジェクトの移動やアニメーションの滑らかさ、さらにはユーザー体験全体に大きな影響を与えます。本授業では、A地点からB地点へのオブジェクトの移動を通じて、フレームレートと時間の関係性を深く理解します。

筆者は、端的にパラパラ漫画の仕組みをベースに理解しています。
一枚一枚ノートの端を捲るたびに、処理が全て走るという事です。

本記事においては、フレームレートを意図的に変更することで、時間制御とオブジェクトの挙動がどのように連動しているかを体験的に学べます。
これからUnityを触る初学の皆様、なんとなくUnityをやってきた振り返り組の皆様、参考になれば幸いです。

### Webプログラミングでは理解しにくいフレームレートのハードルの高さ

Webプログラミングでは、ブラウザがレンダリングやタイミングの多くを自動的に管理してくれるため、開発者がハードウェアのフレームレートについて深く考える機会は少ないかもしれません。
しかし、ゲーム開発やリアルタイムアプリケーションでは、フレームレートが直接的にシステムのパフォーマンスやユーザー体験に影響を及ぼします。

フレームレートの概念を正しく理解していないと、異なるデバイスや環境で予期せぬ動作を引き起こす可能性があります。
例えば、高フレームレートの環境では動きが速くなりすぎたり、低フレームレートではカクついたりすることがあります。

これは、時間経過を適切に計算し、フレームレートに依存しないコードを書くことが求められるためです。

Webプログラミングからゲーム開発に移行する際、このハードウェアレベルでのフレームレートの理解は高いハードルとなり得ます。

本授業(記事)では、そのハードルを克服するために、実際のプログラミングとデバッグを通じてフレームレートの影響を視覚的かつ定量的に理解します。

これにより、フレームレートがゲーム内の挙動にどのように影響するかを深く学び、より堅牢で一貫性のあるアプリケーションを開発できるようになります。

## 実装スクリプト
それでは先に、コピペでも良いので、以下のスクリプトをUnityで作成してください。
内容の説明を後に行います。

**スクリプト：MoveObject.cs**

```csharp
using UnityEngine;

public class MoveObject : MonoBehaviour
{
    public Vector3 targetPosition = new Vector3(10, 0, 0);  // 目標位置
    public float speed = 5f;  // 移動速度
    public int frameRate = 1;  // フレームレート（インスペクターで変更可能）
    private Vector3 startPosition;  // 開始位置

    void Start()
    {
        // フレームレートを設定
        Application.targetFrameRate = frameRate;

        // 現在の位置を開始位置として保持
        startPosition = transform.position;
    }

    void Update()
    {
        // 前回の位置を保存
        Vector3 previousPosition = transform.position;

        // オブジェクトを目標位置に向けて移動
        transform.position = Vector3.MoveTowards(transform.position, targetPosition, speed * Time.deltaTime);

        // 移動量を計算
        float movementAmount = Vector3.Distance(transform.position, previousPosition);

        // 計算式とともにデバッグウィンドウに表示
        Debug.Log($"Movement per frame: {movementAmount} units = speed ({speed}) * Time.deltaTime ({Time.deltaTime})");
    }

    void OnDrawGizmos()
    {
        // マーカーとして赤い球を目標位置に描画
        Gizmos.color = Color.red;
        Gizmos.DrawSphere(targetPosition, 0.5f);
    }
}
```

---

**手順**

1. **シーンの準備**

   - **オブジェクトの作成**
     - ヒエラルキーで `3D Object > Cube` や `Sphere` を選択し、移動させたいオブジェクトを作成します。
     - オブジェクトをシーン内の任意の位置に配置します。この位置が開始位置となります。

2. **スクリプトの設定**

   - **スクリプトの作成**
     - `Assets` フォルダ内で右クリックし、`Create > C# Script` を選択して `MoveObject` スクリプトを作成します。
     - 上記のコードをスクリプトに貼り付けます。

   - **スクリプトのアタッチ**
     - 作成した `MoveObject` スクリプトをオブジェクトにドラッグ＆ドロップしてアタッチします。

   - **インスペクターでの設定**
     - オブジェクトを選択し、インスペクターで以下を設定します。
       - **Target Position**：目標位置（デフォルトは `(10, 0, 0)`）。
       - **Speed**：移動速度（デフォルトは `5`）。
       - **Frame Rate**：フレームレート（初期値は `1`）。

---

## スクリプト説明

### 開始位置の設定**

  - `Start` メソッド内で、オブジェクトの現在の位置を開始位置として保持します。
    ```csharp
    startPosition = transform.position;
    ```
  - **オブジェクトの位置をリセットしない**：ユーザーのご要望により、オブジェクトの位置を変更せず、そのままの位置から移動を開始します。

### オブジェクトの移動**

  - `Update` メソッドで、オブジェクト自身を目標位置に向けて移動します。
    ```csharp
    transform.position = Vector3.MoveTowards(transform.position, targetPosition, speed * Time.deltaTime);
    ```

### 移動量の計算とデバッグ表示**

  - 前回の位置と現在の位置を使って、1フレームあたりの移動量を計算します。
    ```csharp
    float movementAmount = Vector3.Distance(transform.position, previousPosition);
    ```
  - デバッグウィンドウに移動量と計算式を表示します。
    ```csharp
    Debug.Log($"Movement per frame: {movementAmount} units = speed ({speed}) * Time.deltaTime ({Time.deltaTime})");
    ```

### フレームレートの設定

  - インスペクターで指定した `frameRate` を使用して、フレームレートを設定します。
    ```csharp
    Application.targetFrameRate = frameRate;
    ```

### デバッグ用マーカーの表示

  - `OnDrawGizmos` メソッドで、目標位置に赤い球体を描画します。
    ```csharp
    Gizmos.color = Color.red;
    Gizmos.DrawSphere(targetPosition, 0.5f);
    ```

---

## 実際の挙動からの理解

1. **ゲームを再生**し、以下を確認してください。

   - オブジェクトが現在の位置（開始位置）から目標位置に向かって移動します。
   - デバッグウィンドウに、各フレームの移動量とその計算式が表示されます。

2. **デバッグログの例**

   ```
   Movement per frame: 5 units = speed (5) * Time.deltaTime (1)
   ```

   - フレームレートが `1` の場合、`Time.deltaTime` は約 `1` 秒となり、移動量は `5` ユニットになります。

3. **フレームレートの変更**

   - インスペクターで `Frame Rate` の値を変更し、`5` や `10` などに設定して再度ゲームを再生します。
   - `Time.deltaTime` と移動量がそれに応じて変化することを確認してください。

### 何が起きているか
これは、移動速度を5とした時に、１フレームでのUnit(移動量)でどれだけ移動したかを視覚的に表すためのスクリプトです。

#### 各変数の説明

- **Speed（速度）**

  - 単位：**ユニット/秒**
  - 説明：オブジェクトが1秒間に移動する距離を表します。
  - 例：`Speed = 5` の場合、オブジェクトは1秒間に5ユニット移動します。

- **Unit（移動量）**

  - 単位：**ユニット**
  - 説明：**1フレームあたり**のオブジェクトの移動距離を示します。
  - 例：`Movement per frame = 0.5` の場合、オブジェクトはそのフレームで0.5ユニット移動しました。


#### 計算方法

1フレームあたりの移動量（ユニット）は、移動速度（Speed）にそのフレームの経過時間（Time.deltaTime）を掛けたものです。

**計算式：**

Movement per frame (ユニット)} = Speed (ユニット/秒) x times x Time.deltaTime (秒)


**具体例：**

- フレームレート（Frame Rate）：10 FPS
- Time.deltaTime：0.1 秒（1秒 ÷ 10フレーム）
- Speed：5 ユニット/秒

計算すると、

Movement per frame} = 5 x times 0.1 = 0.5 x ユニット


**デバッグログの出力例：**

```
Movement per frame: 0.5 units = speed (5) * Time.deltaTime (0.1)
```

- **Speed** はオブジェクトの **速度** を示し、単位は **ユニット/秒** です。
- **Unit（Movement per frame）** は **1フレームあたりの移動距離** を示し、単位は **ユニット** です。
- 両者は **Time.deltaTime** を介して関係していますが、数値としては異なるものです。

#### 補足情報

- **Time.deltaTime**：前のフレームから現在のフレームまでの経過時間（秒）を示します。フレームレートが変わると、この値も変化します。
- **フレームレートの影響**：フレームレートが高い（FPSが大きい）と、Time.deltaTime は小さくなり、1フレームあたりの移動量も小さくなります。しかし、移動速度（Speed）が一定であれば、オブジェクトは時間経過に対して一貫した速度で移動します。

#### Time.deltaTimeとは

- **フレームレートと `Time.deltaTime` の関係**

  - フレームレートが高くなると、`Time.deltaTime` は小さくなります。
  - 移動速度は `speed * Time.deltaTime` で計算されるため、フレームレートが変化してもオブジェクトの移動速度は一定になります。

- **開始位置の変更**

  - オブジェクトの開始位置を変更したい場合は、シーンビューでオブジェクトを移動させてください。

- **移動速度の調整**

  - インスペクターで `Speed` の値を変更して、オブジェクトの移動速度を調整できます。

- **デバッグ情報の活用**

  - デバッグログとシーンビュー上のマーカーを活用して、移動の挙動を詳細に確認できます。

---

### まとめ

- **オブジェクトの現在の位置を開始位置として使用**：ユーザーのご要望に合わせて、オブジェクトの位置を変更せず、そのままの位置から移動を開始するようにしました。

- **フレームレートの調整が可能**：`frameRate` 変数をインスペクターで変更することで、フレームレートを動的に調整できます。

- **移動量と計算式のデバッグ表示**：各フレームの移動量とその計算式をデバッグウィンドウに表示することで、`Time.deltaTime` の影響を視覚的に理解できます。