Unity Term Book
Scripting & Lifecycle

Update & Lifecycle

Vòng lặp game chính của Unity: Update mỗi frame, FixedUpdate theo Physics timestep cố định, LateUpdate sau mọi Update — hãy chọn đúng hook cho từng việc.

Hãy tưởng tượng...

Update = đồng hồ kim chạy nhanh (tùy FPS) — dùng cho mắt thường thấy được: di chuyển, input, AI. FixedUpdate = đồng hồ điện tử nhịp đều (0.02s/lần) — dùng cho vật lý để không bị jitter. LateUpdate = chạy sau cùng — Camera đợi tất cả vật thể di chuyển xong mới đi theo, tránh giật hình.

Khái niệm chi tiết

Update() gọi mỗi frame — tần suất thay đổi theo FPS (30, 60, 120…). Dùng Time.deltaTime để nhân với mọi giá trị di chuyển/tốc độ — đảm bảo kết quả như nhau dù FPS khác nhau.

FixedUpdate() gọi theo Time.fixedDeltaTime (mặc định 0.02s = 50 lần/giây). Không phụ thuộc FPS. Dùng cho: Rigidbody.AddForce(), Rigidbody.velocity, Physics queries. Một frame render có thể ứng với 0, 1, hoặc nhiều FixedUpdate.

LateUpdate() gọi sau tất cả Update() của mọi script trong frame đó. Lý tưởng cho Camera follow (đảm bảo player đã di chuyển), IK (Inverse Kinematics), và bất kỳ logic nào cần “đọc” kết quả sau Update.

OnGUI() cũ, Update vs coroutine: logic chạy một lần hoặc có delay → dùng Coroutine thay Update để tránh check điều kiện mỗi frame.

Sơ đồ: Thứ tự trong một Frame

⏰ Physics Timestep Accumulation

FixedUpdate() × N

N = số lần timestep tích lũy từ frame trước — thường 0 hoặc 1, có thể nhiều hơn nếu lag

📥 Input Processing

Update() — tất cả script

Input, AI, logic game, animation trigger

LateUpdate() — tất cả script

Camera follow, IK, post-Update logic

🎨 Rendering (Culling → Drawing → PostFX)

Hướng dẫn thực hành

1

Luôn nhân Time.deltaTime trong Update

transform.position += speed * Time.deltaTime * direction — tốc độ đơn vị/giây, không phụ thuộc FPS.

2

Physics forces → FixedUpdate

Đặt AddForce(), velocity trong FixedUpdate. Đọc Input trong Update rồi apply trong FixedUpdate.

3

Camera follow → LateUpdate

Camera script dùng LateUpdate để đảm bảo player đã move xong, tránh camera lag 1 frame.

4

Xóa Update không cần thiết

Ngay cả Update rỗng gây overhead. Dùng Event hoặc Coroutine cho logic không cần chạy mỗi frame.

Trình mô phỏng tương tác

Nhấn Run để xem từng bước trong vòng lặp frame. Điều chỉnh FPS và Physics rate.

FixedUpdate

0

calls

Update

0

calls

LateUpdate

0

calls

Render

0

frames

Current step: Idle
Frame: 0|Time: 0.00s|Phase: Stopped

Ví dụ Code

Cơ bản

Phân chia đúng logic giữa Update, FixedUpdate và LateUpdate.

public class PlayerMovement : MonoBehaviour
{
  [SerializeField] private float speed      = 5f;
  [SerializeField] private float jumpForce  = 8f;
  private Rigidbody _rb;
  private Vector3   _moveDir;
  private bool      _jumpRequest;

  void Awake() => _rb = GetComponent<Rigidbody>();

  void Update()
  {
      // Input — đọc mỗi frame, lưu để FixedUpdate dùng
      float h = Input.GetAxisRaw("Horizontal");
      float v = Input.GetAxisRaw("Vertical");
      _moveDir = new Vector3(h, 0, v).normalized;

      if (Input.GetButtonDown("Jump")) _jumpRequest = true;
  }

  void FixedUpdate()
  {
      // Apply lực vật lý — không dùng Time.deltaTime ở đây
      _rb.velocity = new Vector3(_moveDir.x * speed, _rb.velocity.y, _moveDir.z * speed);

      if (_jumpRequest)
      {
          _rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
          _jumpRequest = false;
      }
  }
}

public class CameraFollow : MonoBehaviour
{
  [SerializeField] private Transform target;
  [SerializeField] private Vector3   offset = new Vector3(0, 5, -8);

  void LateUpdate()
  {
      // Chạy sau target.Update() — vị trí target đã finalize
      transform.position = Vector3.Lerp(transform.position,
          target.position + offset, Time.deltaTime * 5f);
  }
}

Ví dụ Code

Nâng cao

Dùng Coroutine thay Update cho logic không cần chạy mỗi frame.

public class EnemyAI : MonoBehaviour
{
  private Transform _player;
  private float _attackCooldown = 2f;

  void Start()
  {
      _player = GameObject.FindWithTag("Player").transform;
      // AI tick mỗi 0.2s thay vì mỗi frame → tiết kiệm CPU
      StartCoroutine(AIUpdateLoop());
      StartCoroutine(AttackLoop());
  }

  // Chạy logic AI 5 lần/giây thay vì 60 lần/giây
  private IEnumerator AIUpdateLoop()
  {
      var wait = new WaitForSeconds(0.2f);
      while (true)
      {
          UpdatePathfinding();
          yield return wait;
      }
  }

  private IEnumerator AttackLoop()
  {
      while (true)
      {
          yield return new WaitForSeconds(_attackCooldown);
          if (Vector3.Distance(transform.position, _player.position) < 3f)
              Attack();
      }
  }
}

📌 Ghi nhớ nhanh

  • * Time.deltaTime cho mọi di chuyển trong Update
  • Physics force/velocity → FixedUpdate (không cần deltaTime)
  • Camera follow → LateUpdate (đợi player di chuyển xong)
  • Logic thưa hơn → Coroutine + WaitForSeconds

⚠️ Lỗi thường gặp

  • ❌ Di chuyển transform trong FixedUpdate không nhân Time.fixedDeltaTime

    Tốc độ phụ thuộc physics rate — không nhất quán

    ✅ Dùng * Time.fixedDeltaTime hoặc dùng MovePosition()

  • ❌ Đọc Input trong FixedUpdate

    GetKeyDown có thể bị bỏ lỡ nếu frame rất nhanh

    ✅ Đọc Input trong Update, lưu biến, apply trong FixedUpdate