Unity入門:ゲームループと「フレーム」を正しく理解する
本記事のゴール
初学者が フレーム(frame) と 時間(deltaTime / fixedDeltaTime) を正しく理解し、フレームレートに依存しない動きと 物理挙動の安定化 ができるようになること。
- 1. 1. そもそも「フレーム」とは?
 - 2. 2. Unityの基本ループと主要メソッド
 - 3. 3. 時間の扱い:deltaTime ファミリー
 - 4. 4. フレームレート非依存の移動(Update版)
 - 5. 5. 物理は FixedUpdate+Rigidbody で
 - 6. 6. カメラは LateUpdate で「後追い」
 - 7. 7. アニメーションとフレーム
 - 8. 8. コルーチンと時間
 - 9. 9. VSync とフレームレート制御
 - 10. 10. FPSを測るシンプルな表示
 - 11. 11. 典型的な落とし穴と対策
 - 12. 12. 実験して理解を深める(ミニ課題)
 - 13. 13. まとめ(チェックリスト)
 - 14. 付録:最小サンプル(非物理・物理・カメラ)
 
1. そもそも「フレーム」とは?
- ゲームは「1枚の画」を 連続して更新・描画 することで動いて見えます。
 - この「1回の更新+描画」の単位が フレーム、1秒間に何フレーム描けたかが FPS(Frames Per Second)。
- 60 FPS … 1秒に60回更新(約16.67msごと)
 - 30 FPS … 1秒に30回更新(約33.33msごと)
 
 
ポイント:PC性能や負荷でFPSは変動します。フレーム数に依存した計算(例:毎フレーム5px動かす)をすると、PCごとに速度が変わってしまいます。
2. Unityの基本ループと主要メソッド
Unity(Unity 6含む)では、代表的に次の順序で処理が進みます(概念図):
- FixedUpdate(必要回数)… 物理(Rigidbody/Collider)用の固定時間ステップ
 - Update … 毎フレーム1回
 - LateUpdate … Updateの後処理(カメラ追従など)
 - 描画(レンダリング)
 
それぞれの役割:
- Update:入力、UI、非物理の移動やロジック
 - FixedUpdate:物理挙動(Rigidbody の移動、力の適用)
 - LateUpdate:追従や後追い計算(キャラの位置が確定した後にカメラを寄せる、など)
 
3. 時間の扱い:deltaTime ファミリー
- Time.deltaTime「直近のフレーム間の経過時間(秒)」。UpdateやLateUpdateで使う。
 - Time.fixedDeltaTime「物理ステップの固定時間(秒)」。FixedUpdateで使う。既定値は 0.02(= 50Hz)。
 - Time.unscaledDeltaTimeTime.timeScale の影響を受けない経過時間。ポーズ中のUIアニメ等で使う。
 - Time.timeScaleゲーム全体の時間の速さ。0でポーズ、0.5で半分の速度。物理は基本的にこれに追従。
 
重要:Update では Time.deltaTime、FixedUpdate では Time.fixedDeltaTime を使うのが原則。
4. フレームレート非依存の移動(Update版)
悪い例(PCによって速度が変わる):
// 毎フレーム 5 ユニット動く → FPSが高いほど速くなる
transform.Translate(Vector3.forward * 5f);
良い例(時間で正規化):
public float speed = 5f;
void Update()
{
    // 1秒あたり speed ユニット移動(FPSに依存しない)
    transform.Translate(Vector3.forward * speed * Time.deltaTime);
}
5. 物理は FixedUpdate+Rigidbody で
物理演算は 一定間隔(fixedDeltaTime)で積分されるため、Rigidbody の移動は FixedUpdate で行います。
3D(Rigidbody)の例
using UnityEngine;
[RequireComponent(typeof(Rigidbody))]
public class PlayerMover : MonoBehaviour
{
    public float speed = 3f;
    Rigidbody _rb;
    Vector3 _input;
    void Awake() => _rb = GetComponent<Rigidbody>();
    void Update()
    {
        // 入力はUpdateで取得(毎フレーム変わるため)
        float h = Input.GetAxisRaw("Horizontal");
        float v = Input.GetAxisRaw("Vertical");
        _input = new Vector3(h, 0, v).normalized;
    }
    void FixedUpdate()
    {
        // 実際の移動はFixedUpdateで、時間は fixedDeltaTime を使う
        Vector3 delta = _input * speed * Time.fixedDeltaTime;
        _rb.MovePosition(_rb.position + delta);
    }
}
2D(Rigidbody2D)の例
using UnityEngine;
[RequireComponent(typeof(Rigidbody2D))]
public class PlayerMover2D : MonoBehaviour
{
    public float speed = 3f;
    Rigidbody2D _rb;
    Vector2 _input;
    void Awake() => _rb = GetComponent<Rigidbody2D>();
    void Update()
    {
        float h = Input.GetAxisRaw("Horizontal");
        float v = Input.GetAxisRaw("Vertical");
        _input = new Vector2(h, v).normalized;
    }
    void FixedUpdate()
    {
        Vector2 delta = _input * speed * Time.fixedDeltaTime;
        _rb.MovePosition(_rb.position + delta);
    }
}
よくあるミス:Rigidbody を transform.position += … で動かす(補間が乱れ、貫通やガタつきの原因)。MovePosition/MoveRotation を使いましょう。
6. カメラは LateUpdate で「後追い」
キャラクターの移動が終わった 後 にカメラを寄せるとブレが減ります。
public class FollowCamera : MonoBehaviour
{
    public Transform target;
    public Vector3 offset = new Vector3(0, 5, -7);
    public float damping = 6f;
    void LateUpdate()
    {
        if (!target) return;
        // 指数補間(フレームレート非依存のなめらか補間)
        float t = 1f - Mathf.Exp(-damping * Time.deltaTime);
        Vector3 desired = target.position + offset;
        transform.position = Vector3.Lerp(transform.position, desired, t);
        transform.LookAt(target);
    }
}
7. アニメーションとフレーム
Animator の Update Mode は3種類:
- Normal:timeScale の影響を受ける(通常)
 - Animate Physics:物理ステップに同期(Rigidbody連動のキャラ向け)
 - Unscaled Time:timeScale の影響を受けない(ポーズ中にUIだけ動かしたい等)
 
// 例:物理と同期させたい
animator.updateMode = AnimatorUpdateMode.AnimatePhysics;
8. コルーチンと時間
- yield return null; … 次の フレーム まで待つ(スケール影響なし)
 - yield return new WaitForSeconds(t); … スケール影響あり(timeScale=0 だと止まる)
 - yield return new WaitForSecondsRealtime(t); … スケール影響なし(ポーズ中も動く)
 
IEnumerator Blink(TextMeshProUGUI label)
{
    while (true)
    {
        label.enabled = !label.enabled;
        yield return new WaitForSecondsRealtime(0.5f); // ポーズ中も点滅
    }
}
9. VSync とフレームレート制御
- VSync(垂直同期):画面リフレッシュと描画を同期。画面のティアリングを防止。Quality 設定の VSync Count で制御。
 - Application.targetFrameRate:任意の上限FPSを指定(VSyncが有効だと VSyncが優先 されることに注意)。
 
void Start()
{
    QualitySettings.vSyncCount = 0;          // VSync無効(検証用)
    Application.targetFrameRate = 60;        // 60FPSを狙う
}
実機(Android/iOS)やエディタでは挙動が異なる場合あり。最終ターゲット環境で検証 すること。
10. FPSを測るシンプルな表示
開発中の計測用(OnGUIはデバッグ限定でOK):
using UnityEngine;
public class FpsMeter : MonoBehaviour
{
    float _accum, _time;
    int _frames;
    float _fps;
    void Update()
    {
        _frames++;
        _accum += Time.unscaledDeltaTime; // ポーズの影響を受けない
        if (_accum >= 0.5f)               // 0.5秒ごとに更新
        {
            _fps = _frames / _accum;
            _frames = 0;
            _accum = 0f;
        }
    }
    void OnGUI()
    {
        GUI.Label(new Rect(10, 10, 150, 30), $"{_fps:F1} FPS");
    }
}
11. 典型的な落とし穴と対策
- UpdateでRigidbodyを位置移動 → FixedUpdate+Move系 に切り替える
 - deltaTimeを掛け忘れ → 速度×時間 の形に統一
 - FixedUpdateで Time.deltaTime を使う → Time.fixedDeltaTime に修正
 - ポーズ中の演出が止まらない/止まってしまう
- 止めたい:WaitForSeconds(スケールに従う)
 - 止めたくない:unscaledDeltaTime / WaitForSecondsRealtime
 
 - 急な負荷増でワープ(大きすぎるdeltaTime)
- Time.maximumDeltaTime を小さめに設定して一度に進む最大時間を制限する方法もある(高度な対策)。
 
 
12. 実験して理解を深める(ミニ課題)
- VSyncのON/OFF と targetFrameRate を切り替えて FPSの変化を観察
 - Updateで移動 と FixedUpdateでRigidbody移動 を比べ、段差での貫通/ガタつきを検証
 - timeScale=0 にして、WaitForSeconds と WaitForSecondsRealtime の違いを体感
 - カメラ追従を Update と LateUpdate で切り替え、ブレの違いを比較
 
13. まとめ(チェックリスト)
- 非物理の移動は Update × Time.deltaTime
 - 物理挙動は FixedUpdate × Time.fixedDeltaTime + Rigidbody.MovePosition
 - カメラや追従は LateUpdate
 - ポーズやUIエフェクトは unscaledDeltaTime / WaitForSecondsRealtime
 - VSync と Application.targetFrameRate の関係を理解
 - まずは ターゲット端末でFPSを計測、体感と数値で確認
 
付録:最小サンプル(非物理・物理・カメラ)
非物理の一定速度移動(キーボード)
using UnityEngine;
public class SimpleMove : MonoBehaviour
{
    public float speed = 4f;
    void Update()
    {
        float h = Input.GetAxisRaw("Horizontal");
        float v = Input.GetAxisRaw("Vertical");
        Vector3 dir = new Vector3(h, 0, v).normalized;
        transform.position += dir * speed * Time.deltaTime;
    }
}
物理での等速移動(Rigidbody)
using UnityEngine;
[RequireComponent(typeof(Rigidbody))]
public class PhysicsMove : MonoBehaviour
{
    public float speed = 4f;
    Rigidbody rb;
    Vector3 dir;
    void Awake() => rb = GetComponent<Rigidbody>();
    void Update()
    {
        dir = new Vector3(Input.GetAxisRaw("Horizontal"), 0, Input.GetAxisRaw("Vertical")).normalized;
    }
    void FixedUpdate()
    {
        rb.MovePosition(rb.position + dir * speed * Time.fixedDeltaTime);
    }
}
LateUpdateでの滑らかカメラ追従
using UnityEngine;
public class SmoothFollow : MonoBehaviour
{
    public Transform target;
    public Vector3 offset = new Vector3(0, 5, -8);
    public float damping = 6f;
    void LateUpdate()
    {
        if (!target) return;
        float t = 1f - Mathf.Exp(-damping * Time.deltaTime);
        transform.position = Vector3.Lerp(transform.position, target.position + offset, t);
        transform.LookAt(target);
    }
}





ディスカッション
コメント一覧
まだ、コメントがありません