IEnumerator SendResultsC(Results res) { yield return(new WaitForSeconds(UnityEngine.Random.Range(0.21f, 0.38f))); #endif if (isLocalPlayer) { Debug_UI.UpdateUI(res.position, res.position, res.position, currentTick, res.timestamp); serverResults = res; worldUpdated = true; } else { currentTick++; updateCount++; lastResults = res; GameManager.PlayerTick(this, lastResults); if (tickUpdate != null) { tickUpdate(res, false); } histResults[updateCount % 3] = lastResults; if (currentTick > 2 && currentTick % GameManager.singleton.networkSettings.maxDeltaTicks != 0) { //if (Time.fixedTime - 2f > startTime) startTime = Mathf.Min(Time.time, Time.fixedTime); //else // startTime = Time.fixedTime - ((Time.fixedTime - startTime) / (Time.fixedDeltaTime * _sendUpdates) - 1) * (Time.fixedDeltaTime * _sendUpdates); } else { startTime = Mathf.Min(Time.time, Time.fixedTime); } } }
void Awake() { singleton = this; }
IEnumerator SendResultsC(Results res) { yield return(new WaitForSeconds(UnityEngine.Random.Range(0.21f, 0.38f))); #endif if (isLocalPlayer) { foreach (Results t in clientResults) { if (t.timestamp == res.timestamp) { Debug_UI.UpdateUI(posEnd, res.position, t.position, currentTick, res.timestamp); } } if (serverResultList.Count > data.serverResultsBuffer) { serverResultList.RemoveAt(0); } if (!ServerResultsContainTimestamp(res.timestamp)) { serverResultList.Add(res); } serverResults = SortServerResultsAndReturnFirst(); if (serverResultList.Count >= data.serverResultsBuffer) { reconciliate = true; } } else { currentTick++; if (!isServer) { serverResults = res; if (tickUpdate != null) { tickUpdate(res); } } if (currentTick > 2) { serverResults = res; posStart = posEnd; rotStart = rotEnd; headStartRot = headEndRot; headEndRot = res.camX; //if (Time.fixedTime - 2f > startTime) startTime = Time.fixedTime; //else // startTime = Time.fixedTime - ((Time.fixedTime - startTime) / (Time.fixedDeltaTime * _sendUpdates) - 1) * (Time.fixedDeltaTime * _sendUpdates); posEnd = posEndO; rotEnd = rotEndO; groundPointTime = serverResults.groundPointTime; posEndG = serverResults.groundPoint; posEndO = serverResults.position; rotEndO = serverResults.rotation; } else { startTime = Time.fixedTime; serverResults = res; posStart = serverResults.position; rotStart = serverResults.rotation; headStartRot = headEndRot; headEndRot = res.camX; posEnd = posStart; rotEnd = rotStart; groundPointTime = serverResults.groundPointTime; posEndG = posEndO.y; posEndO = posStart; rotEndO = rotStart; } } }
void CmdSendInputs (Inputs inp, Results res) { #else void CmdSendInputs (Inputs inp) { #endif #if (SIMULATE) #if (CLIENT_TRUST) StartCoroutine (SendInputs (inp, res)); #else StartCoroutine (SendInputs (inp)); #endif } #if (CLIENT_TRUST) IEnumerator SendInputs (Inputs inp, Results res) { #else IEnumerator SendInputs (Inputs inp) { #endif yield return new WaitForSeconds (UnityEngine.Random.Range (0.21f, 0.28f)); #endif if (!isLocalPlayer) { if (clientInputs.Count > data.clientInputsBuffer) clientInputs.RemoveAt (0); if (!ClientInputsContainTimestamp (inp.timestamp)) clientInputs.Add (inp); #if (CLIENT_TRUST) tempResults = res; #endif currentTFixedUpdates += sendUpdates; if (data.debug && lastTick + 1 != inp.timestamp && lastTick != -1) { Debug.Log ("Missing tick " + lastTick + 1); } lastTick = inp.timestamp; } } [ClientRpc] void RpcSendResults (Results res) { if (isServer) return; #if (SIMULATE) StartCoroutine (SendResults (res)); } IEnumerator SendResults (Results res) { yield return new WaitForSeconds (UnityEngine.Random.Range (0.21f, 0.38f)); #endif if (isLocalPlayer) { foreach (Results t in clientResults) { if (t.timestamp == res.timestamp) Debug_UI.UpdateUI (posEnd, res.position, t.position, currentTick, res.timestamp); } if (serverResultList.Count > data.serverResultsBuffer) serverResultList.RemoveAt (0); if (!ServerResultsContainTimestamp (res.timestamp)) serverResultList.Add (res); serverResults = SortServerResultsAndReturnFirst (); if (serverResultList.Count >= data.serverResultsBuffer) reconciliate = true; } else { currentTick++; if (!isServer) { serverResults = res; onTickUpdate.Invoke (res); } if (currentTick > 2) { serverResults = res; posStart = posEnd; rotStart = rotEnd; headStartRot = headEndRot; headEndRot = res.camX; //if (Time.fixedTime - 2f > startTime) startTime = Time.fixedTime; //else // startTime = Time.fixedTime - ((Time.fixedTime - startTime) / (Time.fixedDeltaTime * _sendUpdates) - 1) * (Time.fixedDeltaTime * _sendUpdates); posEnd = posEndO; rotEnd = rotEndO; groundPointTime = serverResults.groundPointTime; posEndG = serverResults.groundPoint; posEndO = serverResults.position; rotEndO = serverResults.rotation; } else { startTime = Time.fixedTime; serverResults = res; posStart = serverResults.position; rotStart = serverResults.rotation; headStartRot = headEndRot; headEndRot = res.camX; posEnd = posStart; rotEnd = rotStart; groundPointTime = serverResults.groundPointTime; posEndG = posEndO.y; posEndO = posStart; rotEndO = rotStart; } } } Results SortServerResultsAndReturnFirst () { Results tempRes; for (int x = 0; x < serverResultList.Count; x++) { for (int y = 0; y < serverResultList.Count - 1; y++) { if (serverResultList [y].timestamp > serverResultList [y + 1].timestamp) { tempRes = serverResultList [y + 1]; serverResultList [y + 1] = serverResultList [y]; serverResultList [y] = tempRes; } } } if (serverResultList.Count > data.serverResultsBuffer) serverResultList.RemoveAt (0); return serverResultList [0]; } bool ServerResultsContainTimestamp (int timeStamp) { for (int i = 0; i < serverResultList.Count; i++) { if (serverResultList [i].timestamp == timeStamp) return true; } return false; } Inputs SortClientInputsAndReturnFirst () { Inputs tempInp; for (int x = 0; x < clientInputs.Count; x++) { for (int y = 0; y < clientInputs.Count - 1; y++) { if (clientInputs [y].timestamp > clientInputs [y + 1].timestamp) { tempInp = clientInputs [y + 1]; clientInputs [y + 1] = clientInputs [y]; clientInputs [y] = tempInp; } } } if (clientInputs.Count > data.clientInputsBuffer) clientInputs.RemoveAt (0); return clientInputs [0]; } bool ClientInputsContainTimestamp (int timeStamp) { for (int i = 0; i < clientInputs.Count; i++) { if (clientInputs [i].timestamp == timeStamp) return true; } return false; } //Function which replays the old inputs if prediction errors occur void Reconciliate () { for (int i = 0; i < clientResults.Count; i++) { if (clientResults [i].timestamp == serverResults.timestamp) { clientResults.RemoveRange (0, i); for (int o = 0; o < clientInputs.Count; o++) { if (clientInputs [o].timestamp == serverResults.timestamp) { clientInputs.RemoveRange (0, o); break; } } break; } } tempResults = serverResults; controller.enabled = true; for (int i = 1; i < clientInputs.Count - 1; i++) { tempResults = MoveCharacter (tempResults, clientInputs [i], Time.fixedDeltaTime * sendUpdates, data.maxSpeedNormal); } groundPointTime = tempResults.groundPointTime; posEnd = tempResults.position; rotEnd = tempResults.rotation; posEndG = tempResults.groundPoint; } //Input gathering void Update () { if (isLocalPlayer) { if (inputsInterface == null) throw (new UnityException ("inputsInterface is not set!")); curInput.inputs.x = inputsInterface.GetMoveX (); curInput.inputs.y = inputsInterface.GetMoveY (); curInput.x = inputsInterface.GetMouseX (); curInput.y = inputsInterface.GetMouseY (); curInput.jump = inputsInterface.GetJump (); curInput.sprint = inputsInterface.GetSprint (); curInput.crouch = inputsInterface.GetCrouch (); } } //This is where the ticks happen void FixedUpdate () { if (data.strafeToSpeedCurveScale != _strafeToSpeedCurveScale) { _strafeToSpeedCurveScale = data.strafeToSpeedCurveScale; strafeToSpeedCurveScaleMul = 1f / data.strafeToSpeedCurveScale; } if (isLocalPlayer || isServer) { currentFixedUpdates++; } if (isLocalPlayer && currentFixedUpdates >= sendUpdates) { currentTick++; if (!isServer) { onTickUpdate.Invoke (lastResults); clientResults.Add (lastResults); } if (clientInputs.Count >= data.inputsToStore) clientInputs.RemoveAt (0); clientInputs.Add (curInput); curInput.timestamp = currentTick; posStart = myTransform.position; rotStart = myTransform.rotation; startTime = Time.fixedTime; if (reconciliate) { Reconciliate (); lastResults = tempResults; reconciliate = false; } controller.enabled = true; lastResults = MoveCharacter (lastResults, clientInputs [clientInputs.Count - 1], Time.fixedDeltaTime * _sendUpdates, data.maxSpeedNormal); #if (CLIENT_TRUST) CmdSendInputs (clientInputs [clientInputs.Count - 1], lastResults); #else CmdSendInputs (clientInputs [clientInputs.Count - 1]); #endif if (data.debug) onTickUpdateDebug.Invoke(clientInputs [clientInputs.Count - 1], lastResults); controller.enabled = false; posEnd = lastResults.position; groundPointTime = lastResults.groundPointTime; posEndG = lastResults.groundPoint; rotEnd = lastResults.rotation; } if (isServer && currentFixedUpdates >= sendUpdates && (currentTFixedUpdates >= sendUpdates || isLocalPlayer)) { if (isLocalPlayer) { onTickUpdate.Invoke (lastResults); RpcSendResults (lastResults); } if (!isLocalPlayer && clientInputs.Count > 0) { currentFixedUpdates -= sendUpdates; currentTFixedUpdates -= sendUpdates; //if (clientInputs.Count == 0) // clientInputs.Add (curInputServer); //clientInputs[clientInputs.Count - 1] = curInputServer; curInput = SortClientInputsAndReturnFirst (); posStart = myTransform.position; rotStart = myTransform.rotation; startTime = Time.fixedTime; controller.enabled = true; serverResults = MoveCharacter (serverResults, curInput, Time.fixedDeltaTime * _sendUpdates, data.maxSpeedNormal); #if (CLIENT_TRUST) if (serverResults.timestamp == tempResults.timestamp && Vector3.SqrMagnitude(serverResults.position-tempResults.position) <= data.clientPositionToleration * data.clientPositionToleration && Vector3.SqrMagnitude(serverResults.speed-tempResults.speed) <= data.clientSpeedToleration * data.clientSpeedToleration && ((serverResults.isGrounded == tempResults.isGrounded) || !data.clientGroundedMatch) && ((serverResults.crouch == tempResults.crouch) || !data.clientCrouchMatch)) serverResults = tempResults; #endif groundPointTime = serverResults.groundPointTime; posEnd = serverResults.position; rotEnd = serverResults.rotation; posEndG = serverResults.groundPoint; controller.enabled = false; onTickUpdate.Invoke (serverResults); if (data.debug) onTickUpdateDebug.Invoke(curInput, serverResults); RpcSendResults (serverResults); } } if (isLocalPlayer && currentFixedUpdates >= sendUpdates) currentFixedUpdates = 0; } //This is where all the interpolation happens void LateUpdate () { if (data.movementType == MoveType.UpdateOnceAndLerp) { if (isLocalPlayer || isServer || (Time.time - startTime) / (Time.fixedDeltaTime * _sendUpdates) <= 1f) { interpPos = Vector3.Lerp (posStart, posEnd, (Time.time - startTime) / (Time.fixedDeltaTime * _sendUpdates)); //if ((Time.time - startTime) / (Time.fixedDeltaTime * _sendUpdates) <= groundPointTime) // interpPos.y = Mathf.Lerp (posStart.y, posEndG, (Time.time - startTime) / (Time.fixedDeltaTime * _sendUpdates * groundPointTime)); //else // interpPos.y = Mathf.Lerp (posStart.y, posEndG, (Time.time - startTime + (groundPointTime * Time.fixedDeltaTime * _sendUpdates)) / (Time.fixedDeltaTime * _sendUpdates * (1f - groundPointTime))); myTransform.rotation = Quaternion.Lerp (rotStart, rotEnd, (Time.time - startTime) / (Time.fixedDeltaTime * _sendUpdates)); if (isLocalPlayer) myTransform.rotation = Quaternion.Euler (myTransform.rotation.eulerAngles.x, curInput.x, myTransform.rotation.eulerAngles.z); myTransform.position = interpPos; } else { myTransform.position = Vector3.Lerp (posEnd, posEndO, (Time.time - startTime) / (Time.fixedDeltaTime * _sendUpdates) - 1f); myTransform.rotation = Quaternion.Lerp (rotEnd, rotEndO, (Time.time - startTime) / (Time.fixedDeltaTime * _sendUpdates) - 1f); } } else { myTransform.position = posEnd; myTransform.rotation = rotEnd; } } //Data not to be messed with. Needs to be outside the function due to OnControllerColliderHit Vector3 hitNormal; //Actual movement code. Mostly isolated, except transform Results MoveCharacter (Results inpRes, Inputs inp, float deltaMultiplier, Vector3 maxSpeed) { inp.y = Mathf.Clamp (curInput.y, dataInp.camMinY, dataInp.camMaxY); if (inp.x > 360f) inp.x -= 360f; else if (inp.x < 0f) inp.x += 360f; Vector3 pos = myTransform.position; Quaternion rot = myTransform.rotation; myTransform.position = inpRes.position; myTransform.rotation = inpRes.rotation; Vector3 tempSpeed = myTransform.InverseTransformDirection (inpRes.speed); myTransform.rotation = Quaternion.Euler (new Vector3 (0, inp.x, 0)); //Character sliding of surfaces if (!inpRes.isGrounded) { inpRes.speed.x += (1f - inpRes.groundNormal.y) * inpRes.groundNormal.x * (inpRes.speed.y > 0 ? 0 : -inpRes.speed.y) * (1f - data.slideFriction); inpRes.speed.x += (1f - inpRes.groundNormal.y) * inpRes.groundNormal.x * (inpRes.speed.y < 0 ? 0 : inpRes.speed.y) * (1f - data.slideFriction); inpRes.speed.z += (1f - inpRes.groundNormal.y) * inpRes.groundNormal.z * (inpRes.speed.y > 0 ? 0 : -inpRes.speed.y) * (1f - data.slideFriction); inpRes.speed.z += (1f - inpRes.groundNormal.y) * inpRes.groundNormal.z * (inpRes.speed.y < 0 ? 0 : -inpRes.speed.y) * (1f - data.slideFriction); } Vector3 localSpeed = myTransform.InverseTransformDirection (inpRes.speed); Vector3 localSpeed2 = Vector3.Lerp (myTransform.InverseTransformDirection (inpRes.speed), tempSpeed, data.velocityTransferCurve.Evaluate (Mathf.Abs (inpRes.rotation.eulerAngles.y - inp.x) / (deltaMultiplier * data.velocityTransferDivisor))); if (!inpRes.isGrounded && data.strafing) AirStrafe (ref inpRes, ref inp, ref deltaMultiplier, ref maxSpeed, ref localSpeed, ref localSpeed2); else localSpeed = localSpeed2; BaseMovement (ref inpRes, ref inp, ref deltaMultiplier, ref maxSpeed, ref localSpeed); float tY = myTransform.position.y; inpRes.speed = transform.TransformDirection (localSpeed); hitNormal = new Vector3 (0, 0, 0); inpRes.speed.x = data.finalSpeedCurve.Evaluate (inpRes.speed.x); inpRes.speed.y = data.finalSpeedCurve.Evaluate (inpRes.speed.y); inpRes.speed.z = data.finalSpeedCurve.Evaluate (inpRes.speed.z); controller.Move (inpRes.speed * deltaMultiplier); //This code continues after OnControllerColliderHit gets called (if it does) if (Vector3.Angle (Vector3.up, hitNormal) <= data.slopeLimit) inpRes.isGrounded = true; else inpRes.isGrounded = false; //float speed = inpRes.speed.y; inpRes.speed = (transform.position - inpRes.position) / deltaMultiplier; //inpRes.speed.y = speed; float gpt = 1f; float gp = myTransform.position.y; //WIP, broken, Handles hitting ground while spacebar is pressed. It determines how much time was left to move based on at which height the player hit the ground. Some math involved. if (data.handleMidTickJump && !inpRes.isGrounded && tY - gp >= 0 && inp.jump && (controller.isGrounded || Physics.Raycast (myTransform.position + controller.center, Vector3.down, (controller.height / 2) + (controller.skinWidth * 1.5f)))) { float oSpeed = inpRes.speed.y; gpt = (tY - gp) / (-oSpeed); inpRes.speed.y = data.speedJump + ((Physics.gravity.y / 2) * Mathf.Abs((1f - gpt) * deltaMultiplier)); Debug.Log (inpRes.speed.y + " " + gpt); controller.Move (myTransform.TransformDirection (0, inpRes.speed.y * deltaMultiplier, 0)); inpRes.isGrounded = true; Debug.DrawLine (new Vector3( myTransform.position.x, gp, myTransform.position.z), myTransform.position, Color.blue, deltaMultiplier); inpRes.jumped = true; } if (data.snapSize > 0f) myTransform.position = new Vector3 (Mathf.Round (myTransform.position.x * snapInvert) * data.snapSize, Mathf.Round (myTransform.position.y * snapInvert) * data.snapSize, Mathf.Round (myTransform.position.z * snapInvert) * data.snapSize); if (inpRes.isGrounded) localSpeed.y = Physics.gravity.y * Mathf.Clamp(deltaMultiplier, 1f, 1f); inpRes = new Results (myTransform.position, myTransform.rotation, hitNormal, inp.y, inpRes.speed, inpRes.isGrounded, inpRes.jumped, inpRes.crouch, gp, gpt, inp.timestamp); myTransform.position = pos; myTransform.rotation = rot; return inpRes; } //The part which determines if the controller was hit or not void OnControllerColliderHit (ControllerColliderHit hit) { hitNormal = hit.normal; }