I have coded this movement interpolation for Unity3d using Photon.
OnPhotonSerializeView gets called at the tickrate.
Any kind of criticism/improvement suggestions are welcome. I tried to make the code as plain as possible because I don't see this as something that should be over-engineered because it is rather algorithmic in nature.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerInterpolation : MonoBehaviour {
Queue<Vector3> positions = new Queue<Vector3>();
Queue<Quaternion> rotations = new Queue<Quaternion>();
Queue<int> frameNumbers = new Queue<int>();
static int amountBuffered = 1;
static float oneTickTime = 1.0f / Settings.tickRate;
PhotonView photonView;
int localSentFrameNumber = 0;
int lastReceivedFrameNumber = -1;
int lastFrameNumberMovedTowards = 0;
int expectedExecuteFrameNumber = 0; //An estimation of what frame number should be executing at this time.
float expectedExecuteFrameNumberLastExecutedTime = 0;
Vector3 moveTowards;
float moveTowardsSpeed = 1.0f;
bool moveInProgress = false;
void Start() {
photonView = gameObject.GetComponent<PhotonView>();
moveTowards = transform.position;
}
void Update() {
if (photonView.isMine) return;
transform.position = Vector3.MoveTowards(transform.position, moveTowards, moveTowardsSpeed * Time.deltaTime);
if (Mathf.Abs(Vector3.Distance(transform.position, moveTowards)) < 0.001f) {
moveInProgress = false;
} else {
moveInProgress = true;
}
if (expectedExecuteFrameNumberLastExecutedTime + oneTickTime < Time.realtimeSinceStartup) {
expectedExecuteFrameNumber++;
expectedExecuteFrameNumberLastExecutedTime = Time.realtimeSinceStartup;
}
if (frameNumbers.Count > 0 && ((frameNumbers.Peek() + amountBuffered <= lastReceivedFrameNumber) || frameNumbers.Peek() <= expectedExecuteFrameNumber - amountBuffered)) {
if (lastReceivedFrameNumber - frameNumbers.Peek() > amountBuffered) {
int trimUntil = lastReceivedFrameNumber - amountBuffered;
while (frameNumbers.Peek() < trimUntil) {
positions.Dequeue();
rotations.Dequeue();
frameNumbers.Dequeue();
}
}
Vector3 position = positions.Dequeue();
Quaternion rotation = rotations.Dequeue();
int frameNumber = frameNumbers.Dequeue();
if (moveInProgress == true) {
transform.position = moveTowards;
}
float distance = Vector3.Distance(transform.position, position);
moveTowards = position;
moveTowardsSpeed = distance * (frameNumber - lastFrameNumberMovedTowards) / oneTickTime;
lastFrameNumberMovedTowards = frameNumber;
expectedExecuteFrameNumber = frameNumber;
expectedExecuteFrameNumberLastExecutedTime = Time.realtimeSinceStartup;
}
}
void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info) {
if (stream.isWriting) {
stream.SendNext(transform.position);
stream.SendNext(transform.rotation);
stream.SendNext(localSentFrameNumber++);
} else {
positions.Enqueue((Vector3)stream.ReceiveNext());
rotations.Enqueue((Quaternion)stream.ReceiveNext());
int frameNumber = (int)stream.ReceiveNext();
frameNumbers.Enqueue(frameNumber);
lastReceivedFrameNumber = frameNumber;
}
}
}