Unity Term Book
Scripting & Lifecycle

MonoBehaviour

MonoBehaviour là lớp cơ sở của mọi script Unity — cung cấp vòng đời (lifecycle), truy cập Component, và cầu nối giữa code C# của bạn với Unity Engine.

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

MonoBehaviour giống như hợp đồng lao động với Unity. Khi ký hợp đồng (kế thừa MonoBehaviour), nhân viên (script) được tự động gọi vào các buổi họp định kỳ: họp khai mạc (Awake), họp ngày đầu (Start), họp hàng ngày (Update), họp vật lý (FixedUpdate), và họp kết thúc (OnDestroy). Không kế thừa = không được gọi, dù có định nghĩa method.

Khái niệm chi tiết

MonoBehaviour kế thừa từ Behaviour → Component → Object. Việc kế thừa cho phép Unity gọi các magic methods (Awake, Start, Update…) tự động thông qua reflection — bạn không cần override hay gọi base method.

Lifecycle quan trọng: Awake chạy ngay khi object được tạo (kể cả disabled), Start chạy frame đầu sau khi object được enable, Update mỗi frame, FixedUpdate theo Physics timestep (mặc định 0.02s).

Hiệu năng: Empty Update() vẫn tốn chi phí gọi hàm qua reflection. Xóa Update rỗng. Dùng enabled = false để dừng lifecycle thay vì destroy toàn bộ object.

Hạn chế: Không thể dùng constructor ( new MyScript()) — phải dùng AddComponent<T>(). Không thread-safe — toàn bộ MonoBehaviour API chỉ chạy trên main thread.

Sơ đồ: Chuỗi kế thừa & Magic Methods

Object
Component
Behaviour
MonoBehaviour
YourScript

Awake()

Khởi tạo nội bộ · Trước Start

Start()

Setup ban đầu · Frame đầu tiên

Update()

Logic hàng frame · Input, AI...

FixedUpdate()

Physics · 0.02s/lần

LateUpdate()

Sau tất cả Update · Camera follow

OnEnable()

Khi object được kích hoạt

OnDisable()

Khi object bị vô hiệu hóa

OnDestroy()

Khi object bị hủy

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

1

Tạo script C# mới

Assets → Create → C# Script. Đặt tên đúng với class name để tránh lỗi compile.

2

Dùng Awake cho khởi tạo nội bộ

Awake để cache component references (_rb = GetComponent<Rigidbody>()). Tránh phụ thuộc vào object khác ở đây.

3

Dùng Start cho setup liên-object

Start khi cần tham chiếu đến object khác (đảm bảo Awake của họ đã chạy xong).

4

Phân biệt Update vs FixedUpdate

Input và AI → Update. Lực vật lý (AddForce, velocity) → FixedUpdate để tránh glitch.

5

Dọn dẹp trong OnDestroy

Hủy subscription event, Coroutine, timer trong OnDestroy để tránh memory leak.

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

Nhấn Play Scene để xem thứ tự gọi lifecycle methods. Nhấn Disable hoặc Destroy để xem các bước tiếp theo.

Lifecycle Timeline

Console

Nhấn Play để bắt đầu...

Frame: |State: Inactive|Chờ Play...

Ví dụ Code

Cơ bản

Cấu trúc MonoBehaviour chuẩn với đầy đủ lifecycle methods và lý do dùng từng cái.

using UnityEngine;

public class PlayerController : MonoBehaviour
{
  [SerializeField] private float speed = 5f;
  private Rigidbody _rb;
  private Vector3   _inputDir;

  void Awake()
  {
      // Cache component — không phụ thuộc script khác
      _rb = GetComponent<Rigidbody>();
  }

  void Start()
  {
      // Setup cần đảm bảo Awake của GameManager đã chạy
      GameManager.Instance.RegisterPlayer(this);
  }

  void Update()
  {
      // Đọc input mỗi frame
      float h = Input.GetAxisRaw("Horizontal");
      float v = Input.GetAxisRaw("Vertical");
      _inputDir = new Vector3(h, 0, v).normalized;
  }

  void FixedUpdate()
  {
      // Di chuyển physics — không dùng Time.deltaTime ở đây
      _rb.MovePosition(_rb.position + _inputDir * speed * Time.fixedDeltaTime);
  }

  void OnDestroy()
  {
      GameManager.Instance.UnregisterPlayer(this);
  }
}

Ví dụ Code

Nâng cao

Tối ưu hoá: tránh Update rỗng, dùng OnEnable/OnDisable thay Enable flag thủ công.

using UnityEngine;
using System;

// Component có lifecycle đầy đủ — kết hợp với Event System
public class HealthComponent : MonoBehaviour
{
  [SerializeField] private int maxHp = 100;
  private int _currentHp;

  public event Action<int,int> OnHealthChanged;  // (current, max)
  public event Action           OnDied;

  void Awake() => _currentHp = maxHp;

  void OnEnable()
  {
      // Subscribe khi component được kích hoạt
      GameEvents.OnDamageDealt += TakeDamage;
  }

  void OnDisable()
  {
      // Unsubscribe ngay — tránh gọi khi disabled
      GameEvents.OnDamageDealt -= TakeDamage;
  }

  public void TakeDamage(int amount)
  {
      _currentHp = Mathf.Max(0, _currentHp - amount);
      OnHealthChanged?.Invoke(_currentHp, maxHp);
      if (_currentHp == 0) OnDied?.Invoke();
  }
}

📌 Ghi nhớ nhanh

  • Awake = nội bộ, Start = liên-object, Update = hàng frame
  • Physics → FixedUpdate, không Update
  • Empty Update() vẫn tốn chi phí — xóa nếu không dùng
  • OnEnable/OnDisable dùng để subscribe/unsubscribe event

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

  • ❌ GetComponent trong Update()

    Chậm — tìm kiếm component mỗi frame, rất tốn kém

    ✅ Cache trong Awake: _rb = GetComponent<Rigidbody>()

  • ❌ Dùng constructor: new PlayerCtrl()

    MonoBehaviour không cho phép dùng new — sẽ throw warning

    ✅ go.AddComponent<PlayerCtrl>()