Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NetworPlayerState struct (Experimental) #3955

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

NiftyliuS
Copy link

@NiftyliuS NiftyliuS commented Dec 13, 2024

Network Player State Struct

This struct is intended to transfer the most common states used for desync detection and reconciliation as efficiently as possible
( see diagram ) with addition of 3 functions that meant to simplify the process.

1. Ability to pass defaults in the constructor.

NetworkPlayerState updatedState = new NetworkPlayerState(NetworkPlayerState? defaults);

This is meant to allow easy cloning of the struct while retaining the ability to change any one of the memebers

var previousState = new NetworkPlayerState(){ .... }
var newState = new NetworkPlayerState(previousState ){ Position= new Vector3( x, y, z ) }

2. Ability to compare and extract changes.

NetworkPlayerState changes = new NetworkPlayerState().GetChangedStateComparedTo(NetworkPlayerState state);

It is important to send as little information as possible to reduce the network traffic hence we really only want to send the changes ( for example if rotation remained the same we dont send it but we do send the new position for comparison )

var previousState = new NetworkPlayerState(){ .... }
var newState = new NetworkPlayerState(previousState ){ Position= new Vector3( x, y, z ) }

NetworkPlayerState? changedState = newState .GetChangedStateComparedTo(previousState);
// null means no changes between the states and they dont need to be sent
// If you still want to send simply send new new NetworkPlayerState(){ TickNumer=currentTick }

3. Ability to duplicate with overrides

NetworkPlayerState newState= new NetworkPlayerState().OverrideWith(NetworkPlayerState overrides);

This is crucial when working with only changes and is meant to allow for cloning previous state with the received changes in a compact manner.

var lastState = new NetworkPlayerState(){ .... }
var changes= new NetworkPlayerState(){ Position= new Vector3( x, y, z ) }

NetworkPlayerState nextState = lastState.OverrideWith(changes);

This is particularly usefull to reduce the code size when sending the changes over network

Fields:

  • Int TickNumber - mutable int of 11bits ( 0-2047 ) that contains the tick the state happened on the client
    It is required before sending as it is a part of the header
  • bool IsParentSet - Signifies if there was a change in the parent ( null will mean unset if IsParentSet is true )
  • NetworkIdentity? Parent - NetworkIdentity of the parent, it is important to know that to distinguish between no change and no parent please use new NetworkPlayerState().IsParentSet
  • Vector3? Position - Player position in the world
  • Vector3? BaseVelocity - Player velocity vector
  • Quaternion? Rotation- Player rotation Quaternion
  • byte[]? AdditionalState - Any additional state required for the game to run, they are compared as a byte array and if any byte is different will be sent

note: If parent is sent It is the developer decision if to send relative values or absolute ones

Usage example:

 [Command(requiresAuthority)]
   private void onStateChange(NetworkPlayerState clientStateChange) {
       // Add changes on top of last known State stored on the server
       nextState = lastReceivedState.OverrideWith(clientStateChange)
       lastReceivedState = nextState; // keep track of State
   }
   
   public void FixedUpdate() {
     // Send only the State changes from the client
     if (isClient) { 
       NetworkPlayerStats newState = GetPlayerState();
       NetworkPlayerState? changes = newState.GetChangedStateComparedTo(lastState)
       // only send if there is atleast 1 change ( excluding tick number )
       if (changes is not null) { 
           changes.TickNumber = currentClientTick;
           onStateChange(changes.Value);
      }
       lastState = newState;
       // Do client logic
     }
     
     if (isServer) {
        if (nextState is null) { // duplicate last knownState if no new State were sent
           nextState = new NetworkPlayerState(lastReceivedState) {
               TickNumber = currentTick; // update the tick to current;
           }
           // Do server logic
        }    
     }
   }

Byte Structure Diagram

tick-predictions-Page-4

Summary i guess...

This is 2 of 2 structs i want to introduce to allow for a simple and coherent exchange of inputs and states between server and client.
When combined the message with both NetworkPlayerInputs and NetworkPlayerState:


tick-predictions-Page-41

The ideal scenario is to send only the inputs on change
and every Nth tick to send inputs with the state attached for desync detection

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant