// calculate what position and velocity we should send to peers who do not own the characters public void CalculatePositionAndVelocityToSend(ref Vector2 position, ref Vector2 velocity, ref float distToDestination) { // to save some bandwidth we're sending 2d vectors. y is calculated procedurally based on the ground height at the point you are standing on so isn't needed to be sent position.x = transform.position.x; position.y = transform.position.z; Vector3 velocity3d = new Vector3(_rvoController.newVelocity.x, 0f, _rvoController.newVelocity.z); Vector3 positionToDestinationXZ = GameUtils.SubXZ(_locomotion.Destination, transform.position); distToDestination = Mathf.Max(positionToDestinationXZ.magnitude - _locomotion.ArrivalThreshold, 0f); // check if our current rvo controller velocity is gonna make us overshoot the target if (velocity3d.magnitude * GetNetworkUpdateInterval() > distToDestination) { // set our speed so we do not overshoot velocity3d = velocity3d.normalized * (distToDestination / GetNetworkUpdateInterval()); } const float DirectionTolerance = 0.819f; // 0.819f == cos(35 degrees) const float StoppingTime = 4f; // seconds // if the character is not heading towards the destination, or the destination is far away then it's not gonna stop soon and the distance can be unlimited if (GameUtils.DotXZ(velocity3d.normalized, positionToDestinationXZ.normalized) < DirectionTolerance || // character is not traveling towards target (probably RVO related) distToDestination > velocity3d.magnitude * GetNetworkUpdateInterval() * StoppingTime) // the destination is far away, so no stop is imminent { distToDestination = NetworkTransform.DistanceUnlimited; } // this section makes sure our velocity would not take us off the nav mesh Vector3 from = transform.position; Vector3 targetPosition = AStarPathfindingUtils.CalculateExitPoint(ref from, from + velocity3d, _locomotion.Simulator, true); // see if the target poition would cause us to exit the nav mesh float distToOriginalTargetSqr = GameUtils.GetDistSqXZ(from, from + velocity3d); float distToNewTargetSqr = GameUtils.GetDistSqXZ(from, targetPosition); if (distToNewTargetSqr < distToOriginalTargetSqr) // if the returned length is nearer, it means we hit an edge { const float NudgeIntoNavMeshDist = 0.1f; if (distToNewTargetSqr > NudgeIntoNavMeshDist * NudgeIntoNavMeshDist) { float distToNewTarget = Mathf.Sqrt(distToNewTargetSqr); // here we're moving targetPosition slightly back onto the nav mesh away from the nav mesh perimeter, this is to stop the character being moved slightly off mesh targetPosition = from + (((targetPosition - from) / distToNewTarget) * (distToNewTarget - NudgeIntoNavMeshDist)); } else { targetPosition = from; // edge is very close, so just use the from position as the target position } velocity3d = targetPosition - transform.position; } velocity.x = velocity3d.x; velocity.y = velocity3d.z; if (!NetworkTransform.IsMovement(new Vector3(velocity3d.x, 0f, velocity3d.z))) // is no movement being sent { // if we're sending a velocity of zero, we can have distance unlimited because there will be no movement anyway, so having distance unlimited means it can start moving quickly when a movement begins distToDestination = NetworkTransform.DistanceUnlimited; } }
// looks to see if updates are coming to a hard stoppage ahead private bool IsStopAhead(int update, float displayTime) { for (; update < _networkTransforms.Count; ++update) { NetworkTransform netTrans = _networkTransforms[update]; if (displayTime < netTrans.time) { break; } // this update has reached its destination if (netTrans.hasDestinationBeenReached || !NetworkTransform.IsMovement(netTrans.correctVelocity)) { return(true); } } return(false); }
// add a new network transform public void SetTransformInterpolated(Vector2 position, Vector2 velocity, Quaternion rotation, float distToDestination, double networkTime) { if (!allowNetworkUpdates || loopMode) // for debugging { return; } if (_networkTransforms.Count > 0 && GetMostRecentUpdate().networkTime > networkTime) // this checks if we are receiving an update which is older than our most recent { return; // we're not interested in out of order network updates } NetworkTransform temp = null; if (_networkTransforms.Count >= _numNetworkTransformHistory) // we only keep a history of size _numNetworkTransformHistory { temp = _networkTransforms[0]; temp.isFirstUsage = true; temp.hasTransformBeenFullyBlendedIn = false; temp.havePredictionsBeenFullyCorrected = false; temp.isTransitionToNextReady = false; temp.hasDestinationBeenReached = false; // debugging temp.actualNextUpdateTime = -1f; temp.angleError = -999f; temp.positionalError = -1f; // debugging } else { temp = new NetworkTransform(); } temp.time = Time.time; temp.predictedDistToDestination = temp.correctedDistToDestination = distToDestination; // we are unpacking the 2d vectors into 3d vectors, the y elements are actually the z elements - see OnSerializeView for packing temp.position = new Vector3(position.x, 0f, position.y); temp.predictedVelocity = temp.correctVelocity = new Vector3(velocity.x, 0f, velocity.y); temp.rotation = rotation; if (!_locomotion.ShouldIgnoreRotation) { temp.correctedRotation = temp.predictedRotation = NetworkTransform.IsMovement(temp.predictedVelocity) ? Quaternion.LookRotation(temp.predictedVelocity.normalized) : rotation; } else { temp.correctedRotation = temp.predictedRotation = rotation; } temp.networkTime = networkTime; temp.rotationPeriod = GetNetworkUpdateInterval(); float notUsed = 0f; float positionBlendInTimeSetting = 0f; float rotationBlendInTimeSetting = 0f; CalculateBlendTimes(ref notUsed, ref positionBlendInTimeSetting, ref rotationBlendInTimeSetting); temp.positionBlendInTime = positionBlendInTimeSetting; temp.rotationBlendInTime = rotationBlendInTimeSetting; // debugging ---------------------------------------------------------------------------------------------------------------------- temp.extrapolatedPosition = temp.position; temp.expectedNextUpdateTime = temp.time + GetNetworkUpdateInterval(); if (_networkTransforms.Count > 0) { _networkTransforms[_networkTransforms.Count - 1].actualNextUpdateTime = Time.time; Vector3 predictedPosition = _networkTransforms[_networkTransforms.Count - 1].position + _networkTransforms[_networkTransforms.Count - 1].predictedVelocity; _networkTransforms[_networkTransforms.Count - 1].positionalError = GameUtils.GetDistXZ(predictedPosition, temp.position); float dot = GameUtils.DotXZ((temp.position - _networkTransforms[_networkTransforms.Count - 1].position).normalized, _networkTransforms[_networkTransforms.Count - 1].predictedVelocity.normalized); _networkTransforms[_networkTransforms.Count - 1].angleError = Mathf.Rad2Deg * Mathf.Acos(dot); } // ----------------------------------------------------------------------------------------------------------------------------------- int _networkTransformsCount = _networkTransforms.Count; if (_networkTransformsCount >= _numNetworkTransformHistory) // we only keep a history of size _numNetworkTransformHistory { for (int netTrans = 0; netTrans < _networkTransformsCount - 1; ++netTrans) { _networkTransforms[netTrans] = _networkTransforms[netTrans + 1]; } _networkTransforms[_networkTransformsCount - 1] = temp; } else { _networkTransforms.Add(temp); } }