using UnityEngine; using System; using System.Collections; using System.Reflection; // For the client which owns the player, the local transform contains // the current client _predicted_ position/rotation. The buffered state // contains the _server_ position/rotation, which is the correct state. The // predicted and server states are interpolated together so the predicted // state will always converge with the server state. // // For the server the transform contains the current 100% legal position and // rotation of the player in question. He sends this over the network // to all clients. // // For other clients, which don't own this player, the buffered state contains // the correct values. This is played back with a 100 ms delay to elminate // choppyness. The delay needs to be higher is the ping time between the server // and client is larger than 100 ms. public class GraduallyUpdateState : MonoBehaviour { Component targetController; FieldInfo isMovingFieldInfo; internal struct State { internal double timestamp; internal Vector3 pos; internal Quaternion rot; } // How far behind should server data be played back during remote player // interpolation. The poorer the connection the higher this value needs to // be. Fast connections should do fine with 0.1. The server latency // affects this the most. public double m_InterpolationBackTime = 0.1; // We store twenty states with "playback" information State[] m_BufferedState = new State[20]; State[] m_LocalBufState = new State[20]; // Keep track of what slots are used int m_TimestampCount; int m_LocalStateCount; bool m_IsMine = false; // Stat variables for latency, msg rate float m_Timer = Time.time + 1; int m_MsgCounter = 0; int m_MsgRate = 0; double m_MsgLatencyTotal = 0; double m_MsgLatency = 0; // Stat variabels for prediction stuff float m_TimeAccuracy = 0; float m_PredictionAccuracy = 0; bool m_FixError = false; Vector3 m_NewPosition; // The position vector distance to start error correction. The higher the latency the higher this // value should be or it constantly tries to correct errors in prediction, of course this depends // on the game too. public float m_PredictionThreshold = 0.25F; // Time difference in milliseconds where we check for error in position. If the server time value // of a state is too different from the local state time then the error correction comparison is // highly unreliable and you might try to correct more errors than there really are. public float m_TimeThreshold = 0.05F; Rect connInfo = new Rect (Screen.width-170,40,160,50); // We need to grab a reference to the isMoving variable in the javascript ThirdPersonController script void Start() { targetController = GetComponent("ThirdPersonController"); isMovingFieldInfo=targetController.GetType().GetField("isMoving"); } // Convert field info from character controller script to a local bool variable bool targetIsMoving { get { return (bool) isMovingFieldInfo.GetValue(targetController); } } IEnumerator MonitorLocalMovement() { while (true) { yield return new WaitForSeconds(1/15); // Shift buffer contents, oldest data erased, 18 becomes 19, ... , 0 becomes 1 for (int i=m_LocalBufState.Length-1;i>=1;i--) { m_LocalBufState[i] = m_LocalBufState[i-1]; } // Save currect received state as 0 in the buffer, safe to overwrite after shifting State state; state.timestamp = Network.time; state.pos = transform.position; state.rot = transform.rotation; m_LocalBufState[0] = state; // Increment state count but never exceed buffer size m_LocalStateCount = Mathf.Min(m_LocalStateCount + 1, m_LocalBufState.Length); // // Check if the client side prediction has an error // // Find the local buffered state which is closest to network state in time int j = 0; bool match = false; for (j=0; j m_PredictionThreshold) { //Debug.Log("Error in prediction("+m_PredictionAccuracy+"), local is " + m_LocalBufState[j].pos + " network is " + m_BufferedState[0].pos); //Debug.Log("Local time: " + m_LocalBufState[j].timestamp + " Network time: " + m_BufferedState[0].timestamp); // Find how far we travelled since the prediction failed Vector3 localMovement = m_LocalBufState[j].pos - m_LocalBufState[0].pos; // "Erase" old values in the local buffer m_LocalStateCount = 1; // New position which we need to converge to in the update loop m_NewPosition = m_BufferedState[0].pos + localMovement; // Trigger the new position convergence routine m_FixError = true; } else { m_FixError = false; } } } void OnGUI() { if (m_IsMine) { connInfo = GUILayout.Window(0, connInfo, MakeConnInfoWindow, "Local Player"); } } void MakeConnInfoWindow(int windowID) { GUILayout.Label(string.Format("{0} msg/s {1,4:f3} ms", m_MsgRate, m_MsgLatency)); GUILayout.Label(string.Format("Time Difference : {0,3:f3}", m_TimeAccuracy)); GUILayout.Label(string.Format("Prediction Difference : {0,3:f3}", m_PredictionAccuracy)); if (Time.time - m_Timer > 0) { m_MsgRate = m_MsgCounter; m_Timer = Time.time + 1; m_MsgCounter = 0; if (m_MsgRate != 0) { m_MsgLatency = (m_MsgLatencyTotal/(double)m_MsgRate)*1000F; } else { m_MsgLatency = 0; } m_MsgLatencyTotal = 0; } } // The network sync routine makes sure m_BufferedState always contains the last 20 updates // The latest update is in slot 0, oldest in slot 19 void OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info) { // Always send transform (depending on reliability of the network view) if (stream.isWriting) { Vector3 pos = transform.position; Quaternion rot = transform.rotation; stream.Serialize(ref pos); stream.Serialize(ref rot); } // When receiving, buffer the information else { m_MsgCounter++; m_MsgLatencyTotal += (Network.time-info.timestamp); // Receive latest state information Vector3 pos = Vector3.zero; Quaternion rot = transform.rotation;//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[i] = 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 interpolationTime) { for (int i=0;i 0.0001) t = (float)((interpolationTime - lhs.timestamp) / length); // if t=0 => lhs is used directly transform.position = Vector3.Lerp(lhs.pos, rhs.pos, t); transform.rotation = Quaternion.Slerp(lhs.rot, rhs.rot, t); //m_InterpolationTime = (Network.time - m_BufferedState[i].timestamp)*1000; return; } } } // 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. else { State latest = m_BufferedState[0]; transform.localPosition = latest.pos; transform.localRotation = latest.rot; //Debug.Log("Extrapolating " + latest.pos); } } } }