Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Network Transform : Rotations only?

Discussion in 'Multiplayer' started by GixG17, Mar 11, 2016.

  1. GixG17

    GixG17

    Joined:
    Aug 18, 2013
    Posts:
    105
    This sounds a bit odd:

    I got SpawnWithClientAuthority objects that are working pretty well. For some of these objects, I'm using the "Sync Transform" mode and I'd like them to only sync the rotation. Sync Transform, however, includes the object's position as well... which is not needed in this particular case.

    Imagine, if you will, an shooter where the player's head rotates independently from the body. The body tracks the position, so I don't need to track the position for the head too; only the rotation.

    How can I go about it?

    ///
    Right now I got it set up where all of the "parts" for the players are scattered in the "object list/hierarchy" without any parenting and it's really messy... not to mention it complicates the code for no reason when I want to, say, hide/unhide particular player characters (I have to do it per piece). The reason why they're currently not parented is that tracking positions for both parent and child objects yield very jittery results. Hence why I want to stop position tracking for the head objects.
     
  2. GixG17

    GixG17

    Joined:
    Aug 18, 2013
    Posts:
    105
    I had hoped someone would have insight on the matter :\

    As far as I can tell, Google searches only yields this specific thread, I'm surprised no-one else seems to have this problem... or is there something I'm missing entirely?

    The only thing I can think of right how would be to use SyncVar and hand-craft the functionality myself but that seems overly inefficient (with higher risk of sending too much data) for something that should practically be a an option from the "Transform Sync Mode" drop menu.
     
  3. thegreatzebadiah

    thegreatzebadiah

    Joined:
    Nov 22, 2012
    Posts:
    836
    If you want interpolation then Transform Sync Mode isn't going to cut it anyway. I recommend writing your own script using either rpc's or syncvars and then you can customize it to your hearts content.

    Here's what I've been using to maybe get you started. I didn't bother to clean it up or anything so it's probably got stuff you don't need but hopefully it's still useful as an example. This is loosely inspired by the old NetworkRigidbody script that was floating around the forums but updated to use UNET.
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using UnityEngine.Networking;
    4.  
    5. [NetworkSettings (channel=1, sendInterval=0.0f)]
    6. public class UNetRigidbody : NetworkBehaviour
    7. {
    8.  
    9.     public float interpolationBackTime = 0.1f;
    10.     float extrapolationLimit = 0.5f;
    11.     public bool skipLerp = false, dontLerp = true;
    12.     public float minLerpSpeed = .2f;
    13.     public float minDistanceAtWhichToStopLerpingAndJustJumpStraightThere = .1f;
    14.     public float sendInterval = 0.0f;
    15.  
    16.     public float lastTimeServerReceivedNetworkUpdateFromOwner;
    17.     public float lastSyncTime;
    18.  
    19.     public struct State
    20.     {
    21.         internal float timestamp;
    22.         internal Vector3 pos;
    23.         internal Vector3 velocity;
    24.         internal Quaternion rot;
    25.         internal Vector3 angularVelocity;
    26.     }
    27.  
    28.     public State[] stateBuffer = new State[10];
    29.     public int timestampCount;
    30.     State targetState;
    31.     bool hasDeterminedAuthority = false;
    32.  
    33.     public void Start()
    34.     {
    35.         targetState.pos = transform.position;
    36.         targetState.rot = transform.rotation;
    37.     }
    38.  
    39.     public override void OnStartAuthority()
    40.     {
    41.         base.OnStartAuthority();
    42.  
    43.         hasDeterminedAuthority = true;
    44.     }
    45.  
    46.     public void OnStartNonOwner()
    47.     {
    48.         hasDeterminedAuthority = true;
    49.     }
    50.  
    51.     [Command]
    52.     public void CmdSendState(State state)
    53.     {
    54.         lastTimeServerReceivedNetworkUpdateFromOwner = Time.time;
    55.         RpcReceiveState(state);
    56.     }
    57.  
    58.     [ClientRpc]
    59.     public void RpcReceiveState(State state)
    60.     {
    61.         if (hasAuthority) return;
    62.  
    63.         lastSyncTime = Time.time;
    64.  
    65.         // Shift the buffer sideways, deleting state 20
    66.         for (int i = stateBuffer.Length - 1; i >= 1; i--)
    67.         {
    68.             stateBuffer[i] = stateBuffer[i - 1];
    69.         }
    70.  
    71.         stateBuffer[0] = state;
    72.  
    73.         timestampCount = Mathf.Min(timestampCount + 1, stateBuffer.Length);
    74.     }
    75.  
    76.     public float lastSendTime;
    77.     public void LateUpdate()
    78.     {
    79.         if (!hasDeterminedAuthority) return;
    80.  
    81.         if (hasAuthority)
    82.         {
    83.             if (sendInterval != 0 && Time.time - lastSendTime > sendInterval)
    84.             {
    85.                 State state = new State();
    86.                 state.pos = transform.position;
    87.                 state.rot = transform.rotation;
    88.                 if (GetComponent<Rigidbody>())
    89.                 {
    90.                     state.velocity = GetComponent<Rigidbody>().velocity;
    91.                     state.angularVelocity = GetComponent<Rigidbody>().angularVelocity;
    92.                 }
    93.                 state.timestamp = NetworkManager.instance.networkTime;
    94.                 CmdSendState(state);
    95.                 lastSendTime = Time.time;
    96.             }
    97.         }
    98.         else
    99.         {
    100.             setInterpolationPosition();
    101.         }
    102.     }
    103.  
    104.     public void Update()
    105.     {
    106.         if (!hasDeterminedAuthority) return;
    107.  
    108.         if (!hasAuthority && timestampCount > 0)
    109.         {
    110.             setInterpolationPosition();
    111.         }
    112.     }
    113.  
    114.     public void stopLerping()
    115.     {
    116.         dontLerp = true;
    117.     }
    118.  
    119.     public void restartLerping()
    120.     {
    121.         skipLerp = true;
    122.     }
    123.  
    124.     public void setInterpolationPosition()
    125.     {
    126.         if (!hasDeterminedAuthority) return;
    127.  
    128.         float distance = Vector2.Distance(transform.position, targetState.pos);
    129.         float lerpSpeed = Mathf.Max(minLerpSpeed, minDistanceAtWhichToStopLerpingAndJustJumpStraightThere / distance);
    130.         if (skipLerp)// || distance > 2)
    131.         {
    132.             lerpSpeed = 1;
    133.             skipLerp = false;
    134.             dontLerp = false;
    135.         }
    136.         else if (dontLerp)
    137.         {
    138.             lerpSpeed = 1;
    139.             //dontLerp = false;
    140.             targetState.pos = transform.position;
    141.             targetState.rot = transform.rotation;
    142.             if (GetComponent<Rigidbody>() && !GetComponent<Rigidbody>().isKinematic)
    143.             {
    144.                 targetState.velocity = GetComponent<Rigidbody>().velocity;
    145.                 targetState.angularVelocity = GetComponent<Rigidbody>().angularVelocity;
    146.             }
    147.         }
    148.  
    149.         transform.position = Vector3.Lerp(transform.position, targetState.pos, lerpSpeed);
    150.         transform.rotation = Quaternion.Slerp(transform.rotation, targetState.rot, lerpSpeed);
    151.  
    152.         if (GetComponent<Rigidbody>() && !GetComponent<Rigidbody>().isKinematic)
    153.         {
    154.             GetComponent<Rigidbody>().velocity = Vector3.Lerp(GetComponent<Rigidbody>().velocity, targetState.velocity, lerpSpeed);
    155.             GetComponent<Rigidbody>().angularVelocity = Vector3.Lerp(GetComponent<Rigidbody>().angularVelocity, targetState.angularVelocity, lerpSpeed);
    156.         }
    157.     }
    158.  
    159.     /**
    160.      * We have a window of interpolationBackTime where we basically play
    161.      * By having interpolationBackTime the average ping, you will usually use interpolation.
    162.      * And only if no more data arrives we will use extrapolation
    163.      */
    164.     public void FixedUpdate()
    165.     {
    166.         if (!hasDeterminedAuthority) return;
    167.  
    168.         if (hasAuthority) return;
    169.  
    170.         // This is the target playback time of the rigid body
    171.         double interpolationTime = NetworkManager.instance.networkTime - interpolationBackTime;
    172.  
    173.         // Use interpolation if the target playback time is present in the buffer
    174.         if (stateBuffer[0].timestamp > interpolationTime)
    175.         {
    176.             // Go through buffer and find correct state to play back
    177.             for (int i = 0; i < timestampCount; i++)
    178.             {
    179.                 if (stateBuffer[i].timestamp <= interpolationTime || i == timestampCount - 1)
    180.                 {
    181.                     // The state one slot newer (<100ms) than the best playback state
    182.                     State rhs = stateBuffer[Mathf.Max(i - 1, 0)];
    183.                     // The best playback state (closest to 100 ms old (default time))
    184.                     State lhs = stateBuffer[i];
    185.  
    186.                     // Use the time between the two slots to determine if interpolation is necessary
    187.                     double length = rhs.timestamp - lhs.timestamp;
    188.                     float t = 0.0F;
    189.                     // As the time difference gets closer to 100 ms t gets closer to 1 in
    190.                     // which case rhs is only used
    191.                     // Example:
    192.                     // Time is 10.000, so sampleTime is 9.900
    193.                     // lhs.time is 9.910 rhs.time is 9.980 length is 0.070
    194.                     // t is 9.900 - 9.910 / 0.070 = 0.14. So it uses 14% of rhs, 86% of lhs
    195.                     if (length > 0.0001)
    196.                         t = (float)((interpolationTime - lhs.timestamp) / length);
    197.  
    198.                     // if t=0 => lhs is used directly
    199.                     if (Vector3.SqrMagnitude(lhs.pos - rhs.pos) > 100) // This may no longer be necessary
    200.                     {
    201.                         // If jumping too far don't lerp just teleport
    202.                         targetState.pos = rhs.pos;
    203.                         if (GetComponent<Rigidbody>() && !GetComponent<Rigidbody>().isKinematic)
    204.                         {
    205.                             GetComponent<Rigidbody>().velocity = lhs.velocity;
    206.                             GetComponent<Rigidbody>().angularVelocity = lhs.angularVelocity;
    207.                         }
    208.                     }
    209.                     else
    210.                     {
    211.                         targetState.pos = Vector3.Lerp(lhs.pos, rhs.pos, t);
    212.                         if (GetComponent<Rigidbody>() && !GetComponent<Rigidbody>().isKinematic)
    213.                         {
    214.                             GetComponent<Rigidbody>().velocity = Vector3.Lerp(lhs.velocity, rhs.velocity, t);
    215.                             GetComponent<Rigidbody>().angularVelocity = Vector3.Lerp(lhs.angularVelocity, rhs.angularVelocity, t);
    216.                         }
    217.                     }
    218.                     targetState.rot = Quaternion.Slerp(lhs.rot, rhs.rot, t);
    219.  
    220.                     return;
    221.                 }
    222.             }
    223.         }
    224.         // Use extrapolation
    225.         else
    226.         {
    227.             State latest = stateBuffer[0];
    228.  
    229.             float extrapolationLength = (float)(interpolationTime - latest.timestamp);
    230.             // Don't extrapolate for more than 500 ms, you would need to do that carefully
    231.             if (extrapolationLength < extrapolationLimit)
    232.             {
    233.  
    234.                 float axisLength = extrapolationLength * latest.angularVelocity.magnitude * Mathf.Rad2Deg;
    235.                 Quaternion angularRotation = Quaternion.AngleAxis(axisLength, latest.angularVelocity);
    236.  
    237.                 targetState.pos = latest.pos + latest.velocity * extrapolationLength;
    238.  
    239.                 targetState.rot = angularRotation * latest.rot;
    240.                 if (GetComponent<Rigidbody>() && !GetComponent<Rigidbody>().isKinematic)
    241.                 {
    242.                     targetState.velocity = latest.velocity;
    243.                     targetState.angularVelocity = latest.angularVelocity;
    244.                 }
    245.             }
    246.         }
    247.  
    248.         setInterpolationPosition();
    249.     }
    250.  
    251.     public override float GetNetworkSendInterval()
    252.     {
    253.         return sendInterval;
    254.     }
    255. }
    256.  
     
  4. GixG17

    GixG17

    Joined:
    Aug 18, 2013
    Posts:
    105
    Thanks, I'll take a took. Could you elaborate on why/how "Transform Sync Mode" wouldn't cut it?
     
  5. thegreatzebadiah

    thegreatzebadiah

    Joined:
    Nov 22, 2012
    Posts:
    836
    Sorry, I worded that confusingly, I was just trying to say that if you set "Transform Sync Mode" to "Sync Transform" there will be no interpolation at all. I think the other sync modes do include interpolation, but either way if you only want to sync rotation you'll probably want to use your own script instead of the Network Transform component.
     
  6. GixG17

    GixG17

    Joined:
    Aug 18, 2013
    Posts:
    105
    Thanks for the clarification.

    I interpreted your code and made my own version (especially since I'm using a playerController instead of a Rigidbody) to add extra rotations. It doesn't have interpolation yet but I got the gist of it (make sure you use the latest positions and then Lerp). Custom-building your own script was simpler than I original though! Thanks!
     
  7. SweatyChair

    SweatyChair

    Joined:
    Feb 15, 2016
    Posts:
    140
    was looking codes that only sync position instead of using NetworkTransform too

    @thegreatzebadiah's codes seems very solid even with interpolation n extrapolation, saved my life and definitely will give it a try
     
    thegreatzebadiah likes this.