Search Unity

Sync Object Position In Multiplayer?

Discussion in 'Multiplayer' started by SomeGuy22, Jul 7, 2012.

  1. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    I know I just posted today but I'm having problems again (Sorry, I'm new to networking)

    This script from the networking example should sync objects positions and rotations with the server, but whenever the scene loads, the object is set to some weird position and not where it should be when the scene starts.

    It requires a network view component.

    This is the script:

    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class NetworkInterpolatedTransform : MonoBehaviour {
    5.    
    6.     public double interpolationBackTime = 0.1;
    7.    
    8.     internal struct  State
    9.     {
    10.         internal double timestamp;
    11.         internal Vector3 pos;
    12.         internal Quaternion rot;
    13.     }
    14.  
    15.     // We store twenty states with "playback" information
    16.     State[] m_BufferedState = new State[20];
    17.     // Keep track of what slots are used
    18.     int m_TimestampCount;
    19.    
    20.     void OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info)
    21.     {
    22.         // Always send transform (depending on reliability of the network view)
    23.         if (stream.isWriting)
    24.         {
    25.             Vector3 pos = transform.localPosition;
    26.             Quaternion rot = transform.localRotation;
    27.             stream.Serialize(ref pos);
    28.             stream.Serialize(ref rot);
    29.         }
    30.         // When receiving, buffer the information
    31.         else
    32.         {
    33.             // Receive latest state information
    34.             Vector3 pos = Vector3.zero;
    35.             Quaternion rot = Quaternion.identity;
    36.             stream.Serialize(ref pos);
    37.             stream.Serialize(ref rot);
    38.            
    39.             // Shift buffer contents, oldest data erased, 18 becomes 19, ... , 0 becomes 1
    40.             for (int i=m_BufferedState.Length-1;i>=1;i--)
    41.             {
    42.                 m_BufferedState[i] = m_BufferedState[i-1];
    43.             }
    44.            
    45.             // Save currect received state as 0 in the buffer, safe to overwrite after shifting
    46.             State state;
    47.             state.timestamp = info.timestamp;
    48.             state.pos = pos;
    49.             state.rot = rot;
    50.             m_BufferedState[0] = state;
    51.            
    52.             // Increment state count but never exceed buffer size
    53.             m_TimestampCount = Mathf.Min(m_TimestampCount + 1, m_BufferedState.Length);
    54.  
    55.             // Check integrity, lowest numbered state in the buffer is newest and so on
    56.             for (int i=0;i<m_TimestampCount-1;i++)
    57.             {
    58.                 if (m_BufferedState[i].timestamp < m_BufferedState[i+1].timestamp)
    59.                     Debug.Log("State inconsistent");
    60.             }
    61.            
    62.             //Debug.Log("stamp: " + info.timestamp + "my time: " + Network.time + "delta: " + (Network.time - info.timestamp));
    63.         }
    64.     }
    65.    
    66.     // This only runs where the component is enabled, which is only on remote peers (server/clients)
    67.     void Update () {
    68.         double currentTime = Network.time;
    69.         double interpolationTime = currentTime - interpolationBackTime;
    70.         // We have a window of interpolationBackTime where we basically play
    71.         // By having interpolationBackTime the average ping, you will usually use interpolation.
    72.         // And only if no more data arrives we will use extrapolation
    73.        
    74.         // Use interpolation
    75.         // Check if latest state exceeds interpolation time, if this is the case then
    76.         // it is too old and extrapolation should be used
    77.         if (m_BufferedState[0].timestamp > interpolationTime)
    78.         {
    79.             for (int i=0;i<m_TimestampCount;i++)
    80.             {
    81.                 // Find the state which matches the interpolation time (time+0.1) or use last state
    82.                 if (m_BufferedState[i].timestamp <= interpolationTime || i == m_TimestampCount-1)
    83.                 {
    84.                     // The state one slot newer (<100ms) than the best playback state
    85.                     State rhs = m_BufferedState[Mathf.Max(i-1, 0)];
    86.                     // The best playback state (closest to 100 ms old (default time))
    87.                     State lhs = m_BufferedState[i];
    88.                    
    89.                     // Use the time between the two slots to determine if interpolation is necessary
    90.                     double length = rhs.timestamp - lhs.timestamp;
    91.                     float t = 0.0F;
    92.                     // As the time difference gets closer to 100 ms t gets closer to 1 in
    93.                     // which case rhs is only used
    94.                     if (length > 0.0001)
    95.                         t = (float)((interpolationTime - lhs.timestamp) / length);
    96.                    
    97.                     // if t=0 => lhs is used directly
    98.                     transform.localPosition = Vector3.Lerp(lhs.pos, rhs.pos, t);
    99.                     transform.localRotation = Quaternion.Slerp(lhs.rot, rhs.rot, t);
    100.                     return;
    101.                 }
    102.             }
    103.         }
    104.         // Use extrapolation. Here we do something really simple and just repeat the last
    105.         // received state. You can do clever stuff with predicting what should happen.
    106.         else
    107.         {
    108.             State latest = m_BufferedState[0];
    109.            
    110.             transform.localPosition = latest.pos;
    111.             transform.localRotation = latest.rot;
    112.         }
    113.     }
    114. }
    115.  
    what is wrong here? Thanks!
     
  2. appels

    appels

    Joined:
    Jun 25, 2010
    Posts:
    2,687
    make sure you only run this script on clients, not the server.
     
  3. gfoot

    gfoot

    Joined:
    Jan 5, 2011
    Posts:
    550
    I haven't read the script, and the problem may indeed lie there, but also bear in mind that loading a level in a network game requires a lot of subtle precautions. This kind of symptom where objects are not in their correct positions after a level load often means that you didn't disable the message queue properly while the level was loading - or generally that you were careless about how you triggered the level load, leading to some desync between the server and client. The problem there can be that the server sends messages to be received by a certain network view, but the client doesn't yet have that network view set up. You should see some errors on the console though if this is the case.

    If you haven't already done so, read this in detail: http://docs.unity3d.com/Documentation/Components/net-NetworkLevelLoad.html
     
  4. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    Well there's no errors, and it happens even if I start the scene in build mode, not network load.

    I removed the script, but is there a way to do this manually? Like sync objects in a scene with the client?
     
  5. appels

    appels

    Joined:
    Jun 25, 2010
    Posts:
    2,687
    The script is for interpolation which I think is way to complicated for your use.
    Start off with simple sync and add the interpolation later.
    You need to use the OnSerializeNetworkView method.
     
    james401 likes this.
  6. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    I tried using this code:

    Code (csharp):
    1.  
    2. #pragma strict
    3.  
    4. function OnSerializeNetworkView (stream : BitStream, info : NetworkMessageInfo)
    5.     {
    6.         var position : Vector3;
    7.         var rotation : Quaternion;
    8.        
    9.         if (stream.isWriting)
    10.         {
    11.             position = transform.position;
    12.             rotation = transform.rotation;
    13.             stream.Serialize (position);
    14.             stream.Serialize (rotation);
    15.         }
    16.         else
    17.         {
    18.             stream.Serialize (position);
    19.             stream.Serialize (rotation);
    20.             transform.position = position;
    21.             transform.rotation = rotation;
    22.         }
    23.     }
    24.  
    with that dragged in to the networkView "observe" option.

    It still didn't work.

    What is wrong with it? Why aren't the positions syncing? (By the way this is used on some enemies with AI)
     
  7. Bluntweapon

    Bluntweapon

    Joined:
    Feb 24, 2012
    Posts:
    158
    What exactly does this mean? That the objects get set to weird locations even if you're not connected to a server? If this is the case that means you're setting the Transform's position somewhere else, and could be overriding your network syncs.

    Well...certainly you could do it with an RPC or something if you only have to do it once or very infrequently.

    There's nothing wrong with this code per sé. Add some Debug.Logs to see if you're even sending or receiving the correct data in the first place as it may have nothing to do with the networking code.
     
  8. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    Thanks for the help, I'm new to networking and really used to single player.

    The interpolation script works but only if its on a player controlled network view.

    I'll do some more research to see if I can get the transform sync working.

    Any tips for getting this script to work is appreciated! Thanks in advance!
     
  9. appels

    appels

    Joined:
    Jun 25, 2010
    Posts:
    2,687
    The script looks ok, it should work. Put some debugs in and try and find out why it doesn't work.
     
  10. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    I put debugs everywhere on that script and none of them have shown up. That means only 1 thing: the OnSerializeNetworkView function is not being called. How is this function called in the first place? Is it only called on actual players?

    Because then it wouldn't work on the NPC's I'm trying to put this on.
     
  11. gfoot

    gfoot

    Joined:
    Jan 5, 2011
    Posts:
    550
    Unity doesn't know what "players" or "NPCs" are, it only knows about GameObjects and Components. OnSerializeNetworkView is called on whatever component the NetworkView is pointing at (look in the Inspector). By default the NetworkView will watch the transform, and if that's all you want then you don't need to supply any code. You could check that works on its own, to make sure your client and server are set up correctly. Then if you need more specialised serialization, point the NetworkView at your own script and implement OnSerializeNetworkView.
     
  12. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    So NetworkView syncs whatever component it's pointing at? Because I've had the networkView point at the transform for the NPC's and they still don't match up. Does that mean my server is set up incorrectly? I'm pretty sure it isn't.

    How do you normally sync objects in the scene (that aren't players)? I've put networkViews all over the npcs covering every single component (even though it's not necessary) and they still haven't synced up.
     
  13. gfoot

    gfoot

    Joined:
    Jan 5, 2011
    Posts:
    550
    If your NPCs have NetworkViews attached, watching their transforms, and the server-client relationship is working properly, then whoever owns an NPC's NetworkView should be able to move that NPC around and have the transform synced automatically on the other peers. You need to put your own code in to disable the regular move-NPC-around code on non-owning peers, otherwise they'll conflict. But generally, watching transforms should just work without much effort on your part - so long as it's the owner that's trying to move the object.

    If you want to synchronize custom script data then you need to point the NetworkView at your own script and implement OnSerializeNetworkView. It won't do anything magically, you need to write the serialization code yourself. You probably only want/need one NetworkView per object, pointing at a component that knows how to serialize all of the other components attached to the object (including the transform).
     
  14. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    Thanks for the help! So what you're saying is that only the host of the server should run the npc scripts? (They control themselves, with no involvement of players) So I would have to disable the scripts only if you are the host of the server. How can the script tell if you're the host of the server?
     
  15. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    Never mind I got the cubes to sync up! (This is what I did for those that want to know)

    I had one networkview observe the transform of the npc while another observed the npc AI script. (I might have also fixed some bugs in the players scripts that also caused it to work) I am running the npc scripts on all clients, so it's not that efficient, but it works, and that's all that matters! :D
     
  16. Bluntweapon

    Bluntweapon

    Joined:
    Feb 24, 2012
    Posts:
    158
    This seems...kinda redundant. I'm happy for you and all, but I'd like to think the point of this exorcise is to understand how architecture works and not just hack together a solution?

    Network.isServer
     
  17. gfoot

    gfoot

    Joined:
    Jan 5, 2011
    Posts:
    550
    In the spirit of simplest solution first, I would begin with just syncing transforms and disabling local logic for remotely-owned objects (!networkView.isMine). It should be easier to understand that way around, and you retain some control over what gets sent over the wire, which tends to become quite important in the long term.

    But if you're willing to go more complex, you can run the AI on all clients, and use NetworkView (or even RPCs) to sync the AI's goal. The simulations will diverge, but it's up to you whether that's a problem. If you balance it right, it can work quite well. It's all about lying convincingly, anyway.

    Also, it doesn't have to be the server that controls the NPCs - so long as each is only controlled by one peer. So the server could share the NPCs out amongst the clients, and let them do the controlling. I'm not sure how easy it is to transfer ownership of an existing instance though, in Unity.
     
  18. Bluntweapon

    Bluntweapon

    Joined:
    Feb 24, 2012
    Posts:
    158
    You would have to change the NetworkViewID on that specific NetworkView to change ownership. Whoever calls Network.AllocateViewID owns the NetworkViews that uses that ID, so all you'd have to do is call Allocate on the client you want to run the scripts, send it off to the other players by RPC, and change the ViewIDs at the same time.
     
  19. gfoot

    gfoot

    Joined:
    Jan 5, 2011
    Posts:
    550
    Oh, I see. Changing the view ID sounds kind of unstable. I tried it in one of my demos and it does work, but Unity reports errors because there are still packets in flight using the old ID which now have nowhere to go.

    So I guess it's possible, but not ideal. It's a shame you can't just change the owner and have Unity do the heavy lifting - that's what it's there for.