Netcode with Lacewing. What are the Best practices to track packet sent and keep sync

Welcome to our brand new Clickteam Community Hub! We hope you will enjoy using the new features, which we will be further expanding in the coming months.

A few features including Passport are unavailable initially whilst we monitor stability of the new platform, we hope to bring these online very soon. Small issues will crop up following the import from our old system, including some message formatting, translation accuracy and other things.

Thank you for your patience whilst we've worked on this and we look forward to more exciting community developments soon!

Clickteam.
  • Hi,

    I'm building a 2p fighting game, and I'm trying to get a very basic netcode system up and running to allow 1v1 online play. I'm totally new to the entire netcode thing, but I managed to setup lacewing (blue) client and do some basic data input data exchange between two peers :

      catching the player's control inputs

      sending them over to the other peer

      get data translated back into controller's input, effectively getting one char to move on the other side.

    My 1st question is: How Can I ensure that both player are synced based on an in-game timer?
    My idea was that the channel master would send over its timer, so that the other player would be in sync with him.. but if I, as the channel master, send my packet "frame_cout = 0" a t=0, there is a lag of,say, 33 ms when it is received so player two will receive "frame = 0" at t+2, being effectively 2 frames late. Since I don't know how to measure latency, I don't see how I can sync player 2 with the timer sent by player 1 (the channel master).
    am I looking at this problem the wrong way...?


    My 2nd question is: Is there an easy way to ensure packet receiving order?
    I'm assuming that once I solve problem 1, I can timestamp the packets myself when sending them over, then put them into a list or array and sort them, but maybe there's an easier way...?


    FYI, I watched this very interesting talk at Please login to see this link., but i'm quite a few steps before thinking about such things for the moment. My concern right now is

  • Thanks for your reply!

    However, my question was not really "is this possible?" as I know for a fact that it is: the two main implementations for this "lockstep" and "rollback" networking techniques (which are well described Please login to see this link., for those interested).

    The question I am trying to ask is "how can I implement basic lockstep netcode with fusion using lacewing"? I guess I should start by implementing a basic "ping" feature with a back and forth message, and then determine lag and frame position based on this.

    Ideally, I'm looking for the feedback from people with strong experience with the implementation of peer-to-peer netcode using Lacewing on CF2.5...

  • Ideally, I'm looking for the feedback from people with strong experience with the implementation of peer-to-peer netcode using Lacewing on CF2.5...

    This person isn't me, but until someone else with more experience answers I thought I'd try and help:

    I'd use a state system, meaning whatever your fighter is doing (at least regarding inputs), you give each move a value or 'state', so for example, if you were to throw a Hadoken like Ryu, the input sequence would be d,dr,r + punch if facing right - on completion of the input sequence you'd then set the state to a value, so say in this case, 1. I would use 2 states for each player - current state and a pending state.

    On player 1's side:
    4 values: player 1's current state (P1CS), player 1's pending state (P1PS), player 2's current state (P2CS), player 2's received current state (P2RCS)

    On completion of the input sequence, you set P1PS to your chosen value (1 in the example). You blast this value to Player 2. On receipt, player 2's current state is blasted back to player 1 and stored in P2RCS. When player 1 receives player 2's current state, P1CS is set to the value of P1PS (1 in the example) and the animation (hadoken) begins.
    If P2RCS is different to P2CS on player 1's client - update with new animation and set P2CS to P2RCS.

    You then repeat this process for player 2.

    I would assign a value (state) for each possible action, including taking damage, KO, wake up actions etc and every time this state changes, I'd blast this changed state to the other player's client - if there is a difference, immediately update the receiving client to the new state and subsequent associated animations.

    Sorry if this sounds messy and confusing, hopefully someone else can provide you with a better (and clearer) answer.

  • Since I can't edit my post to add to it, the advantage of using a state system over direct inputs is that there's fewer data elements to send to the other client, so would be faster with less chance of loss/corruption. No need to reassemble the input sequence and then determine the correct move/action to be performed.
    If you want to measure latency could you not compare 2 counters or timers? Each player client has there own timer/counter that starts counting as soon as the match begins. You send the other players's timer value when required and store it as a separate value to compare.
    So P1 timer = 10ms , you ask for P2's timer, you receive it, value is 15ms but P1's timer is now at 45ms, so latency would be P1's timer (current value) - P2's received timer value, 45 - 15 = 30ms?

  • Final part:

    So player 1 performs a move (e.g. hadoken), the pending state is sent to player 2 (in the sequence I described in my first post). Player 2's state is sent back to player 1 who then starts the actual move and P1CS ( current state) is set to P1PS (pending state). You know from above the latency between sending P1's pending state to P2 (and also subsequently sending P2's current state back to P1), so you create a delay counter to count down from whatever the latency value is, once it reaches zero, on Player 2's client side, Player 1's state is updated (so using the example above, Player 2 would see Player 1 begin to perform the hadoken).

  • Hi Graeme, thanks a lot your ideas and interest in this topic !


    After reading through you proposal and thinking it through, I think I'll stick with only sending inputs through the network, since this is the most lightweight approach: no need to send over object states and variables (because Peer to Peer resync based on sending object state will be necessarily involve sending complementary data as well, like x/y position, x/y movement speed, animation frame, etc etc. Plus, handling the same for projectiles and other interactibles.

    Let's put it that way, the industry's standard for netcode is moving away from "lockstep" and implementing "rollback" networking instead. But in both instances, games are deterministic, meaning that on any machine/instance of the game, the same inputs from players will produce the exact same results. This is the way to go to do this properly: no need to send anything BUT player inputs because the game engine will be able to reproduce the exact same game states based on inputs alone.

    So I know this is what I want to achieve (starting with a basic lockstep implementation, for learning purposes), but my main problem is getting the initial "frame timer" sync. Sharing "counters" between the two peers is also what I had in mind, but the question is how can I ensure counters are sync (considering that sending the timer over the network will necessarily desync it because it will arrive late).. I'm guessing I have to calculate latency by sending a message back and forth, but I'm wondering if there's another way..

    Also big problem: packet loss and ensuring packets (containing player inputs) are always received and managed in the right order... Since inputs is the only thing to send across (thanks to deterministic game logic), I need to ensure inputs always arrive in order, or at least use some post-process to rearrange them in the right order..

    anyways, I guess I'll keep on looking for literature about this topic as I'm sure there is a ton and see how I can transpose this to Fusion/lacewing.

    Thanks !

  • That's fair enough, I understand where you're coming from re: states vs direct inputs. I can't think of another way to calculate latency other than sending data (timestamps) back and forth and stored as counters/variables.
    I'm making a fighting game too, offline single and multiplayer at the moment, haven't decided yet whether it would be worth the effort for me to add online multiplayer to it, but I'd be really interested if you manage to get this to work (or not) - so please add to this thread with updates on your progress; would be good to see how your game's coming on.

  • I'm glad to know that someone else is potentially interested in this topic ; besides, I'm always happy to know there are other fighting-game enthusiasts within the Fusion community :D

    I have my base engine running for local 1v1, and for the most part it is pretty robust... but I didn't design and develop it with the idea of net play, initially thinking that was far beyond my own capacity. I've only very recently started to look into feasibility and read articles about how to build online peer-to-peer netcode. For now, I'm taking baby steps to learn and re-conceptualize the engine to allow for input latency, framelocking and so on.

    If you think you might end up trying to build netcode yourself, I can only recommend reading through this Please login to see this link. to ensure your engine is thought and designed in a way that will facilitate transition from local to online play.

    Anyways, I'll think and experiment some more in the next few days, so if I manage to get anything working, I'll post here to explain what I did !

    Cheers

  • That's great, thanks for the link - I'll definitely check that out. I've only made single/local multiplayer fighting games in the past, just starting to dabble with netcode. I look forward to seeing what you come up with!

  • Quick update:
    I managed to get Ping and synchronize remote clocks (frame counters).

    Here is a sample of the code I used for this, hopefully it is readable:
    // netcode is the active object where I store different alt vals.
    // lb is the lacewing blue client extension
    // microtimer is the extension to have microsecond precision
    // controller is an active object whose variables and flag store the button presses (its sort of an interface between actual joystick/keyboard input registration and game logic)

    // Send local frame timer to peer
    * netcode: AwaitingPingReply is off
    Microtimer : Reset custom timer
    lb : Clear binary to send
    lb : Select peer on channel with ID peer_id( "netcode" )
    lb : Add short self_frame_count( "netcode" )
    lb : Blast binary to peer on subchannel 200
    netcode : Set AwaitingPingReply on


    // When the peer gets the message, it automatically replies including its own frame number
    * lb : On binary message from peer on subchannel 200 (blasted)
    netcode : Set peer_frame_count to Short( "lb", 0 )

    lb : Clear binary to send
    lb : Select peer on channel with ID peer_id( "netcode" )
    lb : Add short self_frame_count( "netcode" )
    lb : Blast binary to peer on subchannel 201

    * lb : On binary message from peer on subchannel 201 (blasted)
    netcode : Set peer_frame_count to Short( "lb", 0 )
    netcode : Set ping to CustomTimerSec( "Microtimer" )
    netcode : Set AwaitingPingReply off


    * Always
    netcode : Set frame_advantage to ( peer_frame_count( "netcode" ) + ( 0.5 * ping( "netcode" ) / locked_framerate( "netcode" ) ) ) - self_frame_count( "netcode" )
    frame_adv : Set Counter to Int(frame_advantage( "netcode" ))

    * Always
    self_f_count : Set Counter to self_frame_count( "netcode" )
    peer_f_count : Set Counter to peer_frame_count( "netcode" )


    >>> Then in the Main Game Loop, I can "pause" the execution for one frame (skip conditions and stop animations) until the the frame advantage is lowered

    I am also storing local controller state (each input is an alterable flag ON/OFF which can be serialized into a single variable and sent accross network). Upon receiption of that variable, I deserialized it back into controller flags :

    // SENDING
    + Always
    lb : Clear binary to send
    lb : Add short self_controller_state( "netcode" )
    lb : Add short self_frame_count( "netcode" )
    lb : Blast binary to peer on subchannel 3

    //RECEIVING AND DESERIALIZE

    * lb : On binary message from peer on subchannel 3 (blasted)
    netcode : Set peer_input_btn to Short( "lb", 0 )
    netcode : Set remote_input_start_frame to Short( "lb", 8 )

    controller : Set b1_pressed to peer_input_btn( "netcode" ) and 1
    controller : Set b2_pressed to peer_input_btn( "netcode" ) and 2
    controller : Set b3_pressed to peer_input_btn( "netcode" ) and 4
    controller : Set b4_pressed to peer_input_btn( "netcode" ) and 8
    controller : Set l1_pressed to peer_input_btn( "netcode" ) and 16
    controller : Set r1_pressed to peer_input_btn( "netcode" ) and 32
    controller : Set u_pressed to peer_input_btn( "netcode" ) and 64
    controller : Set d_pressed to peer_input_btn( "netcode" ) and 128
    controller : Set l_pressed to peer_input_btn( "netcode" ) and 256
    controller : Set r_pressed to peer_input_btn( "netcode" ) and 512


    I'm working on storing the remote inputs received in a MagicDeque Array to keep the last n inputs and manage order properly (considering packet loss and out of order reception).

    I'm relying heavily on the "Fight the lag" document available at Please login to see this link. to think and design this implementation but it's really hard as network logic is explained rather loosely and I'm new to all this...

Participate now!

Don’t have an account yet? Register yourself now and be a part of our community!