Bí quyết chạy game xây dựng thành phố trên CPU 16 MHz

Công nghệ22 tháng 10, 2026·6 phút đọc

SimCity nguyên bản từng chạy trọn vẹn trên CPU Intel 386SX chỉ 16 MHz nhờ một thủ thuật tối ưu thông minh: không chạy toàn bộ mô phỏng trong mỗi khung hình. Bài viết phân tích mã nguồn Micropolis để hé lộ cách các nhà phát triển thời kỳ đầu chia nhỏ khối lượng tính toán, một kỹ thuật vẫn được áp dụng rộng rãi trong game hiện đại.

Bí quyết chạy game xây dựng thành phố trên CPU 16 MHz

Bạn có bao giờ cảm thấy mình bị "chiều hư" bởi phần cứng hiện đại? Ngay cả những CPU rẻ nhất hôm nay cũng có ít nhất 4 lõi, xử lý hàng tỷ phép tính mỗi giây. Ý tưởng chạy phần mềm trên một cỗ máy dưới 1 GHz gần như là chuyện viễn tưởng. Ấy vậy mà đã từng có thời cả một tựa game vận hành trên phần cứng yếu hơn cả nghìn lần như thế.

Ảnh bìa SimCity cổ điểnẢnh bìa SimCity cổ điển

Khi nhắc đến những thành tựu kỹ thuật ấn tượng trong game retro, hầu hết mọi người sẽ nghĩ ngay đến các cột mốc đồ họa của những game hành động như Doom hay Quake. Điều đó đúng, nhưng cá nhân tôi lại ấn tượng hơn nhiều với những ví dụ đầu tiên của thể loại xây dựng thành phố. Mô phỏng cả một nền kinh tế ở quy mô lớn với một phần nhỏ sức mạnh ngày nay nghe có vẻ bất khả thi, nhất là khi các game xây dựng hiện đại đôi khi vẫn ì ạch trên máy móc tân tiến.

Tất nhiên, điều đó không ngăn được các nhà phát triển trong quá khứ tạo ra và vận hành game xây dựng thành phố trên phần cứng yếu hơn rất nhiều. Ví dụ, SimCity nguyên bản liệt kê CPU Intel 386SX trong yêu cầu hệ thống. Đây là dòng CPU khi ra mắt chạy ở xung nhịp "huy hoàng" 16 MHz.

Theo góc nhìn hiện đại, việc chạy trọn vẹn một tựa game — đặc biệt là thể loại vốn ngốn CPU như xây dựng thành phố — đúng là phép màu thuần túy. Vậy tại sao Maxis có thể chạy mô phỏng cả một thành phố trên phần cứng cổ xưa ấy, trong khi ngày nay ngay cả những game idle đơn giản cũng đòi hỏi CPU vài GHz?

Mã nguồn mở Micropolis: chìa khóa để giải mã

Tất nhiên có rất nhiều lý do, phần lớn đến từ yếu tố bên ngoài như hệ sinh thái hệ điều hành. Nhưng điều đó không có nghĩa là mã game không chứa những tối ưu cực kỳ khéo léo. May mắn thay, mã nguồn này đã được công khai và phát hành theo giấy phép GPL vào năm 2008 dưới tên gọi Micropolis. Đừng nhầm lẫn bởi cái tên — đây vẫn là mã của tựa game nguyên bản, nhưng vì chỉ mã nguồn được mở chứ không phải nhãn hiệu, nên nó buộc phải mang tên khác.

Dù vậy, đây vẫn là một tài nguyên quý giá, vì chúng ta có thể thấy chính xác cách game vận hành hiệu quả đến vậy. Như thường lệ khi phân tích hiệu năng phần mềm, ta có thể bỏ qua 90% mã nguồn. Trọng tâm nằm ở phần lõi của mô phỏng thành phố — các thành phần cập nhật hệ thống như giao thông, hỏa hoạn, điện năng và tội phạm.

Và thực sự chỉ có một hàm duy nhất đảm nhận toàn bộ việc này: hàm Simulate() trong tệp s_sim.c. Hàm này được gọi mỗi khung hình, rồi lần lượt gọi các hàm cập nhật của từng hệ thống như CrimeScan(), FireAnalysis(), CollectTax()...

Minh họa mã nguồn và cấu trúc mô phỏngMinh họa mã nguồn và cấu trúc mô phỏng

Thủ thuật cốt lõi: không chạy mọi thứ trong mỗi khung hình

Vấn đề là gọi tất cả các hàm ấy mỗi khung hình sẽ làm quá tải con CPU tội nghiệp năm 1989. Giải pháp: không gọi mọi hàm trong mọi khung hình.

Đúng vậy, SimCity chỉ đơn giản là bỏ qua phần lớn mô phỏng. Bạn có để ý tham số mod16 của hàm Simulate không? Chỗ mà bình thường bạn sẽ mong đợi một deltaTime? Con số này lặp qua các giá trị từ 0 đến 15, rồi được dùng để quyết định phần nào của mô phỏng sẽ chạy trong khung hình cụ thể đó.

Điều này rõ ràng khiến mô phỏng tính toán nhanh hơn nhiều, nhưng nó ảnh hưởng thế nào đến trải nghiệm chơi? Với SimCity, tôi cho rằng gần như không đáng kể. Trung bình một khía cạnh mô phỏng mất 8 khung hình để phản ứng với hành động của người chơi, tệ nhất là 15 khung hình. Nếu là độ trễ đầu vào của game bắn súng thì đúng là thảm họa, nhưng với mô phỏng một thành phố? Tôi cho rằng nó thậm chí còn mang lại cảm giác tốt hơn so với phản ứng tức thì.

Kỹ thuật này không chỉ dành cho game xây dựng

Thoạt nhìn, tối ưu này có vẻ rất đặc thù cho một thể loại game. Nhưng thực ra nó là điểm khởi đầu cho rất nhiều tối ưu khác. Hãy xem một ví dụ từ Assetto Corsa (2014).

Là game đua xe, một trong những yêu cầu hình ảnh là render xe với hiệu ứng bóng loáng. Để tạo các phản chiếu đó, game dùng kỹ thuật rất phổ biến là cập nhật cubemap định kỳ. Vì cập nhật toàn bộ cubemap mỗi khung hình là quá tốn kém, game áp dụng mẹo tương tự SimCity: không cập nhật cả 6 mặt của cubemap cùng lúc, mà chỉ vài mặt tùy theo cài đặt chất lượng phản chiếu. Giao diện cài đặt không nói rõ, nhưng nhìn vào tệp .ini ta thấy biến có tên FACES_PER_FRAME.

Minh họa kỹ thuật phân bổ khối lượng tính toán qua nhiều khung hìnhMinh họa kỹ thuật phân bổ khối lượng tính toán qua nhiều khung hình

Cho đến giờ, ta vẫn bám theo khái niệm phân bổ công việc qua nhiều khung hình bằng cách cắt các tác vụ dài thành những phần có kích thước cố định. Với một số tác vụ khác, sẽ khôn ngoan hơn nếu xác định kích thước và nội dung các phần ngay tại thời điểm chạy. Với tác vụ render, điều này có thể phụ thuộc vào kích thước màn hình hoặc thời gian kể từ lần cập nhật trước. Đây là lý do Unreal Lumen trông đẹp đến vậy: nó không cần tính lại toàn bộ ánh sáng mỗi khung hình. Đây cũng có thể là lý do kết quả đôi khi trông kỳ lạ, chẳng hạn khi phản chiếu chưa được cập nhật đầy đủ.

Bài học cho lập trình viên hôm nay

Dù các tối ưu phân bổ qua nhiều khung hình phổ biến nhất trong mã render, ví dụ SimCity cho thấy chúng cũng hiệu quả ở các lĩnh vực khác. Lần tới khi bạn làm việc với mã gameplay, hãy tự hỏi: "Mình có thực sự cần chạy toàn bộ đoạn mã này mỗi khung hình không?"

Với độc giả Việt Nam đang phát triển game indie hoặc tối ưu ứng dụng trên thiết bị cấu hình thấp, bài học này càng có giá trị: không cần phần cứng mạnh nếu biết cách phân bổ khối lượng tính toán hợp lý theo thời gian. Kỹ thuật đơn giản nhưng hiệu quả này đã giúp SimCity chạy mượt trên CPU 16 MHz gần bốn thập kỷ trước, và vẫn là nền tảng cho nhiều engine đồ họa tiên tiến ngày nay.

Chia sẻ:FacebookX
Nội dung tổng hợp bằng AI, mang tính tham khảo. Xem bài gốc ↗