Các công ty game đã "tự tay làm" game như thế nào trước khi có engine hoàn chỉnh?


Câu trả lời cho câu hỏi này gai góc hơn nhiều so với những gì hầu hết mọi người tưởng tượng.

Các nhà phát triển game ngày nay mở Unity hoặc Unreal, trình soạn thảo đã sẵn sàng, engine vật lý đã sẵn sàng, pipeline render đã sẵn sàng, hệ thống ánh sáng đã sẵn sàng. Công việc của họ là điền nội dung vào một khung đã được xây dựng sẵn. Điều đó đơn giản là không tồn tại vào những năm 1980. Không có framework, không có engine, không có cơ sở hạ tầng tái sử dụng. Nếu bạn muốn làm một trò chơi, trước tiên bạn phải xây dựng một cỗ máy có thể chạy nó, và sau đó trên cỗ máy đó, sử dụng ngôn ngữ assembly, bạn bảo nó từng pixel phải có màu gì.

Tất cả bắt đầu từ pixel.

Đồ họa game thời kỳ đầu được viết trực tiếp vào bộ nhớ. Các lập trình viên cần biết địa chỉ bộ nhớ tương ứng với từng vị trí trên màn hình. Để vẽ một điểm tại một vị trí nhất định, bạn ghi một giá trị vào địa chỉ đó — giá trị đại diện cho màu sắc. Để vẽ một đường, bạn viết một vòng lặp, bảo CPU ghi tại mỗi N byte từ địa chỉ này đến địa chỉ kia. Để vẽ một nhân vật, bạn chuyển đổi từng hàng pixel thành số thập lục phân, viết chúng từng dòng một, tìm offset chính xác trên màn hình, và đặt những con số đó vào đó.

Quy trình này ngày nay được gọi là “software rendering”. Thời đó nó không có tên, bởi vì không có lựa chọn thay thế — nó chỉ đơn giản được gọi là “cách hình ảnh hiện ra”.

Khi Carmack đang làm Wolfenstein đầu tiên, ông phải đối mặt với một vấn đề cụ thể: kiến trúc đồ họa của IBM PC không hỗ trợ cuộn ngang mượt mà. Màn hình cập nhật bằng cách làm mới toàn bộ khung hình cùng một lúc, gây ra hiện tượng xé hình khi ảnh di chuyển — trông như thể nó đang rung lắc. Ông đã dành một lượng thời gian khổng lồ để nghiên cứu các thanh ghi cấp thấp của card đồ họa, và tìm ra cách đánh lừa phần cứng để làm điều mà nó không hỗ trợ nguyên bản, mô phỏng nó bằng phần mềm — và hình ảnh có thể cuộn mượt mà.

Không có tài liệu nào cho việc này, không có kinh nghiệm tiền nhân. Ông chỉ ngồi đó, lật từng trang sổ tay phần cứng, và tự mày mò từng chút một qua thử nghiệm và sai sót.

Đây là điều kiện làm việc cơ bản của các lập trình viên thời đó. Bạn không đối mặt với một hệ thống có sách hướng dẫn — bạn đối mặt với chính phần cứng. Bạn phải hiểu phần cứng hơn cả phần cứng hiểu chính nó, và tìm ra không gian trong các kẽ hở của nó.

Âm thanh là một vấn đề ở một chiều kích hoàn toàn khác.

Hệ thống âm thanh của PC thời kỳ đầu là một cái còi — PC Speaker. Nó chỉ có thể tạo ra một âm thanh duy nhất: một tiếng bíp. Để phát nhạc bằng nó, bạn phải điều khiển tần số rung của cái còi này, chuyển đổi giữa các tần số khác nhau ở tốc độ cực cao, tận dụng hiệu ứng lưu ảnh của thính giác con người để làm cho nó nghe như một giai điệu đang được phát. Kỹ thuật này gọi là PWM, điều chế độ rộng xung. Nguyên lý là vật lý âm thanh, việc thực hiện là kiểm soát thời gian chính xác đến micro giây. Sai một chút thôi, và thứ bạn nhận được chỉ là tiếng ồn.

Có những lập trình viên thời đó專門 nghiên cứu cách vắt thêm nhiều lớp âm thanh từ PC Speaker. Phương pháp của họ là chuyển đổi giữa các tần số khác nhau trong những khung thời gian cực ngắn, tạo ra hiệu ứng pseudo-stereo. Kỹ thuật này bị coi như ma thuật đen trong giới demo thời đó. Bạn lấy một cái còi chỉ có thể kêu “bíp”, và tạo ra thứ gì đó nghe gần giống âm nhạc. Các lập trình viên khác nghe được không hề biết bạn đã làm thế nào — bởi vì nguyên lý không trực quan. Bạn cần một sự hiểu biết cực kỳ chính xác về kiểm soát thời gian để thực hiện được nó.

Khi Adlib và Sound Blaster ra đời, chúng mang đến âm thanh tổng hợp FM. Đây là một hệ thống khác cần học từ đầu. Nguyên lý của tổng hợp FM là sử dụng dạng sóng của một tần số để điều chế dạng sóng của tần số khác, tạo ra các âm sắc phức tạp. Các thông số điều chế khác nhau tạo ra âm thanh nhạc cụ khác nhau. Các lập trình viên phải tự điều chỉnh thông số, tự kiểm tra xem tổ hợp nào nghe như piano, tổ hợp nào như violin, tổ hợp nào như tiếng nổ. Không có câu trả lời tiêu chuẩn — tất cả phụ thuộc vào tai và kinh nghiệm. Thời đó, mỗi lập trình viên âm thanh của mỗi công ty đều có bảng thông số tích lũy riêng. Đó là những bí mật thương mại theo đúng nghĩa.

Cảm giác thiết kế ở cấp độ logic game lại là một dạng khác.

Không có engine đồng nghĩa với không có phát hiện va chạm có sẵn, không có mô phỏng vật lý có sẵn, không có framework AI có sẵn. Nếu bạn muốn một nhân vật dừng lại khi đến bức tường thay vì xuyên qua nó, bạn phải tự viết phát hiện va chạm, tự định nghĩa “va chạm” là gì, và tự quyết định tốc độ và vị trí của nhân vật thay đổi như thế nào sau va chạm.

Phát hiện va chạm trong game thời kỳ đầu đơn giản và thô bạo: coi cả nhân vật và chướng ngại vật đều là hình chữ nhật, kiểm tra xem hai hình chữ nhật có chồng lên nhau không, và nếu có thì dừng lại. Cách này nhanh, nhưng vấn đề là nhân vật game thực tế không phải hình chữ nhật — chúng là sprite có hình dạng. Luôn có một sai lệch giữa hộp va chạm hình chữ nhật và đồ họa thực tế. Đó là lý do tại sao trong game cũ, đạn rõ ràng bắn trượt nhưng vẫn được tính là trúng, hoặc nhân vật dừng lại trước khi chạm vào tường một cách có thể thấy được.

Người chơi thời đó không gọi đây là lỗi. Họ gọi nó là đặc điểm của game. Bởi vì họ không biết rằng nó có thể khác đi.

Một điều khác đáng nhắc đến: PC thời kỳ đầu có bộ nhớ cực kỳ hạn chế. 640KB là trần cứng trong suốt một thập kỷ. Con số đó ngày nay thậm chí không chứa nổi một ảnh chụp màn hình. Các lập trình viên phải nhồi tất cả nội dung game — đồ họa, hiệu ứng âm thanh, bản đồ, mã logic — vào trong 640KB đó, và còn phải chừa chỗ cho dữ liệu thời gian chạy.

Làm thế nào? Nén, tải theo khối, và tái sử dụng.

Nén dữ liệu đồ họa là một kỹ thuật riêng. Các lập trình viên phải tự phát minh ra thuật toán nén của riêng mình, làm cho dữ liệu đồ họa nhỏ nhất có thể khi lưu trữ và giải nén ngay lập tức khi cần hiển thị. Nếu bản đồ quá lớn không vừa, bạn chia nó thành các khối, tải khối mà người chơi đang đứng, và dỡ bỏ khi họ rời đi. Ngày nay kỹ thuật này gọi là streaming. Thời đó nó gọi là “không còn cách nào khác.”

Mỗi byte trong bộ nhớ đều phải giành giật. Khi viết mã, các lập trình viên liên tục phải nghĩ: tôi có thể dùng kiểu dữ liệu nhỏ hơn cho biến này không? Tôi có thể dùng ít biến tạm thời hơn cho logic này không? Tôi có thể giảm số lần truy cập bộ nhớ trong vòng lặp này không? Cách suy nghĩ này hầu như không cần thiết đối với lập trình viên ngày nay. Phần cứng hiện đại có đủ dư địa bộ nhớ, và trình biên dịch đủ thông minh trong việc tối ưu hóa — bạn không cần phải đếm từng byte.

Nhưng các lập trình viên thời đó mang trong đầu toàn bộ hoạt động bên trong của máy tính. Từ chu kỳ lệnh CPU đến bố trí bộ nhớ đến thanh ghi GPU, họ phải hiểu từng lớp, bởi vì mỗi lớp đều là một kẽ hở mà họ có thể cần khai thác.

Carmack từng viết một đoạn mã tính căn bậc hai nghịch đảo, với một dòng chú thích viết “what the fuck”. Đoạn mã này sử dụng một thủ thuật thao tác bit cực kỳ phản trực giác để xấp xỉ nhanh căn bậc hai nghịch đảo, nhanh hơn nhiều so với thư viện toán học tiêu chuẩn — tất cả để tiết kiệm thời gian tính toán trong render 3D. Đoạn mã này sau đó trở thành huyền thoại trong văn hóa lập trình viên, được trích dẫn hết lần này đến lần khác, bởi vì nó đại diện cho một cách tư duy đã biến mất: phương pháp vắt kiệt hiệu suất từ toán học và sự sáng tạo trong giới hạn phần cứng.

Các lập trình viên game thời đó gần giống như sự kết hợp giữa kỹ sư và nhà khoa học. Vấn đề họ giải quyết không chỉ là “làm thế nào để triển khai tính năng này” — mà là “liệu tính năng này có thể được triển khai trong giới hạn vật lý của cỗ máy này không.”

Hai câu hỏi khác nhau rất nhiều.

Câu hỏi đầu tiên có câu trả lời tiêu chuẩn: tra tài liệu, gọi API, làm theo các bước. Câu hỏi thứ hai không có câu trả lời tiêu chuẩn. Bạn phải tự phát minh ra câu trả lời. Đôi khi câu trả lời tồn tại, đôi khi không. Trước tiên bạn phải tìm ra mình đang ở trường hợp nào, và sau đó quyết định phải làm gì.

Ngành công nghiệp game ngày nay là một hệ thống hợp tác khổng lồ. Các công ty engine, các công ty middleware, các công ty công cụ — mỗi lớp đều có người làm việc trên nó. Các nhà phát triển đứng trên đỉnh của hệ thống này, tập trung vào chính nội dung. Đây là sự tiến bộ, là điều tốt — nó cho phép nhiều người hơn làm game, và quy mô cùng độ phức tạp của game vượt xa những gì có thể trong quá khứ.

Nhưng kỷ nguyên xây dựng game từ những pixel đó có một thứ mà ngày nay không còn tồn tại.

Bạn mở game, và bạn biết từng điểm trên màn hình đến từ đâu, từng âm thanh được tạo ra như thế nào, từng va chạm được phán quyết ra sao. Bởi vì tất cả những điều đó đều do bạn viết ra, từng dòng một. Không có gì được người khác làm thay cho bạn.

Cảm giác này có một cái tên. Nó được gọi là sự kiểm soát hoàn toàn.

Vậy nên, những vĩ nhân phát triển game thời đó — họ thực sự là vĩ nhân. Thực sự tuyệt vời.