Sử Dụng Timer Để Điều Khiển Trò Chơi Trên Windows
Trong lập trình game trên nền tảng Windows, thách thức lớn nhất so với các nền tảng khác như console chính là làm thế nào để cửa sổ game hoạt động hài hòa cùng các cửa sổ khác. Ngay cả ở chế độ toàn màn hình, thực chất đây vẫn là một cửa sổ thông thường. Nếu bạn bỏ qua vòng lặp xử lý thông điệp hệ thống Windows mà chỉ tập trung vào việc refresh màn hình liên tục, có lẽ bạn sẽ bị cộng đồng người dùng Windows “ném đá” dữ dội.
Vậy làm thế nào để chương trình game của bạn trở thành một “công dân tốt” trên Windows? Giải pháp đơn giản nhất là sử dụng vòng lặp PeekMessage để xử lý các thông điệp Windows. Khi hàng đợi thông điệp trống, chúng ta chuyển sang xử lý một frame logic game và đồng thời refresh màn hình. Vòng lặp chính sẽ có dạng như sau:
|
|
Đa số các game hiện nay, đặc biệt là game 3D, đều sử dụng cấu trúc tương tự. Nếu bạn dùng MFC, phần code vòng lặp thông điệp không cần tự viết mà sẽ nằm trong cơ chế framework, lúc này render_frame()
thường được đặt trong hàm OnIdle
. Vị trí của render_frame()
trong đoạn code trên tương đương với vị trí OnIdle
trong MFC.
Tuy nhiên, cách làm này chưa thực sự tối ưu về mặt hiệu suất. Mặc dù xử lý đầy đủ các thông điệp hệ thống, vòng lặp chính gần như chiếm dụng 100% tài nguyên CPU. Để giảm bớt tình trạng chiếm dụng vô độ này, biện pháp đơn giản là gọi hàm Sleep()
trong render_frame()
, thậm chí chỉ Sleep(0)
cũng giúp nhường CPU cho tiến trình khác. Tuy nhiên đây chỉ là giải pháp tạm thời.
Một cải tiến hợp lý là giới hạn tốc độ khung hình (frame rate) và kiểm tra thời gian xử lý từng frame. Khi phát hiện chưa đến thời điểm render frame mới, chương trình sẽ chờ bằng vòng lặp kiểm tra thời gian liên tục. Giải pháp này đặc biệt phổ biến trong nhiều game 2D hiện đại. Tuy nhiên, các game 3D gần như không áp dụng cách này do nhu cầu cao về frame rate. Việc đạt được 100+ FPS không chỉ là mục tiêu của nhà phát triển mà còn là mong mỏi của game thủ. Frame rate quá thấp sẽ làm giảm trải nghiệm điều khiển trong game 3D, trong khi cơ chế Sleep khó duy trì mức FPS ổn định.
Một phương pháp đáng cân nhắc là sử dụng cơ chế định thời của Windows thông qua WM_TIMER
. Bằng cách kích hoạt thông điệp timer trong vòng lặp GetMessage, chúng ta có thể thực hiện animation mà không làm gián đoạn cơ chế message-driven truyền thống của Windows:
|
|
Tuy nhiên, đa số lập trình viên game từ chối giải pháp này do độ chính xác thấp của WM_TIMER
. Trên Windows 98, tần suất định thời chỉ khoảng 20Hz, còn Windows NT đạt 100Hz - mức tối thiểu chấp nhận được. Ngoài ra, WM_TIMER
có đặc tính ưu tiên thấp và cho phép gộp nhiều thông điệp giống nhau, dẫn đến thiếu chính xác.
Giải pháp tối ưu do kỹ sư Cloud Wu đề xuất bao gồm việc xây dựng hệ thống định thời riêng với độ chính xác tùy chỉnh. Chúng ta có thể thiết kế mảng vòng chứa 100 hàng đợi sự kiện (với độ phân giải 1/100 giây) để quản lý các timer theo thời gian định trước. Cơ chế WaitableTimer
của Windows sẽ kích hoạt hệ thống định thời này, kết hợp với một event riêng biệt để thông báo render màn hình:
|
|
Giải pháp này kết hợp ưu điểm của WaitableTimer và cơ chế event-driven bản địa Windows, cho phép cân bằng giữa trải nghiệm người dùng và hiệu suất hệ thống. Khi điều chỉnh độ chính xác của hệ thống định thời, chúng ta đồng thời kiểm soát được mức độ sử dụng CPU của ứng dụng.