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);
}
}
ディスカッション
コメント一覧
まだ、コメントがありません