NetworkInterpolatedTransform to PUN

I transfered the NetworkInterpolatedTransform.cs, which works perfectly fine with unitys build in networking, of the Unity Network Example to PUN. But, in contrast to unity networking, the movement of the remote players is far from smooth. I get a much better result with a simple lerp from the actual position to the new received position. With a interpolationBackTime of 0.1(default Time) the remote players just jump from one position to the next. When i set the default Time to 0.2 or higher, the movement gets smoother(but far away from a simple lerp), but the received data is also more outdated. I use Photon Cloud. Please let me know, if you figure out what my mystake is, that would be great :).

[code2=csharp]using UnityEngine;
using System.Collections;

public class NetworkInterpolatedTransform : Photon.MonoBehaviour {

public double interpolationBackTime = 0.1;

internal struct State
internal double timestamp;
internal Vector3 pos;
internal Quaternion rot;

// We store twenty states with "playback" information
State[] m_BufferedState = new State[20];

// Keep track of what slots are used
int m_TimestampCount;

void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
// Always send transform (depending on reliability of the network view)
if (stream.isWriting)
Vector3 pos = transform.localPosition;
Quaternion rot = transform.localRotation;
stream.Serialize(ref pos);
stream.Serialize(ref rot);
// When receiving, buffer the information
// Receive latest state information
Vector3 pos = transform.position;
Quaternion rot = Quaternion.identity;
stream.Serialize(ref pos);
stream.Serialize(ref rot);

// Shift buffer contents, oldest data erased, 18 becomes 19, ... , 0 becomes 1
for (int i = m_BufferedState.Length-1; i >= 1; i--)
m_BufferedState = m_BufferedState[i-1];

// Save currect received state as 0 in the buffer, safe to overwrite after shifting
State state;
state.timestamp = info.timestamp;
state.pos = pos;
state.rot = rot;
m_BufferedState[0] = state;

// Increment state count but never exceed buffer size
m_TimestampCount = Mathf.Min(m_TimestampCount + 1, m_BufferedState.Length);

// Check integrity, lowest numbered state in the buffer is newest and so on
for (int i = 0; i < m_TimestampCount-1; i++)
if (m_BufferedState.timestamp < m_BufferedState[i+1].timestamp)
Debug.Log("State inconsistent");

//Debug.Log("stamp: " + info.timestamp + "my time: " + PhotonNetwork.time + "delta: " + (PhotonNetwork.time - info.timestamp));

// This only runs where the component is enabled, which is only on remote peers (server/clients)
void Update ()
double currentTime = PhotonNetwork.time;
double interpolationTime = currentTime - interpolationBackTime;
// 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 extrapolation

// Use interpolation
// Check if latest state exceeds interpolation time, if this is the case then
// it is too old and extrapolation should be used
if (m_BufferedState[0].timestamp > interpolationTime)
for (int i = 0; i < m_TimestampCount; i++)
// Find the state which matches the interpolation time (time+0.1) or use last state
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
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);
// Use extrapolation. Here we do something really simple and just repeat the last
// received state. You can do clever stuff with predicting what should happen.
State latest = m_BufferedState[0];
transform.localPosition = latest.pos;
transform.localRotation = latest.rot;


  • Azaldur
    I did some further testing. The buffer always contains data, but I realized that while in the network example interpolation is always used when the remote player is moving, with photon it switches between extrapolation and interpolation. I also host a photon server instead of using cloud, now. So there shouldn't be any ping differences to unitys networking solution. If interpolation or extrapolation is used is decided by this line: if (m_BufferedState[0].timestamp > interpolationTime){// interpolation} else {//extrapolation}. Does that help someone to find a soultion? It's very annoying that it doesn't work and I have no idea why. Is there maybe an error with Photons timestamp?
  • MoDDIB
    I got the same trouble when I tried this.
  • Leepo
    The script is working fine, I've tested it quite a bit.

    (PS i would add this to the script:
    void Awake()
    if (photonView.isMine)
    this.enabled = false;//Only enable inter/extrapol for remote players. (The "OnSerializePhotonView will still be called when disabled!)

    Now, the problem is the following:
    Unity Networking has a default sendrate of 25times/second, PUN uses a sendrate of 20 times/second and 10times/second for OnSerialize. You can modify this!

    Because OnSerialize is only send once every 100msec per default, the default interpolationtime of 100ms will mess up: It jumps between inter and extrapolation which causes weird behaviour.
    Either stick with the default sendrate of 10 and set the interpolationtime to 0.15 (150ms). Or raise the sendrate to 15/20 (..25?!).
    Do keep an eye on your network traffic, raising the sendrate will greatly affect traffic so be smart with your interpolation time.