1 điểm bởi GN⁺ 2024-06-14 | 1 bình luận | Chia sẻ qua WhatsApp
  • 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
  • CNetworkMessage cung 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ơi
    • MSG_SEQ_ADDPLAYER: thêm người chơi
    • MSG_SEQ_REMPLAYER: xóa người chơi
    • MSG_SEQ_PAUSE: tạm dừng hoặc bỏ tạm dừng
    • MSG_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ượng CPlayerAction cho mỗi người chơi đang hoạt động và áp dụng nó vào CPlayerTarget
  • CPlayerAction chứ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
  • 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ùng CSessionState::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_NEAR bằ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=0 trong 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
  • CPacket quản lý thứ tự và độ tin cậy của packet
    • pa_ulSequence: số thứ tự để xác định thứ tự và loại bỏ trùng lặp
    • pa_ubReliable: cờ độ tin cậy
    • pa_ubRetryNumber: theo dõi số lần truyền lại
    • pa_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
  • 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 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

  • CCommunicationInterface phụ 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 ý
  • CCommunicationInterface duy trì hai bộ đệm master
    • cci_pbMasterInput: giải tuần tự packet UDP đến thành CPacket để lưu
    • cci_pbMasterOutput: tuần tự hóa CPacket cầ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
  • adr_uwID của CAddress đượ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ặc 0 thì đó là packet broadcast
    • Giá trị khác là ID client trong session

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ùng ExchangeBuffers để 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

  • CNetworkMessage là 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, WriteBits và 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 qua AllocMemory, có vẻ nội bộ gọi malloc
  • CLinearAllocator có 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
  • MESSAGETYPE có 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
  • Nén mặc định có vẻ là LZRW1 và có thể đổi bằng biến shell net_iCompression
  • CPlayerAction khô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 CPlayerAction gố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 game CSessionState
  • CNetworkLibrary kế thừa CMessageDispatcher đã 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 CSessionState mới, tuần tự hóa nó và lưu thành trạng thái mặc định ga_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_CONNECTREMOTESESSIONSTATE vớ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_CONNECTREMOTESESSIONSTATE gồ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
  • MSG_GAMESTREAMBLOCKS là 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à descriptor CPlayerCharacter
  • MSG_SEQ_REMPLAYER được gửi khi người chơi ngắt kết nối, chỉ chứa chỉ số người chơi
  • MSG_SEQ_CHARACTERCHANGE truyề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
  • MSG_SEQ_PAUSE truyề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 console
  • MSG_SEQ_ALLACTIONS chứ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 CPlayerAction cho từng CPlayerTarget
    • sau đó xử lý timer, event, entity di chuyển và vật lý
  • Kiểm tra đồng bộ được thực hiện bằng MakeSynchronisationCheck()
    • tạo CSyncCheck thông qua ChecksumForSync() của entity, player target và các thành phần khác
    • client gửi MSG_SYNCCHECK cho server, và nếu không khớp với trạng thái server thì kết nối sẽ bị ngắt

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_bLerpActions bị 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ự CPlayerAction và 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

 
GN⁺ 2024-06-14
Ý 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 luôn thắc mắc vì sao họ lại làm ra những gã đàn ông đầu nổ tung vừa gào thét vừa lao tới, và âm thanh lại lớn dần khi chúng đến gần
      Tôi vẫn còn nghe thấy âm thanh đó
    • Tôi rất thích cái không khí khi bên dưới một bài viết về cách một trò chơi huyền thoại được tạo ra lại có ai đó thản nhiên nói kiểu “à đúng rồi, cái đó tôi làm đấy, vui lắm”
    • Tôi thực sự rất thích trò chơi đó
      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
    • Serious Sam là một trong những trò chơi yêu thích nhất của chú tôi, cùng với Duke Nukem 3D
      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
    • Tôi thực sự biết ơn chế độ chia màn hình
  • 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

    • Serious Sam chạy nhanh ngay cả trên phần cứng tệ, mà trông vẫn khá đẹp
      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
    • Âm thanh “aaaaaaaaaaaaah” phát ra từ nhiều loa cùng lúc thật vui
    • Cuối thập niên 90, tôi từng quản lý website hỗ trợ kỹ thuật của EA, và đội hỗ trợ/QA đã chơi Serious Sam với quy mô lớn sau giờ làm
      Đó 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

    • Vài tuần trước tôi mới mua cartridge này, vì tôi thích cartridge rung GBC cũ nên rất ấn tượng khi nó có multiplayer qua cáp link
      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

    • Tôi rất tiếc khi ở The Talos Principle 2 họ bỏ engine tự phát triển để dùng Unreal Engine
    • Tôi vừa mới biết DLC của Talos 2 sẽ lên Steam vào thứ Sáu này
  • 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-...

    • Đúng vậy. Cả hai đều là hệ thống deterministic lockstep
      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
    • Con số đó cho thấy việc Tempest Rising có giới hạn đơn vị kỳ lạ đến mức nào
      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

    • Tôi tò mò “nhiều kẻ địch đến thế” cụ thể là bao nhiêu
      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
    • Thường thì nó được biết đến nhiều hơn với tên software bloat
    • Đúng vậy, điều này được biết đến như định luật của Wirth
  • 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

    • Thậm chí còn có một game lấy đúng ý đó làm chủ đề, tên là “I Hate Running Backwards”
      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
    • Bạn và Netrisca ở cùng nhau, còn hàng nghìn kẻ địch kia thì cô độc một mình
    • Cũng có những khẩu súng tạo cảm giác như nó bắn ra trước cả khi bạn nhấn nút chuột vài phần của giây
    • Tôi vẫn nhớ cảnh vừa lùi như thế vừa tuyệt vọng nhặt số đạn được spawn ra
  • 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

    • Một ngày nào đó tôi muốn thử làm việc với kiểu kiến trúc lockstep như vậy
      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

    • Các nhà phát triển Tribes từng viết một whitepaper nói về những khái niệm network code tương tự
      https://www.gamedevs.org/uploads/tribes-networking-model.pdf
    • Có lẽ đây là trò chơi tôi yêu thích nhất khi còn nhỏ, đặc biệt là Tribes 2
      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
    • Tribes thật tuyệt
      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