Search Unity

Lockstep syncing transform properties

Discussion in 'Multiplayer' started by subwayheaven, Jul 7, 2019.

  1. subwayheaven

    subwayheaven

    Joined:
    Oct 15, 2014
    Posts:
    11
    TLDR: Can you use the Transform component in a deterministic lockstep simulation in Unity? Why would syncing the transform.position field work but not the transform.eulerAngle values?

    Hi guys,

    I have a working lockstep project that checks for dysncs every frame by sending over all gameObject data each lockstep turn. It checks these values to make sure that every client running the game has exactly the same game state at all times.

    I've managed to use a fixed point library to do some maths (Moving gameobjects from one position to another) and then setting the transform position field like so;

    Code (CSharp):
    1.  
    2. //Do fancy maths using custom fixed point library, then..
    3. this.transform.position = new Vector3(fixedPoint.x, 0, fixedPoint.z);
    4.  
    This looks correct as the game objects all have the same position on multiple clients on both Windows 10 and an Android phone and my project says it's all good.

    However, for curiosity I tried checking the direction value I'm using and making sure all clients are calculating the same value.

    They don't.

    I'm getting values like

    Local Direction (Windows 10 Laptop): 87
    Remote Direction (Android Phone): 87.000000002424

    This is my code that sets the rotation of the transform component for the game objects based on the direction value stored in _unitData;

    Code (CSharp):
    1.     public void SetUp()
    2.     {
    3.         this.transform.rotation = Quaternion.Euler(0f, _unitData.Direction, 0f);
    4.     }
    Here is my code that initialises the direction as a float which contains only integer values;

    Code (CSharp):
    1. var xpos = Random.Range(-100, 100);
    2. var zpos = Random.Range(-100, 100);
    3. var speed = Random.Range(4, 6);
    4. var direction = Random.Range(0, 359);
    5.  
    6. var gamestate  = ObjectPool.Instance.GetGameState();
    7. action = new CreateUnit(PhotonNetwork.LocalPlayer.ActorNumber, 1, xpos, zpos, speed, direction, gamestate, _gameFrame, _lockStepTurnID);
    The direction value is always set to a random integer and so shouldn't cause an issue with floating point math as far as I know (?) however the _unitData that stores this value does store it as a float.

    This code is very prototype so please be gentle.

    At the moment I just want to run tests to see what causes desyncs and what doesn't - I don't actually plan on attempting cross platform determinism just determinism on x86 machines. I'm testing using a windows laptop and an android phone because it should show desyncs quicker and if I can fix them on an ARM processor I'm confident an x86 processor shouldn't be an issue.

    I'm hoping that I can use the Transform component from Unity (I have no idea what I'd do if I can't because internally it does some funky floating point math that executes differently on different x86 processors) however this is an assumption as I haven't seen anything on the internet that confirms this.

    I'd appreciate any suggestions/ideas/feedback!


    Thanks guys!
     
    Last edited: Jul 8, 2019
  2. subwayheaven

    subwayheaven

    Joined:
    Oct 15, 2014
    Posts:
    11
    So I've made some progress in the last week or so.

    I'm going to document things here as it helps get my mind clear - plus, who knows, someone might find it useful.

    By the way, my work is following the tutorial made by Clinton Brennan on how to achieve a deterministic lockstep implementation in Unity. So if you want a quick understanding of what I'm attempting to make - you can read his quick explanation (It has two parts but part one has the idea explaned).

    (This forum won't let me post the link - if you google "Clinton Brennan lockstep" you can find the tutorial).

    There are two different "actions" in my simulation that get sent between clients;

    NoAction
    CreateUnit


    These are then executed at the same time on all machines - so the simulation should run insync with every other machine at all times.

    Then, I don't need a server to keep a record of the game state, every client has the exact same game state.

    Checking for desyncs
    Both of these actions actually contain an array of all Units currently in the game - I send this data across the network to all clients so I can have more power debugging desyncs by comparing the actions game state to the local clients and see exactly what the difference is.

    Usually you can just make a checksum and send that over so it's a fraction of the data - but then you can't see explicitly which game object desycned and why it happened.

    NoAction doesn't do anything - it's just sent by default so that every lockstep turn has the local game state sent along with it.

    I discovered a problem with my simulation detecting desyncs in the game state on frames where a client was sending a "CreateUnit" action across the network.

    It was only desyncing for that specific frame - every "NoAction" frame reported a complete sync in game state between all clients.

    I found out that the cause was *somehow* related to CreateUnit actions being delayed and sent in the next lockstep turn but not having their lockstep turn (which all actions store) being updated. This led to the desyncing code comparing a recieved action to the wrong action the client stored (and then desyncing as the units position information was slightly off (Xpos being 63.456 instead of 66.765 etc).

    So I've tested this with Android/PC and PC/PC and it seems to be staying in sync.I'm hoping my way of detecting desyncs by comparing the game state every lockstep turn is working.

    My previous post mentioned that the direction value (which was changed by the eulerAngle transform property) was causing a desync. This seems to no longer be the case and was just a false flag caused by other issues.

    I'll add more to this at a latter point.