Serious Sam xử lý lượng lớn kẻ địch qua kết nối modem 56k
(staniks.github.io)- Serious Engine 1 đặt chơi đơn·chơi mạng·phát lại demo trên cùng một mô phỏng quyết định, nên thay vì ghi hoặc truyền toàn bộ trạng thái ở mỗi tick, nó ghi và truyền hành động của người chơi cùng các khối luồng game
- Demo và chơi mạng chia sẻ trạng thái game ban đầu, rồi áp dụng delta đầu vào như
CPlayerAction, vì vậy tính quyết định của seed số ngẫu nhiên và logic game là cốt lõi của việc đồng bộ - Tầng mạng trực tiếp xử lý số thứ tự, ACK, truyền lại, giới hạn băng thông và bộ đệm theo từng kết nối trên nền UDP để duy trì khả năng chơi ngay cả trên đường truyền chậm
CNetworkMessagecung cấp tuần tự hóa, nén LZ77/LZRW1 và mã hóa delta dựa trên XOR, nhưng các thông điệp bao gồm chat không được mã hóa và có thể chỉ được nén- Mô hình client-server của Serious Sam tiết kiệm băng thông bằng cách để mỗi client tự duy trì mô phỏng riêng, đổi lại cần các cơ chế hỗ trợ như kiểm tra đồng bộ·dự đoán·truyền lại·xác minh CRC
Cấu trúc mô phỏng dùng chung của Serious Engine
- Mã nguồn Serious Engine 1 được công bố theo GNU GPL v2 vào năm 2016, và phần phân tích dựa trên việc đọc và debug codebase công khai này
- Serious Sam ngay từ đầu đã được thiết kế như một game multiplayer, và chiến dịch chơi đơn về mặt nội bộ cũng hoạt động như một trường hợp đặc biệt của cấu trúc multiplayer
- Các chế độ mà engine hỗ trợ gồm:
- Chiến dịch chơi đơn offline
- Chơi co-op online, LAN, local và nhiều chế độ game khác nhau
- Chia màn hình với nhiều người chơi trên một client
- Ghi và phát lại demo
Ghi demo: lưu hành động thay vì toàn bộ trạng thái
- Nếu lưu toàn bộ trạng thái game cho mỗi tick thì file demo sẽ rất lớn, nên Serious Engine lưu toàn bộ trạng thái game một lần tại thời điểm bắt đầu ghi, rồi sau đó ghi các khối luồng game ở mỗi tick
- Khối luồng game chứa các kiểu thông điệp sau:
MSG_SEQ_ALLACTIONS: hành động của người chơiMSG_SEQ_ADDPLAYER: thêm người chơiMSG_SEQ_REMPLAYER: xóa người chơiMSG_SEQ_PAUSE: tạm dừng hoặc bỏ tạm dừngMSG_SEQ_CHARACTERCHANGE: thay đổi thuộc tính nhân vật người chơi
- Trọng tâm là
MSG_SEQ_ALLACTIONS; engine giải tuần tự một đối tượngCPlayerActioncho mỗi người chơi đang hoạt động và áp dụng nó vàoCPlayerTarget CPlayerActionchứa trạng thái người chơi:- tốc độ di chuyển theo không gian thế giới
pa_vTranslation - xoay nhân vật theo không gian thế giới
pa_aRotation - xoay góc nhìn theo không gian thế giới
pa_aViewRotation - các nút đang được nhấn
pa_ulButtons - dấu thời gian mili giây dựa trên TSC
pa_llCreated
- tốc độ di chuyển theo không gian thế giới
- Khi phát lại, game đọc trạng thái ban đầu rồi áp dụng hành động của người chơi ở từng tick như khi chơi thật
Vì sao cần tính quyết định
- Cấu trúc này dựa trên giả định rằng mọi thứ trong game đều hoàn toàn có thể dự đoán, và chỉ hành động của người chơi mới làm thay đổi game
- Số ngẫu nhiên cũng được xử lý bằng bộ sinh giả ngẫu nhiên dùng seed là một phần của trạng thái game
CEntity::IRnd()dùngCSessionState::Rnd()ses_ulRandomSeedđược khởi tạo trong quá trình giải tuần tự trạng thái game
- Nếu dùng bộ sinh số ngẫu nhiên thật hoặc seed khác nhau, cùng một demo khi phát lại có thể cho ra kết quả khác, dẫn đến mất đồng bộ
Số thực dấu chấm động và xử lý tick
- Bản PC của Serious Sam ban đầu chỉ phát hành cho Windows, nên giả định dùng cùng compiler và runtime đã giúp giảm vấn đề đồng bộ số thực dấu chấm động
- Renderer là DLL, và lời gọi API OpenGL hoặc DirectX có thể làm thay đổi độ chính xác FPU, nên Serious Engine dùng các guard độ chính xác như
CSetFPUPrecision FPUPrecision(FPT_24BIT) - Không tìm thấy nơi nào thiết lập rõ chế độ làm tròn, nhưng có assert kiểm tra trạng thái
_RC_NEARbằng_controlfp - Logic game tách rời khỏi framerate render
- Render thay đổi theo phần cứng và thiết lập, và có vẻ bị giới hạn nội bộ ở 500 FPS
- Logic game được cố định ở 20 tick mỗi giây
- Chuyển động mượt mà được tạo ra bằng nội suy tuyến tính giữa tick hiện tại và tick trước đó; có thể tắt nội suy bằng
/net_bLerping=0trong console
Tầng packet tự xây trên UDP
- Trong multiplayer của Serious Engine vẫn còn các tên hàm như
StartPeerToPeer_t, nhưng mô hình thực tế là client-server - Server nhận và xử lý thông điệp từ client, rồi chuyển tiếp thông tin liên quan đến tất cả client
- Mỗi người chơi chạy mô phỏng riêng tương tự hệ thống demo, và tiến trạng thái bằng cách nhận thông tin hành động của những người chơi khác
- Mạng dùng UDP và chồng thêm giao thức riêng để xử lý việc đảo thứ tự và mất gói
CPacketquản lý thứ tự và độ tin cậy của packetpa_ulSequence: số thứ tự để xác định thứ tự và loại bỏ trùng lặppa_ubReliable: cờ độ tin cậypa_ubRetryNumber: theo dõi số lần truyền lạipa_tvSendWhen: dùng cho thời điểm gửi dự kiến và điều khiển tắc nghẽn
- Packet tin cậy sẽ chờ ACK, nếu không có ACK thì sẽ được gửi lại
- Số lần thử tối đa được đặt bởi
net_iMaxSendRetries, mặc định có vẻ là10 - Khoảng chờ giữa các lần gửi lại được đặt bởi
net_fSendRetryWait, mặc định có vẻ là0.5f
- Số lần thử tối đa được đặt bởi
- Packet tin cậy có thể tạo thành luồng chia thành nhiều packet
- packet đầu là
UDP_PACKET_RELIABLE_HEAD - packet cuối là
UDP_PACKET_RELIABLE_TAIL - packet tin cậy đơn lẻ sẽ có cả hai cờ
- packet đầu là
- Packet không tin cậy không tạo thành luồng vì khi mất gói thì luồng có thể bị vỡ
Vòng đời kết nối và định tuyến packet
CCommunicationInterfacephụ trách giao tiếp ở tầng packet, với các hàm interface cho server, client và broadcast- Interface server và client giả định đã biết đích kết nối, còn interface broadcast được dùng để gửi nhận với địa chỉ tùy ý
CCommunicationInterfaceduy trì hai bộ đệm mastercci_pbMasterInput: giải tuần tự packet UDP đến thànhCPacketđể lưucci_pbMasterOutput: tuần tự hóaCPacketcần gửi rồi truyền qua API socket
- Trừu tượng giao tiếp theo từng client thực tế do
CClientInterfaceđảm nhiệm- Server có interface cho từng người chơi trong mảng
cm_aciClients - Client dùng
cm_ciLocalClientđể giao tiếp với server - Cả client và server đều dùng
cm_ciBroadcastđể thiết lập kết nối
- Server có interface cho từng người chơi trong mảng
adr_uwIDcủaCAddressđược dùng làm định danh duy nhất của client hoặc dấu hiệu packet broadcast- Nếu giá trị là
'//'hoặc0thì đó là packet broadcast - Giá trị khác là ID client trong session
- Nếu giá trị là
Thiết lập kết nối và cơ chế bảo mật cơ bản
- Để kết nối tới server, client gửi một packet broadcast tin cậy có cờ
UDP_PACKET_CONNECT_REQUEST - Nếu đã có client cùng địa chỉ và cổng kết nối thì server sẽ bỏ qua yêu cầu
- Nếu là client mới, server tìm interface client còn trống và thực hiện các việc sau
- tạo định danh duy nhất cho client đó
- gửi định danh cho client bằng packet broadcast tin cậy
UDP_PACKET_CONNECT_RESPONSE
- Định danh không chỉ dùng chỉ số cố định mà được tạo bằng cách kết hợp một phần giá trị timer với chỉ số client
- Muốn giả mạo người chơi khác thì phải khớp
uwID, nên bề mặt tấn công được giảm bớt - Nếu người chơi chưa kết nối gửi packet không phải broadcast, Serious Engine có thể in cảnh báo ra console
Chơi đơn và demo là trường hợp đặc biệt của kết nối cục bộ
- Chơi đơn và phát lại demo về mặt nội bộ cũng có server và client, nhưng chạy trong cùng một process
- Không cần dùng socket giữa các process giống nhau, nên
Client_OpenLocal()kết nối interface client cục bộ với interface phía server - Hai
CClientInterfaceđược ghép nối sẽ dùngExchangeBuffersđể chuyển packet từ bộ đệm output của bên này sang bộ đệm input của bên kia - Chơi local không cần đi qua bộ đệm master input/output hay socket mạng thật
Tầng thông điệp mạng
CNetworkMessagelà lớp trừu tượng thông điệp nằm trên packet và có thể đọc ghi như stream- Thông điệp được tuần tự hóa và giải tuần tự bằng
Read,Write,ReadBits,WriteBitsvà các toán tử<<,>> - Nó cũng có thể chứa thông điệp con, và sau khi ghi dữ liệu cần thiết có thể dùng
Shrinkđể khớp kích thước bộ đệm với kích thước dữ liệu - Bộ đệm
CNetworkMessageđược cấp phát quaAllocMemory, có vẻ nội bộ gọimalloc CLinearAllocatorcó tồn tại nhưng không tìm thấy nơi sử dụng; bộ đệm thông điệp được cấp phát và cấp phát lại khá thường xuyên
Nén và mã hóa delta
- Thông điệp có thể được nén bằng compressor chỉ định hoặc compressor mặc định theo từng loại thông điệp
MESSAGETYPEcó 6 bit thấp là kiểu, còn 2 bit còn lại biểu thị phương thức nén- LZ77
CzlibCompressor - LZRW1
CLZCompressor - không nén
- LZ77
- Nén mặc định có vẻ là LZRW1 và có thể đổi bằng biến shell
net_iCompression CPlayerActionkhông được gửi nguyên trạng mà được chuyển thành delta bằng XOR giữa hành động hiện tại và hành động trước đó- Phía nhận sẽ XOR delta với hành động trước đó để khôi phục
CPlayerActiongốc - Delta nén hiệu quả hơn khi dữ liệu thay đổi ít
- phím nhấn thường được giữ trong nhiều frame liên tiếp
- tốc độ và góc nhìn cũng không dao động trên toàn bộ miền số thực dấu chấm động
- Khi server gửi hành động của nhiều người chơi cùng lúc bằng
MSG_SEQ_ALLACTIONS, hiệu quả của cách này có thể còn cao hơn
Mã hóa thông điệp và chat
- Thông điệp của Serious Engine không được mã hóa
- Nếu tắt nén bằng
net_iCompression=0, tin nhắn chat trong game có thể nhìn thấy dưới dạng plaintext trong payload của packet UDP - Trong tình huống thực tế, nếu nén được bật thì bên nghe lén packet phải nhận diện và giải nén luồng nén LZ, nhưng dữ liệu cần thiết vẫn nằm trong packet
- Game thời đó thường không xử lý mã hóa, và nếu triển khai các cơ chế như xác thực hay trao đổi khóa thì độ phức tạp có thể tăng lên
- Thời đó web cũng chủ yếu là HTTP
Tầng session game
CNetworkLibrary, trái với tên gọi, quản lý session game bao gồm cả trạng thái gameCSessionStateCNetworkLibrarykế thừaCMessageDispatcherđã nói ở trên- Khi khởi động server, engine thực hiện quy trình sau
- khởi tạo thu thập CRC để chuẩn bị kiểm tra xem client kết nối có cùng file với server hay không
- tạo
CSessionStatemới, tuần tự hóa nó và lưu thành trạng thái mặc địnhga_pubDefaultState - tải instance world cục bộ
- khởi tạo interface giao tiếp toàn cục
- khởi tạo trạng thái session cục bộ để khi client kết nối có thể gửi delta trạng thái giữa trạng thái mặc định và trạng thái hiện tại của server
- kết thúc thu thập CRC và lưu vào
ga_ulCRC
- Kiểm tra CRC gần với việc phát hiện sớm mất đồng bộ hơn là chống cheat
- Quy trình client tham gia diễn ra như sau
- khởi tạo trạng thái session cục bộ rỗng và interface giao tiếp
- gửi
MSG_REQ_CONNECTREMOTESESSIONSTATEvới phiên bản build, tên mode, mật khẩu server, số người chơi local,CSessionSocketParams - nhận
MSG_REP_CONNECTREMOTESESSIONSTATEgồm thông điệp, tên file world, cờ độ khó·chế độ game, và thuộc tính session - khởi tạo trạng thái game cơ sở
- gửi
MSG_REQ_STATEDELTAđể yêu cầu phần khác biệt so với trạng thái hiện tại của server - sau khi nhận
MSG_REP_STATEDELTA, tái dựng luồng trạng thái game bằng diff ngược - khởi tạo trạng thái session cục bộ bằng
CSessionState::Read_t() - thực hiện kiểm tra CRC và ngắt kết nối nếu không khớp
Vòng lặp chính và truyền lại luồng game
- Vòng lặp chính của client và server nhìn chung tương tự nhau, nhưng server có thêm việc phải làm
- Vòng lặp cập nhật interface client cục bộ và interface broadcast, rồi để trạng thái session cục bộ xử lý các thông điệp mạng đi vào
- Server cũng đảm nhiệm trao đổi bộ đệm giữa các interface client được ghép cặp, cập nhật interface client phía server, cập nhật GameAgent và xử lý lệnh shell quản trị từ xa
SessionStateLoop()xử lý riêng thông điệp không tin cậy và tin cậy- không tin cậy:
MSG_GAMESTREAMBLOCKS,MSG_KEEPALIVE,MSG_INF_PINGS,MSG_CHAT_OUT - tin cậy:
MSG_INF_DISCONNECTED,MSG_ADMIN_RESPONSE
- không tin cậy:
MSG_GAMESTREAMBLOCKSlà thông điệp không tin cậy, nhưng nếu bị thiếu thì có thể làm hỏng đồng bộ- Serious Engine kiểm tra sequence bị thiếu trong giai đoạn xử lý luồng game và yêu cầu gửi lại
- nếu có block đúng sequence kế tiếp thì xử lý
- nếu không có block kế tiếp và cũng không có block mới hơn thì không làm gì trong vòng lặp đó
- nếu không có block kế tiếp nhưng có block mới hơn thì đặt timeout vì có khả năng bị thiếu
- sau timeout, gửi
MSG_REQUESTGAMESTREAMRESENDđể yêu cầu sequence block bị thiếu và số lượng block
- Server sẽ gửi lại các block luồng game đã được yêu cầu
Xử lý khối luồng game
MSG_SEQ_ADDPLAYERđược gửi khi người chơi vào game, gồm chỉ số người chơi và descriptorCPlayerCharacterMSG_SEQ_REMPLAYERđược gửi khi người chơi ngắt kết nối, chỉ chứa chỉ số người chơiMSG_SEQ_CHARACTERCHANGEtruyền thay đổi về tên người chơi, team và ngoại hình- Trong Serious Sam, bộ đệm ngoại hình chứa cấu trúc
CPlayerSettings - Trong đó có tên file model người chơi, chính sách tự chọn vũ khí, kiểu tâm ngắm và nhiều cờ khác
- Trong Serious Sam, bộ đệm ngoại hình chứa cấu trúc
MSG_SEQ_PAUSEtruyền việc tạm dừng hoặc bỏ tạm dừng, và in tên người chơi đã yêu cầu lên consoleMSG_SEQ_ALLACTIONSchứa thời gian tick hiện tại và hành động của tất cả người chơi- áp dụng
CPlayerActioncho từngCPlayerTarget - sau đó xử lý timer, event, entity di chuyển và vật lý
- áp dụng
- Kiểm tra đồng bộ được thực hiện bằng
MakeSynchronisationCheck()- tạo
CSyncCheckthông quaChecksumForSync()của entity, player target và các thành phần khác - client gửi
MSG_SYNCCHECKcho server, và nếu không khớp với trạng thái server thì kết nối sẽ bị ngắt
- tạo
Dự đoán để giảm độ trễ đầu vào
- Dự đoán là cơ chế giúp game tốc độ cao không bị cảm giác ì do độ trễ Internet
- Dự đoán cho người chơi local dùng các hành động đã gửi lên server
- Dự đoán cho người chơi từ xa dùng hành động cuối cùng nhận được từ server
- Nếu client tự tiến trạng thái game thật mà không chờ phản hồi từ server thì sẽ không biết hành động của người chơi khác và có thể gây mất đồng bộ
- Serious Engine dùng predictor để tránh trộn lẫn trạng thái thật với trạng thái dự đoán
- predictor gần giống một bản sao “ma” gắn với entity thông thường
- temporary predictor được tạo ra trong lúc dự đoán và không có entity liên kết trong trạng thái game thật
- Khi xử lý tick dự đoán, chỉ các entity predictor được xử lý
- Khi client nhận được hành động người chơi từ server, predictor cũ bị hủy và một chu kỳ dự đoán mới bắt đầu
- Khi render, entity gốc đang được dự đoán sẽ không được vẽ mà thay vào đó là predictor, để chuyển động trông như vẫn tiếp diễn mà không làm thay đổi quá nhiều trạng thái thật
- Người chơi local chỉ có thể dự đoán tối đa bằng số hành động đã gửi cho server được lưu trong
plt_abPrediction - Trong dự đoán người chơi từ xa, nếu
cli_bLerpActionsbị tắt thì lặp lại hành động cuối cùng đã nhận - Nếu
cli_bLerpActionsđược bật thì nội suy tuyến tính giữa hai hành động cuối cùng, nhưng mặc định là tắt
So sánh với Doom và Quake
- Mạng của Doom thực tế là peer-to-peer, nhưng các client trao đổi cấu trúc tương tự
CPlayerActionvà mỗi bên tự chạy mô phỏng độc lập - Doom cũng dùng hệ thống tương tự cho ghi và phát lại demo
- Quake dùng cấu trúc khác, trong đó client không tự xử lý phần lớn logic game mà thiên về nhận cập nhật trạng thái từ server
- Cách của Quake giúp bớt phải lo vấn đề đồng bộ, và server cũng dễ chống cheat hơn, chẳng hạn không gửi thông tin entity ở sau tường
- Trong Serious Sam, một session bình thường có số lượng kẻ địch và object đang hoạt động lớn hơn Quake rất nhiều, nên nếu gửi trạng thái của nhiều object ở mỗi tick thì gánh nặng băng thông có thể rất lớn
Tính di động và giới hạn cấu trúc
- Một số thông điệp mạng tuần tự hóa struct theo cách gần giống
reinterpret cast - Điều này có thể ổn khi giả định chỉ có một compiler và một nền tảng, nhưng trong game đa nền tảng thì layout struct và padding có thể khác nhau
- File thực thi 32-bit có thể căn chỉnh theo biên 4 byte, còn 64-bit có thể cố căn theo biên 8 byte
- Vấn đề endian cũng tồn tại
- PC x86 là little-endian
- PS3 là big-endian
- Cấu trúc của Serious Engine thanh lịch ở chỗ nó trừu tượng hóa sự khác biệt giữa các môi trường truyền như mạng và file khỏi logic game, nhưng mô hình mọi client đều giữ một bản sao trạng thái game cũng khiến cheat trở nên khả thi
- Ví dụ, một client đã chỉnh sửa có thể hiển thị đường viền của người chơi khác phía sau tường trong deathmatch
1 bình luận
Ý kiến Hacker News
Tôi là một trong những lập trình viên phụ trách triển khai network code của Serious Sam
Hồi đó tôi thường ngủ dưới gầm bàn ở văn phòng Croteam và lục lại Usenet, đặc biệt được truyền cảm hứng từ các bài viết giải thích hệ thống dự đoán của QuakeWorld
Đêm đó, trong lúc đồng nghiệp Dan dùng một máy Unix 486 cũ làm router để mô phỏng độ trễ, tôi đã code một bản triển khai tối thiểu, đơn giản
Chuyện này diễn ra rất lâu trước khi trò chơi thực tế được xây dựng trên nền đó
Tôi vẫn còn nghe thấy âm thanh đó
Tôi rất mê game co-op và game bắn súng, nhưng bạn bè lúc nào cũng chỉ muốn chơi Counter-Strike
Nhờ Serious Sam mà thỉnh thoảng tôi cũng thuyết phục được họ chơi game tôi thích
Chú là một người vô cùng quan trọng trong cuộc đời tôi, và chơi những trò chú từng thích là một cách hay để kết nối với ký ức về chú
Một game tuyệt vời, multiplayer tuyệt vời, và rất nhiều kỷ niệm đẹp
Serious Sam luôn là một game cho LAN party cực mạnh
Không phải vì nó là tựa game hào nhoáng nhất thời đó, cũng không phải vì ai đã lên kế hoạch từ trước
Khi các game khác chết vì lỗi driver, quá nhiệt, update hỏng các kiểu, thì chỉ cần chạy Serious Sam là nó hoạt động, nên nó thống trị các LAN party
Điểm này còn được giữ lại ở các phần sau: ngay cả khi PC của ai đó gần như hỏng hẳn thì game vẫn hỗ trợ chia màn hình ổn định và xử lý thiết bị nhập rất tốt
Về mặt hệ thống, trò chơi này thật sự xuất sắc ở độ tin cậy
Tương tự, Counter-Strike cũng không có đồ họa quá ấn tượng, nhưng chạy tốt trên cả những chiếc PC “máy nướng bánh mì”, nên nổi tiếng rất lâu
Đó là game bắn súng góc nhìn thứ nhất duy nhất luôn chạy ổn định trên các máy tính công ty, và thật sự rất vui
Hồi đó ở EA, QA và hỗ trợ kỹ thuật chồng lấn nhau khá nhiều: vào mùa hè, nhân viên hỗ trợ sẽ làm beta tester nội bộ cho các đợt phát hành cuối năm; còn vào mùa đông, khi gần Giáng sinh số cuộc gọi tăng vọt, họ lại quay về làm hỗ trợ kỹ thuật
Khi triển khai multiplayer trong bản port Game Boy Color của Vigilante 8, chúng tôi dùng gameplay quyết định
Cáp link GBC có thể gửi và nhận đồng thời 1 byte theo hai chiều, và hoạt động như một cặp thanh ghi dịch đang lấp đầy lẫn nhau qua sợi cáp
Game bị khóa theo frame rate của GBC, và về cơ bản mỗi V-Blank đều cần nhiều công việc cập nhật màn hình; nếu lỡ nhịp thì cuộn mượt sẽ bị đứt
Khi bắt đầu multiplayer, hai máy trao đổi seed, rồi quá trình chạy diễn ra như sau: ở frame A đọc input, nén thành 1 byte và đưa vào buffer truyền. Trong lúc render frame B thì quá trình truyền diễn ra. Đến đầu frame C, ta sẽ có input cục bộ đã gửi ở frame A và input đối thủ nhận được ở frame B
Những input đó được áp dụng vào trạng thái game và frame C được render, nên cả input cục bộ lẫn từ xa đều được áp dụng với độ trễ 1 frame
Chế độ chơi đơn cục bộ không có input lag, nên nếu bạn thua ở multiplayer thì có thể đổ tại độ trễ, và nếu cần thì cứ đổ lỗi riêng cho tôi
Một trò chơi rất hay, và phần giải thích kỹ thuật về cách triển khai multiplayer cũng cực kỳ thú vị
Croteam thực sự là một đội ngũ phát triển game rất tài năng
Tôi cực kỳ thích The Talos Principle phần 1 và 2, và ở phần 1 họ là một trong những người tiên phong sớm làm hẳn engine game Vulkan tùy biến hoàn toàn
Không rõ đây có phải cùng ý tưởng với “1500 cung thủ trên 28.8K” của Age of Empires không
https://www.gamedeveloper.com/programming/1500-archers-on-a-...
Rất nhiều game đã dùng kiểu hệ thống này trong thời gian dài, nhưng ngày nay có vẻ nó ít phổ biến hơn trước vì nhiều lý do
Chỉ cần để người chơi hoặc có tài nguyên hoặc không có tài nguyên là được, đâu cần thêm giới hạn chỉ để mà giới hạn
Ngay cả những game có băng thông gấp 10 lần cũng khó hỗ trợ được nhiều kẻ địch đến thế
Giờ tôi mới nhận ra rằng việc tăng tài nguyên kỹ thuật dường như lại phản tác dụng với hiệu quả và sự sáng tạo trong khoa học máy tính
Khi băng thông, lưu trữ, bộ nhớ và năng lực tính toán tăng lên, phần mềm dường như phản ứng bằng cách trở nên chậm hơn, phình to hơn và kém năng lực hơn trên mỗi đơn vị tài nguyên
Có thể gọi đó là hiệu ứng thiết kế phần mềm kiểu Benjamin Button
Nếu bài viết có nêu con số thì tôi đã không tìm thấy
Trong các game hiện đại cũng có nhiều game multiplayer mà số lượng kẻ địch có thể xem là đủ “quy mô lớn”, và nếu điều quan trọng là số người chơi thì cũng có những game hỗ trợ lượng người chơi rất lớn
Tôi cho rằng đây là game mà thời gian di chuyển lùi còn nhiều hơn thời gian đi tới
Nó có trên Steam, và tôi không chắc có phải cùng nhà phát triển không, nhưng thuộc cùng vũ trụ Serious Sam
Cấu trúc của Factorio cũng tương tự, gần như chỉ truyền các sự kiện input và dựa vào lockstep simulation core
Ngoại lệ dễ thấy là những phần như công cụ lập kế hoạch đường sắt
Nó trông như một ràng buộc thiết kế vừa thỏa mãn vừa dễ kiểm thử
Tôi còn nhớ hồi nhỏ chơi Serious Sam qua bản demo của PC Gamer
Ngay lúc đó nó đã được xem là một trò chơi hoài cổ gợi lại thời DOOM và Quake cũ
Giờ thì đúng nghĩa đã hơn 20 năm trôi qua, và bản thân nó cũng trở thành kinh điển
Starsiege: Tribes có quy mô lớn và vui đến mức phi lý ngay cả trên kết nối 56K
https://www.gamedevs.org/uploads/tribes-networking-model.pdf
Thật ra cách đây không lâu tôi còn tải Tribes 2 về và vài tháng trước vẫn chơi với bot
Dù là game cũ nhưng vẫn rất vui, và tôi thường nghĩ đến việc làm lại nó bằng thứ gì đó như Unity
Có lẽ một ngày nào đó tôi sẽ làm
Năm 1999, được thấy địa hình sinh ngẫu nhiên, trượt xuống như trượt tuyết, nhìn người khác trượt ngược lên trên đó, lại còn có bản đồ lớn và nhiều người chơi nữa