How to program network predication in photon/u3d?

Options
liverol
liverol ✭✭
edited April 2013 in DotNet
i found article about this :http://www.mmorpg.com/discussion2.cfm/post/2228522#2228522

but no luck,failed to do this in unity3d with photon ,
i hope photon team can do us a favour,please give a small example to show how to use network predication(with remote player smooth position sync.)

this means a lot for those who are using photon make a fps like mmog!!
appreciate!!!

Comments

  • dreamora
    Options
    I would recommend you to download the Unity3D Networking Example from the resource area in the unity3d - support area.

    In there you will find a NetworkRigidbody class which does exactly what you are looking for, all you have to do is to replace the OnSerializeNetworkView with sending and receiving through Photon.
  • liverol
    Options
    thanks for the answer,
    the question is:
    there are variables :NetworkTime and NetmessageInfo .timeStamp in u3d network functions,
    what variables should i use instead ,in Photon ?

    GameInstance.peer.RoundTripTime? or use ServerTimeInMilliSeconds ?
  • dreamora
    Options
    timestamp would be the networktime at time of sending which you just put into the hashtable you send.

    the NetworkTime is replaced through the time in milliseconds provided by the PhotonPeer / LightPeer

    that time is the base of any kind of interpolation, extrapolation and prediction as it ensures that all nodes in the network (server and clients) do their calculation basing on the same time
  • liverol
    Options
    for networking time i use peer.ServerTimeInMilliSeconds instead,
    when the packet sent i record the serverTime ,and when read packet ,get the serverTime as timeSpan for State.

    and code will be something like this:
    void OnSendMyData(LitePeer peer,Hashtable evInfo)
    {
    ...
    evInfo.Add((System.Object)"ts",peer.ServerTimeInMilliSeconds/1000d);
    ...
    }

    void OnGetMyData(Hashtable evData)
    {
    ...
    State state;
    state.timestamp =(double)evData["ts"];
    ...
    }

    and in the Update function
    ....
    //usePhoton.serverTime is a static var that keep reading the current peer serverTime.
    double interpolationTime = usePhoton.serverTime/1000.0d - m_InterpolationBackTime;
    ....



    Am i right?

    (ps. after test, the transform latency still exists,my packet sent rate is about 12-20times/s,u3d network works fine,...can't figure this out!!!)
  • Tobias
    Options
    Why do you break down everything to seconds (by /1000d)? You send 4-byte integers anyways, so you don't have to degrade the precision.
    The "transform latency" is what? And m_InterpolationBackTime?
    You will need to get the time elapsed since a transform was sent. On the receiving end this would be usePhoton.serverTime - sentTime. And sentTime should simply be peer.ServerTimeInMilliSeconds.
  • liverol
    Options
    sure,it should be like this:

    void OnSendMyData(LitePeer peer,Hashtable evInfo)
    {
    ...
    evInfo.Add((System.Object)"ts",peer.ServerTimeInMilliSeconds);
    ...
    }

    void OnGetMyData(Hashtable evData)
    {
    ...
    State state;
    state.timestamp =((int)evData["ts"])/1000d;
    ...
    }

    as dreamora said,i used the NetworkRigidBody class in unity network example,
    sorry for my english,"transform latency" means the remote player position/rotation was delayed.

    m_InterpolationBackTime is a constant for transform interpolation(check out u3d the network example).
    because the u3d example uses Seconds for caculation,so i used serverTime/1000,m_InterpolationBack is also Seconds(0.1 in the u3d example)

    You will need to get the time elapsed since a transform was sent. On the receiving end this would be usePhoton.serverTime - sentTime. And sentTime should simply be peer.ServerTimeInMilliSeconds.

    i got a little confused,
    really appreciate if you guys can make a simple example(or a piece of worked code) to show this,that helps a lot!!!
    thanks!!
  • Tobias
    Options
    I can't promise we have a sample anytime soon. I would have to check out animations first :)

    You already send the (server)timestamp when the animation was starting by evInfo.Add(). On the receiving end, you set the animation and "advance" it by:
    int startTime = (int)evData["ts"];
    passedTime = peer.ServerTimeInMilliSeconds - startTime;

    There might be still some lag of course, cause the ServerTimeInMilliSeconds is not completely accurate. Is it maybe what you see as lag?
  • liverol
    Options
    even 2 clients are running on the same machine?
    i just used local host for testing,the lag is still obvious.

    just curious why this works great with u3d network functions!!!
  • Tobias
    Options
    You have to synchronize with the server timestamp even though you are on the same machine, yes.
    Our eyes are pretty good at noticing different movements and Photon (by default) adds ~50ms of lag to accumulate commands sent to each client (to avoid transfer protocol overhead).
    If you could attach your code, maybe someone else can share his insights how to get it more accurate.
  • liverol
    Options
    here is the main networkRigidbody class
    hope someone could help!!
  • Paradoks
    Options
    Any help on this subject ?
    I am - i think - in the same situation as Liverol .
    I wonder if someone has allready made a portage of the networkRigidbody script ?
  • Paradoks
    Options
    is this even good ?
    [code2=csharp]using UnityEngine;
    using System.Collections;

    public class NetworkRigidbodyPhoton : Photon.MonoBehaviour {

    public double m_InterpolationBackTime = 0.1;
    public double m_ExtrapolationLimit = 0.5;

    internal struct State
    {
    internal double timestamp;
    internal Vector3 pos;
    internal Vector3 velocity;
    internal Quaternion rot;
    internal Vector3 angularVelocity;
    }

    // We store twenty states with "playback" information
    State[] m_BufferedState = new State[20];
    // Keep track of what slots are used
    int m_TimestampCount;

    //void OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info)
    void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info){

    // Send data to server
    if (stream.isWriting){

    Vector3 pos = rigidbody.position;
    Quaternion rot = rigidbody.rotation;
    Vector3 velocity = rigidbody.velocity;
    Vector3 angularVelocity = rigidbody.angularVelocity;

    stream.Serialize(ref pos);
    stream.Serialize(ref velocity);
    stream.Serialize(ref rot);
    stream.Serialize(ref angularVelocity);
    }
    // Read data from remote client
    else{

    Vector3 pos = Vector3.zero;
    Vector3 velocity = Vector3.zero;
    Quaternion rot = Quaternion.identity;
    Vector3 angularVelocity = Vector3.zero;
    stream.Serialize(ref pos);
    stream.Serialize(ref velocity);
    stream.Serialize(ref rot);
    stream.Serialize(ref angularVelocity);

    // Shift the buffer sideways, deleting state 20
    for (int i=m_BufferedState.Length-1;i>=1;i--)
    {
    m_BufferedState = m_BufferedState[i-1];
    }

    // Record current state in slot 0
    State state;
    state.timestamp = info.timestamp;
    state.pos = pos;
    state.velocity = velocity;
    state.rot = rot;
    state.angularVelocity = angularVelocity;
    m_BufferedState[0] = state;

    // Update used slot count, however never exceed the buffer size
    // Slots aren't actually freed so this just makes sure the buffer is
    // filled up and that uninitalized slots aren't used.
    m_TimestampCount = Mathf.Min(m_TimestampCount + 1, m_BufferedState.Length);

    // Check if states are in order, if it is inconsistent you could reshuffel or
    // drop the out-of-order state. Nothing is done here
    for (int i=0;i<m_TimestampCount-1;i++)
    {
    if (m_BufferedState.timestamp < m_BufferedState[i+1].timestamp)
    Debug.Log("State inconsistent");
    }
    }
    }

    // We have a window of interpolationBackTime where we basically play
    // By having interpolationBackTime the average ping, you will usually use interpolation.
    // And only if no more data arrives we will use extra polation
    void Update () {

    if (photonView.isMine){return;}
    // This is the target playback time of the rigid body
    double interpolationTime = PhotonNetwork.time - m_InterpolationBackTime;

    // Use interpolation if the target playback time is present in the buffer
    if (m_BufferedState[0].timestamp > interpolationTime)
    {
    // Go through buffer and find correct state to play back
    for (int i=0;i<m_TimestampCount;i++)
    {
    if (m_BufferedState.timestamp <= interpolationTime || i == m_TimestampCount-1)
    {
    // The state one slot newer (<100ms) than the best playback state
    State rhs = m_BufferedState[Mathf.Max(i-1, 0)];
    // The best playback state (closest to 100 ms old (default time))
    State lhs = m_BufferedState;

    // Use the time between the two slots to determine if interpolation is necessary
    double length = rhs.timestamp - lhs.timestamp;
    float t = 0.0F;
    // As the time difference gets closer to 100 ms t gets closer to 1 in
    // which case rhs is only used
    // Example:
    // Time is 10.000, so sampleTime is 9.900
    // lhs.time is 9.910 rhs.time is 9.980 length is 0.070
    // t is 9.900 - 9.910 / 0.070 = 0.14. So it uses 14% of rhs, 86% of lhs
    if (length > 0.0001)
    t = (float)((interpolationTime - lhs.timestamp) / length);

    // if t=0 => lhs is used directly
    transform.localPosition = Vector3.Lerp(lhs.pos, rhs.pos, t);
    transform.localRotation = Quaternion.Slerp(lhs.rot, rhs.rot, t);
    return;
    }
    }
    }
    // Use extrapolation
    else
    {
    State latest = m_BufferedState[0];

    float extrapolationLength = (float)(interpolationTime - latest.timestamp);
    // Don't extrapolation for more than 500 ms, you would need to do that carefully
    if (extrapolationLength < m_ExtrapolationLimit)
    {
    float axisLength = extrapolationLength * latest.angularVelocity.magnitude * Mathf.Rad2Deg;
    Quaternion angularRotation = Quaternion.AngleAxis(axisLength, latest.angularVelocity);

    rigidbody.position = latest.pos + latest.velocity * extrapolationLength;
    rigidbody.rotation = angularRotation * latest.rot;
    rigidbody.velocity = latest.velocity;
    rigidbody.angularVelocity = latest.angularVelocity;
    }
    }
    }
    }[/code2]