Kiến thức Phần mềm

Unity Life Cycle: thứ tự thật của các hàm MonoBehaviour

Unity Life Cycle theo tài liệu Unity 6: Awake chạy một lần, OnEnable chạy mỗi lần bật, vật lý đặt ở FixedUpdate, camera bám nhân vật ở LateUpdate.

  • autocad123.vn
  • 10/10/2024

Unity life cycle là thứ tự Unity gọi các hàm sự kiện bên trong một script kế thừa MonoBehaviour. Biết thứ tự đó, bạn biết dòng lệnh của mình phải nằm ở hàm nào. Đặt nhầm hàm là nguyên nhân của phần lớn lỗi giật hình và lỗi tham chiếu rỗng mà người mới gặp.

Tóm tắt. Unity life cycle có ba chặng: khởi tạo gồm Awake, OnEnable, Start; vòng lặp gồm FixedUpdate, Update, LateUpdate; kết thúc gồm OnDisable, OnDestroy. Ba điều tài liệu chính hãng nói rõ mà phần lớn bài tiếng Việt bỏ qua. Một, Start của bất kỳ đối tượng nào cũng chỉ chạy sau khi Awake đã chạy xong ở mọi đối tượng trong cảnh. Hai, OnDestroy chỉ được gọi trên đối tượng từng được bật. Ba, thứ tự giữa hai script khác nhau không được bảo đảm nếu bạn không đặt Script Execution Order. Mọi câu trích dưới đây kiểm tháng 9/2026 trên tài liệu Unity 6, trang Scripting API bản 6000.2 và trang Order of Execution bản 6000.6.

Ba chặng của vòng đời một MonoBehaviour

Chặng một chạy lúc đối tượng vào cuộc. Chặng hai lặp lại liên tục cho tới khi đối tượng bị tắt hoặc bị huỷ. Chặng ba chạy lúc đối tượng rời cuộc.

Hầu hết các hàm bạn viết đều rơi vào một trong ba chặng này. Unity còn nhiều hàm sự kiện khác cho va chạm, cho giao diện và cho quá trình vẽ hình, nhưng tám hàm dưới đây đủ dùng cho gần như mọi dự án nhỏ.

Ba chặng của một MonoBehaviour Khởi tạo: Awake, OnEnable, Start Chạy lúc đối tượng vào cuộc Vòng lặp: lặp lại tới khi tắt FixedUpdate: nhịp vật lý cố định Update: mỗi khung hình một lần LateUpdate: sau khi mọi Update xong Kết thúc: OnDisable, OnDestroy OnDestroy chỉ gọi nếu từng được bật Awake xong ở mọi đối tượng mới tới Start

Hình 1. Ba chặng theo sơ đồ thứ tự sự kiện của Unity. Chặng giữa lặp lại mọi khung hình, hai chặng còn lại chỉ chạy lúc đối tượng vào cuộc và rời cuộc.

Chặng khởi tạo: Awake, OnEnable và Start khác nhau ở đâu

Tài liệu Unity ghi thẳng: "Awake is called only once during the lifetime of the script instance". Nghĩa là Awake chỉ chạy một lần trong đời một thực thể script. Nếu bạn tải lại cảnh thì đó là một thực thể mới, nên Awake chạy lại.

Một chi tiết ít bài nào nói. Awake vẫn chạy khi thành phần script đang bị tắt, miễn là đối tượng chứa nó đang bật. Còn nếu cả đối tượng đang tắt thì Awake chưa chạy. Nó chờ tới lúc bạn gọi SetActive(true) rồi mới chạy, và vẫn chỉ chạy đúng một lần.

OnEnable thì ngược lại. Nó chạy mỗi lần đối tượng hoặc thành phần chuyển từ tắt sang bật. Bật rồi tắt rồi bật lại ba lần thì OnEnable chạy ba lần. Vì thế đây là chỗ đúng để đăng ký lắng nghe sự kiện, còn OnDisable là chỗ đúng để huỷ đăng ký.

Start đứng sau cùng trong chặng khởi tạo. Tài liệu viết: "Start is called on the frame when a script is enabled just before any of the Update methods are called the first time". Nếu script chưa được bật lúc cảnh nạp xong, Start không chạy cùng khung hình với Awake mà chờ tới lúc được bật.

Điều đáng nhớ nhất ở chặng này. Tài liệu Unity viết: "Start is not called on any object until Awake has been called on every object in the scene". Mọi Awake trong cảnh chạy xong hết rồi mới tới Start đầu tiên. Từ đó ra một quy tắc thực dụng: tự chuẩn bị mình trong Awake, còn đi tìm đối tượng khác thì để trong Start.

FixedUpdate, Update, LateUpdate: đặt nhầm là giật hình

Trong mỗi khung hình, Unity chạy vòng vật lý trước rồi mới tới phần logic. Sơ đồ thứ tự chính hãng xếp FixedUpdate và bước mô phỏng vật lý lên trước, sau đó tới Update, rồi tới LateUpdate.

FixedUpdate không chạy theo khung hình. Tài liệu ghi: "FixedUpdate may be called zero, one, or multiple times per frame depending on the frame rate and simulation needs". Khoảng cách giữa hai lần gọi là Time.fixedDeltaTime. Trang hướng dẫn tối ưu vật lý của Unity ghi giá trị mặc định là 0,02 giây, tức 50 lần mỗi giây.

Vậy nên mọi thứ đụng tới Rigidbody đều thuộc về FixedUpdate: cộng lực, đặt vận tốc, đẩy nhân vật. Đặt chúng trong Update thì lực được cộng theo nhịp khung hình. Máy mạnh cộng nhiều lần hơn máy yếu, và kết quả mô phỏng lệch nhau giữa hai máy.

LateUpdate chạy sau khi mọi Update của mọi script đã xong. Tài liệu nêu thẳng ví dụ: "a follow camera should always be implemented in LateUpdate because it tracks objects that might have moved inside Update". Camera đặt trong Update có thể đọc vị trí nhân vật khi nhân vật chưa kịp di chuyển trong khung hình đó, và bạn thấy hình rung nhẹ.

Việc này đặt vào hàm nào FixedUpdate: nhịp vật lý cố định Lực, vận tốc, mọi thứ đụng Rigidbody Update: mỗi khung hình một lần Đọc phím, đếm giờ, logic trò chơi LateUpdate: sau mọi Update Camera bám nhân vật, kéo xương theo Đặt camera ở Update thì rung Camera đọc phải vị trí cũ của nhân vật Chọn theo việc, không theo thói quen

Hình 2. Ba hàm lặp và loại công việc thuộc về từng hàm. Khung đỏ là lỗi hay gặp nhất khi làm camera đuổi theo nhân vật.

Unity life cycle trả lời câu hỏi: dòng này đặt ở hàm nào

Đây là cách dùng thực tế nhất của sơ đồ vòng đời. Thay vì học thuộc, bạn hỏi ngược hai câu: việc này cần chạy bao nhiêu lần, và cần chạy sau việc gì.

Cần chạy một lần và không phụ thuộc ai thì đặt ở Awake. Cần chạy một lần nhưng phải thấy được đối tượng khác thì đặt ở Start. Cần chạy lại mỗi lần bật thì đặt ở OnEnable. Cần theo nhịp vật lý thì đặt ở FixedUpdate. Cần theo nhịp hình thì đặt ở Update. Cần chạy sau tất cả thì đặt ở LateUpdate.

Time.deltaTime và lý do phải nhân với nó

Update chạy mỗi khung hình, mà số khung hình mỗi giây thì đổi liên tục. Nếu bạn cộng thêm 0,1 vào vị trí sau mỗi lần Update, nhân vật sẽ đi nhanh trên máy mạnh và đi chậm trên máy yếu.

Time.deltaTime là "the interval in seconds from the last frame to the current one". Nhân tốc độ với nó là đổi đơn vị từ "mỗi khung hình" sang "mỗi giây". Kết quả khi đó giống nhau trên mọi máy.

Trong FixedUpdate thì không cần phân vân. Tài liệu ghi rõ: "When this is called from inside MonoBehaviour.FixedUpdate, it returns Time.fixedDeltaTime". Gọi Time.deltaTime ở đó vẫn đúng, vì Unity tự trả về bước thời gian cố định.

Thứ tự giữa hai script khác nhau không được bảo đảm

Đây là chỗ tài liệu nhập môn hay bỏ. Sơ đồ vòng đời chỉ nói thứ tự các hàm bên trong một script. Nó không nói script nào chạy Update trước, khi cảnh của bạn có ba mươi script cùng có Update.

Unity có bảng Script Execution Order trong Project Settings. Tài liệu mô tả: script chạy từ giá trị nhỏ nhất tới lớn nhất, ví dụ -200, -100, -50, 50, 100, 200. Script không nằm trong bảng thì chạy ở khe Default Time, sau nhóm số âm và trước nhóm số dương.

Tài liệu cũng lưu ý bảng này khác với thứ tự các hàm bên trong một script và không làm đổi thứ tự đó. Nó chỉ cho phép bảo đảm Awake của script này luôn chạy trước Awake của script kia. Nếu bạn gặp lỗi lúc có lúc không, hãy nghi ngờ chỗ này trước khi nghi ngờ Unity.

Awake xong hết rồi mới tới Start Đối tượng A Awake OnEnable Đối tượng B Awake OnEnable Rồi mới tới Start của cả hai Nên Start là chỗ an toàn để tìm nhau Còn A trước hay B trước? Thứ tự hai script không bảo đảm Muốn chắc thì đặt Execution Order Lỗi lúc có lúc không thường nằm ở đây

Hình 3. Unity bảo đảm chặng nào trước chặng nào, nhưng không bảo đảm script nào trước script nào trong cùng một chặng.

Chặng kết thúc: OnDisable trước, OnDestroy sau

Khi bạn tắt một đối tượng thì OnDisable chạy. Khi bạn huỷ nó, Unity tắt nó trước rồi mới huỷ, nên bạn thấy OnDisable chạy trước rồi tới OnDestroy.

Một câu trong tài liệu đáng ghi lại: "OnDestroy is only called on GameObjects that have previously been active". Một đối tượng nằm trong cảnh nhưng chưa bao giờ được bật thì lúc thoát cảnh sẽ không có OnDestroy. Nếu bạn đặt code dọn dẹp ở đó, code sẽ không chạy và bạn không nhận được thông báo lỗi nào.

Tài liệu cũng nhắc rằng trên di động, khi hệ điều hành treo ứng dụng, Unity có thể không kịp gọi hàm này. Đừng dựa vào OnDestroy để lưu dữ liệu quan trọng.

Lỗi thường gặp

Bảng dưới gom các lỗi mà người mới học Unity life cycle hay vấp phải. Cột giữa là lý do, vì biết lý do thì lần sau bạn tự đoán được.

Hiện tượngVì saoCách xử lý
Tham chiếu bị rỗng ngay khi chạyĐi tìm đối tượng khác trong Awake, lúc đó nó có thể chưa xong phần khởi tạo của mìnhChuyển đoạn đi tìm sang Start
Nhân vật đi nhanh chậm khác nhau giữa hai máyQuên nhân với Time.deltaTime trong UpdateNhân tốc độ với Time.deltaTime
Vật lý giật hoặc xuyên qua tườngCộng lực cho Rigidbody trong Update thay vì FixedUpdateChuyển phần vật lý sang FixedUpdate
Camera rung nhẹ khi bám nhân vậtCamera cập nhật trong Update nên đọc phải vị trí cũChuyển camera sang LateUpdate
Đăng ký sự kiện bị lặp nhiều lầnĐăng ký trong Awake hoặc Start nhưng đối tượng bị bật tắt nhiều lầnĐăng ký trong OnEnable, huỷ đăng ký trong OnDisable
Code dọn dẹp trong OnDestroy không chạyĐối tượng chưa từng được bật nên OnDestroy không được gọiDọn dẹp ở chỗ khác, hoặc bảo đảm đối tượng từng được bật
Lỗi lúc có lúc không giữa hai scriptThứ tự giữa các script không được bảo đảmĐặt Script Execution Order trong Project Settings

Câu hỏi thường gặp

Unity life cycle có bao nhiêu hàm phải nhớ?

Sơ đồ chính hãng có hàng chục hàm, gồm cả nhóm hàm vẽ hình và nhóm hàm va chạm. Nhưng để làm một trò chơi nhỏ, tám hàm trong bài này đã đủ. Học thêm khi gặp đúng nhu cầu thì nhớ lâu hơn học thuộc cả sơ đồ.

Awake hay Start, nên dùng cái nào?

Dùng Awake cho việc chuẩn bị bên trong chính đối tượng đó, ví dụ lấy thành phần bằng GetComponent. Dùng Start cho việc cần đối tượng khác đã sẵn sàng. Quy tắc này đến thẳng từ câu tài liệu nói mọi Awake chạy xong rồi mới tới Start.

Vì sao có lúc FixedUpdate chạy hai lần trong một khung hình?

Vì nó bù cho phần thời gian bị chậm. Khi một khung hình kéo dài hơn bước cố định, Unity gọi bù để mô phỏng vật lý đuổi kịp. Đó là lý do tài liệu ghi hàm này có thể chạy không lần, một lần hoặc nhiều lần trong một khung hình.

Coroutine nằm ở đâu trong vòng đời?

Coroutine chạy xen vào vòng lặp chứ không phải một chặng riêng. Điểm nó chạy tiếp phụ thuộc vào lệnh yield bạn dùng. Ví dụ WaitForFixedUpdate nối vào cuối vòng vật lý, còn WaitForEndOfFrame nối vào cuối khung hình. Sơ đồ thứ tự chính hãng vẽ các điểm này ở đúng vị trí của chúng.