TKR is P2P rollback network library for Odin.
Rollback Network requires the game to be deterministic, but this allows to only send inputs between peers. Also, to predict remote players inputs so we can keep simulating gameplay without waiting, when receiving the correct input we can rollback and re-simulate the game if necessary.
Features:
- Supports UDP or Steam NetworkMessages transport layer, you can also write a custom transport if needed
- Automatic input delay based on ping, you can customize or disabled this
- Desync detection, players will disconnect when their game state diverges
- Detection when the client frame is too far ahead or behind so you can slowdown/speedup the fixed update rate
- Check the example to see how to do this
example.mp4
For a complete usage check the example folder.
FPS :: 60.0
game: Game
local_input: Input
p2p: tkr.P2P_Session(Game, Input)
udp_transport: tkr.UDP_Transport
tkr.p2p_init(&p2p, num_players, FPS, serialize_input, deserialize_input)
// Players information usually come from a Lobby System or Matchmaking
local_player_index: u64 = 0
local_endpoint := net.Endpoint { net.IP4_Loopback, 5000 }
remote_player_index: u64 = 1
remote_client_id: u64 = 123
remote_endpoint := net.Endpoint { net.IP4_Loopback, 5001 }
tkr.p2p_add_local_player(&p2p, local_player_index)
tkr.p2p_add_remote_player(&p2p, remote_player_index, remote_client_id)
tkr.udp_transport_init(&udp_transport, local_endpoint)
tkr.udp_transport_add_client(&udp_transport, remote_client_id, remote_endpoint)
for game_running {
messages_to_send := tkr.p2p_update(&p2p)
tkr.udp_transport_send_messages(&udp_transport, &p2p, messages_to_send)
tkr.udp_transport_poll(&udp_transport, &p2p)
// Add local input to the simulation
tkr.p2p_add_local_input(&p2p, client_index, local_input)
requests, messages_to_send := tkr.p2p_advance_frame(&p2p)
tkr.udp_transport_send_messages(&udp_transport, &p2p, messages_to_send)
// Must handle this requests in order.
for request in requests {
switch &r in request {
case tkr.Save_Game(Game):
// Store the current game state in the given ptr
r.game_rollback_state.state = game
r.game_rollback_state.checksum = hash.crc32(mem.ptr_to_bytes(&game))
case tkr.Load_Game(Game):
// Load given game state to the global game variable
game = r.game_rollback_state.state
case tkr.Advance_Frame(Input):
// Run gameplay code
game_update(r.inputs, r.status)
case tkr.Skip_Frames:
// We cannot simulate more (reached the maximum Prediction Window)
// Do nothing
}
}
}
game_update :: proc(inputs: [tkr.MAX_NUM_PLAYERS]Input, status: [tkr.MAX_NUM_PLAYERS]tkr.Input_Status) {
// ... gameplay code
}In the example folder you can check simple game example with Steam or UDP transport.
odin build example
Commands to run a 2 player game, run each client with:
.\example.exe 0 127.0.0.1:5000 127.0.0.1:5001
.\example.exe 1 127.0.0.1:5000 127.0.0.1:5001
When running the steam example, each client instance MUST have a different steam account. To do this you need 2 computers (or VMs) each running different steam accounts.
odin build example -define:STEAM_ENABLED=true
Commands to run a 2 player game, run each client with (replace STEAM_IDs with your owns):
.\example.exe 0 <STEAM_ID_1> <STEAM_ID_2>
.\example.exe 1 <STEAM_ID_1> <STEAM_ID_2>