3. Logic thực thi chương trình
Ở bài học trước, chúng ta đã biết viết và chạy chương trình đầu tiên (“Hello World”) trong Unity.
Qua quá trình viết và chạy chương trình, bạn cũng đã biết được:
- Dùng Unity Hub để tạo dự án (cần phải đăng nhập)
- Dùng Unity Editor: để tạo ra các thành phần của một ứng dụng (ví dụ tạo một script, là đoạn mã C#); để dịch và chạy chương trình
- Dùng VS Code để soạn mã nguồn
- Làm quen với một số thuật ngữ Asset, Main Camera, Script; thư viện UnityEngine, lớp MonoBehaviour; hàm Start(), hàm Debug.Log()
Bài học này, chúng ta sẽ tìm hiểu về logic thực thi của một chương trình game trong Unity.
3.1 Vòng đời Script trong Unity
Trong lập trình, một trong những kỹ năng bạn cần có là: biết được luồng xử lý của chương trình. Nghĩa là, khi chương trình bắt đầu chạy, thì nó sẽ bắt đầu từ tập tin nào, bắt đầu từ dòng mã nào, và luồng xử lý sẽ chạy như thế nào?
Theo logic thông thường, chương trình sẽ bắt đầu chạy một tập tin mã nguồn cụ thể nào đó. Trong tập tin, mã nguồn sẽ được đọc và xử lý theo thứ tự từ trên xuống dưới, từ trái qua phải. Như ở bài học trước, chương trình sẽ bắt đầu chạy tập tin mã nguồn FirstScript.cs. Trong đó, hàm Start() sẽ được gọi và thực thi.
Tuy nhiên, trong Unity, logic thực thi một chương trình có diễn ra như vậy không? Hay nó sẽ diễn ra như thế nào? Chúng ta cùng tìm hiểu.
Vì tính đặc thù của một chương trình game, nên Unity đã thiết kế một cơ chế thực thi riêng, gọi là Vòng đời Script (Script Lifecycle).
Script là đoạn mã thực thi.
Vòng đời của một Script được chia thành 3 giai đoạn, gồm:
- Khởi tạo (initialization)
- Cập nhật (looping update)
- Dọn dẹp (cleanup)
Thông tin cụ thể hơn về 3 giai đoạn được thể hiện trong bảng sau:
Unity sử dụng Vòng đời Script để đảm bảo các hành vi phức tạp của trò chơi (như khởi tạo, vật lý, logic game, và hiển thị) được thực hiện một cách có cấu trúc, chính xác về thời gian và đồng bộ.
Vòng đời Script gồm 8 hàm: Awake(), OnEnable(), Start(), FixedUpdate(), Update(), LateUpdate(), OnDisable(), OnDestroy().
3.2 Thực hành với Vòng đời Script
Để làm quen với Vòng đời Script và các hàm của nó, chúng ta cùng làm ứng dụng đơn giản sau. Chúng ta sẽ tạo một script duy nhất để theo dõi thời điểm mỗi hàm của Vòng đời Script được gọi.
- Tạo dự án: Trong Unity Hub, tạo dự án mới, template là Universal 2D, đặt tên là ScriptLifecycleProject. Mở dự án vừa tạo trong Unity Editor.
- Tạo đối tượng: Trong Unity Editor, cửa sổ Hierarchy (thường nằm bên trái), chuột phải vào Main Camera > chọn Create Empty. Hệ thống sẽ tạo ra một GameObject mới, bạn đặt tên cho nó là LifeCycleTracker_GO
- Tạo Script: Trong thư mục Assets, tạo một Script C# mới và đặt tên là LifecycleTracker
- Viết mã C#: Mở script LifecycleTracker.cs và viết đoạn mã sau:
[LifecyleTracker.cs]
using UnityEngine;
using UnityEngine.InputSystem;
public class LifecycleTracker : MonoBehaviour
{
// *** 1. GIAI ĐOẠN KHỞI TẠO ***
void Awake()
{
// Chạy đầu tiên, ngay cả khi script bị vô hiệu hóa
// Dùng để khởi tạo tham chiếu nội bộ (ví dụ: tìm Rigidbody)
Debug.Log("1. [INIT] Awake: Script đã được tải. (Chạy ngay)");
}
void OnEnable()
{
// Chạy mỗi khi GameObject HOẶC Script được bật (Enabled)
Debug.Log("2. [INIT] OnEnable: GameObject/Script được bật.");
}
void Start()
{
// Chạy sau Awake và OnEnable, ngay trước frame Update đầu tiên
// Dùng để tương tác với các đối tượng khác
Debug.Log("3. [INIT] Start: Trước frame đầu tiên. Logic game bắt đầu.");
}
// *** 2. GIAI ĐOẠN CẬP NHẬT (LOOPING) ***
void FixedUpdate()
{
// Chạy theo tần suất cố định (mặc định 0.02s/lần). Dùng cho VẬT LÝ
// (Lưu ý: FixedUpdate có thể chạy nhiều hơn hoặc ít hơn Update)
Debug.Log("4. [LOOP] FixedUpdate: (Vật lý) - Chạy với tần suất cố định.");
}
void Update()
{
// Chạy mỗi khung hình (frame). Tần suất không cố định. Dùng cho INPUT, logic game, di chuyển thông thường
Debug.Log("5. [LOOP] Update: (Logic Game) - Chạy mỗi frame.");
// Ví dụ: Kiểm tra Input để kích hoạt giai đoạn Cleanup
if (Keyboard.current.spaceKey.wasPressedThisFrame)
{
// Tắt Script, sẽ gọi OnDisable()
Debug.Log("Phim Space duoc bam");
enabled = false;
}
if (Keyboard.current.dKey.wasPressedThisFrame)
{
// Hủy đối tượng, sẽ gọi OnDisable() VÀ OnDestroy()
Debug.Log("Phim D duoc bam");
Destroy(gameObject);
}
}
void LateUpdate()
{
// Chạy sau khi TẤT CẢ các hàm Update() đã hoàn tất. Dùng cho Camera theo dõi
Debug.Log("6. [LOOP] LateUpdate: Chạy sau Update.");
}
// *** 3. GIAI ĐOẠN DỌN DẸP ***
void OnDisable()
{
// Chạy mỗi khi GameObject HOẶC Script bị tắt (Disabled)
Debug.Log("7. [CLEANUP] OnDisable: GameObject/Script bị tắt.");
}
void OnDestroy()
{
// Chạy ngay trước khi đối tượng bị hủy vĩnh viễn khỏi Scene
Debug.Log("8. [CLEANUP] OnDestroy: GameObject đã bị hủy.");
}
}
Gán Script và chạy chương trình
- Gán Script: Kéo và thả script LifecycleTracker.cs vào đối tượng LifecycleTracker_GO trong cửa sổ Hierarchy
- Mở cửa sổ console: Vào nenu Window > General > Console
- Chạy chương trình: Nhấn nút Play trong Unity Editor và quan sát Console
Kết quả và Phân tích
Bạn hãy quan sát Console và đối chiếu với các bước dưới đây:
Giai đoạn 1: Khởi tạo (khi nhấn Play)
Bạn sẽ thấy các dòng sau xuất hiện “đúng thứ tự” (chỉ một lần):
Giai đoạn 2: Cập nhật (sau khởi tạo)
Các dòng sau sẽ xuất hiện liên tục (lặp đi lặp lại), bạn để ý cứ mỗi giây sẽ chạy 1 lần, chứng minh đây là vòng lặp game chính.
(Thứ tự lặp lại sẽ là: FixedUpdate > Update > LateUpdate > FixedUpdate > Update > LateUpdate...)
Giai đoạn 3: Dọn dẹp (nhấn phím Space và D)
- Vô hiệu hóa Script (nhấn phím Space):
+ Khi đang chạy, nhấn phím Space (phím trắng, phím dài nhất trên bàn phím)
+ Bạn sẽ thấy dòng 7. [CLEANUP] OnDisable: ... xuất hiện
+ Tất cả các hàm FixedUpdate(), Update(), và LateUpdate() sẽ dừng chạy vì script đã bị vô hiệu hóa.
- Hủy đối tượng (nhấn phím D):
+ (Nếu script đang bị vô hiệu hóa, bạn cần bật lại nó trong Inspector trước khi Play, hoặc dừng Play và chạy lại)
+ Khi đang chạy, nhấn phím D
+ Hai dòng sau sẽ xuất hiện lần lượt (và GameObject sẽ biến mất khỏi Hierarchy):
7. [CLEANUP] OnDisable: ... (Vì đối tượng phải bị tắt trước khi hủy)
8. [CLEANUP] OnDestroy: ... (Chạy cuối cùng)
3.3 Bài tập và câu hỏi
Bài tập 3a. Cài đặt, chạy được và quan sát kết quả của Vòng đời Script (như trong bài học).
Câu hỏi 3.1 Vòng đời của một Script gồm các giai đoạn sau. Phát biểu nào không đúng?
A. Khởi tạo (initialization)
B. Cập nhật (loop update)
C. Dọn dẹp (cleanup)
D. Sửa lỗi (debugging)
Câu hỏi 3.2 Trong giai đoạn Khởi tạo của Vòng đời Script, gồm các phương thức (hàm) sau. Phát biểu nào không đúng?
A. Awake()
B. OnEnable()
C. Init()
D. Start()
Câu hỏi 3.3 Trong giai đoạn Cập nhật của Vòng đời Script, gồm các phương thức (hàm) sau. Phát biểu nào không đúng?
A. OnDisable()
B. Update()
C. FixedUpdate()
D. LateUpdate()