// We interpolate only on other clients, not on the server, and not on the local client) void Update() { //Loop all states for (int i = 0; i < bufferedStatesCount; i++) { //State time is local time - state counter * interval between states float stateTime = lastBufferedStateTime - i * updateRate; //Find the first state that match now - interpTime (or take the last buffer entry) if (stateTime <= Time.time - interpolationBackTime || i == bufferedStatesCount - 1) { //Get one step after and one before the time CharacterNetworkSync.CharacterState afterState = bufferedStates[Mathf.Max(i - 1, 0)]; float afterStateTime = lastBufferedStateTime - (i - 1) * updateRate; CharacterNetworkSync.CharacterState beforeState = bufferedStates[i]; float beforeStateTime = lastBufferedStateTime - i * updateRate;; // Use the time between the two slots to determine if interpolation is necessary double length = afterStateTime - beforeStateTime; 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)((Time.time - interpolationBackTime - beforeStateTime) / length); } //Do the actual interpolation transform.position = Vector3.Lerp(beforeState.position, afterState.position, t); transform.rotation = Quaternion.Slerp(beforeState.rotation, afterState.rotation, t); break; } } }
/// <summary> /// Called when a state is received from server /// </summary> /// <param name="newState"></param> public void ReceiveState(CharacterNetworkSync.CharacterState newState) { //Other Clients: Shift buffer and store at first position for (int i = bufferedStates.Length - 1; i >= 1; i--) { bufferedStates[i] = bufferedStates[i - 1]; } bufferedStates[0] = newState; bufferedStatesCount = Mathf.Min(bufferedStatesCount + 1, bufferedStates.Length); //Other Clients: Check that states are in good order for (int i = 0; i < bufferedStatesCount - 1; i++) { if (bufferedStates[i].state < bufferedStates[i + 1].state) { Debug.LogWarning("Warning, State are in wrong order"); } } lastBufferedStateTime = Time.time; }
/// <summary> /// Receive a good state from the server /// Discard input older than this good state /// Replay missing inputs on top of it /// </summary> /// <param name="serverRecvState"></param> /// <param name="serverRecvPosition"></param> /// <param name="serverRecvRotation"></param> void ServerState(CharacterNetworkSync.CharacterState characterState) { int serverRecvState = characterState.state; Vector3 serverRecvPosition = characterState.position; Quaternion serverRecvRotation = characterState.rotation; //Client: Check that we received a new state from server (not some delayed packet) if (clientAckState < serverRecvState) { //Client: Set the last server ack state clientAckState = serverRecvState; //Client: Discard all input states where state are before the ack state bool loop = true; while (loop && inputStates.Count > 0) { CharacterInput.InputState state = inputStates.Peek(); if (state.inputState <= clientAckState) { inputStates.Dequeue(); } else { loop = false; } } //Client: store actual Character position, rotation and velocity along with current input CharacterInput.InputState oldState = characterInput.currentInput; Vector3 oldPos = transform.position; Quaternion oldRot = transform.rotation; //Client: move back the player to the received server position serverLastRecvPosition = serverRecvPosition; serverLastRecvRotation = serverRecvRotation; transform.position = serverLastRecvPosition; transform.rotation = serverLastRecvRotation; //Client: replay all input based on new correct position foreach (CharacterInput.InputState state in inputStates) { //Set the input characterInput.currentInput = state; //Run the simulation characterMovement.RunUpdate(Time.fixedDeltaTime); characterRotation.RunUpdate(Time.fixedDeltaTime); } //Client: save the new predicted character position serverLastPredPosition = transform.position; serverLastPredRotation = transform.rotation; //Client: restore initial position, rotation and velocity characterInput.currentInput = oldState; transform.position = oldPos; transform.rotation = oldRot; //Client: Check if a prediction error occured in the past //Debug.Log("States in queue: " + inputStates.Count + " Predicted distance: " + Vector3.Distance(transform.position, serverLastPredPosition)); if (Vector3.Distance(transform.position, serverLastPredPosition) > MAX_SERVER_DISTANCE_SNAP) { //Client: Snap to correct position Debug.LogWarning("Prediction error!"); transform.position = Vector3.Lerp(transform.position, serverLastPredPosition, Time.fixedDeltaTime * 10); transform.rotation = Quaternion.Lerp(transform.rotation, serverLastPredRotation, Time.fixedDeltaTime * 10); } } }