예제 #1
0
    // interpolate or extrapolate to approximate the host position
    public void NetworkInterpolationAndExtrapolation(ref Vector3 setPosition, ref Quaternion setRotation)
    {
        if (0 == _networkTransforms.Count)
        {
            return;             // we have received no network updates, there isn't much we can do yet.....
        }

        float currentTime = CalculateLoopModeTime();         // returns current time if not in loop mode

        float velocityBlendInTimeSetting = 0f;
        float positionBlendInTimeSetting = 0f;
        float rotationBlendInTimeSetting = 0f;

        CalculateBlendTimes(ref velocityBlendInTimeSetting, ref positionBlendInTimeSetting, ref rotationBlendInTimeSetting);

        float displayTime = currentTime + networkDisplayTime;

        for (int update = 0; update < _networkTransforms.Count - 1; ++update)
        {
#if DEBUG
            if (loopMode && currentTime < _networkTransforms[update + 1].time)
            {
                break;                 // we're in loop mode and we haven't got to this update yet
            }
#endif
            if (_networkTransforms[update].havePredictionsBeenFullyCorrected)             // if we've already corrected the velocity, there's no more work to do
            {
                continue;
            }

            float distance = GameUtils.GetDistXZ(_networkTransforms[update].position, _networkTransforms[update + 1].position); // distance between this and the next update positions
            float timeDiff = _networkTransforms[update + 1].time - _networkTransforms[update].time;                             // time between the updates
            // calculate the velocity required to perfectly get between the two positions in the time between the transform updates
            Vector3 correctVelocity = (_networkTransforms[update + 1].position - _networkTransforms[update].position);
            if (timeDiff > 0f)
            {
                correctVelocity = correctVelocity.normalized * (distance / timeDiff);
            }

            if (displayTime <= _networkTransforms[update].time)             // if we haven't yet started to consider this update yet, we can clobber the velocity rather than change it over time
            {
                _networkTransforms[update].correctedRotation = _networkTransforms[update + 1].rotation;
                _networkTransforms[update].correctVelocity   = correctVelocity;
                _networkTransforms[update].rotationPeriod    = timeDiff;
                _networkTransforms[update].havePredictionsBeenFullyCorrected = true; // before this is set to true, correctVelocity is somewhere between predictedVelocity and correctVelocity
            }
            else                                                                     // change to the correct velocity over time to avoid any jumps in position
            {
                float lerpToCorrected = (currentTime - _networkTransforms[update + 1].time) / velocityBlendInTimeSetting;
                _networkTransforms[update].correctVelocity = Vector3.Lerp(_networkTransforms[update].predictedVelocity, correctVelocity, Mathf.Min(lerpToCorrected, 1f));
                if (lerpToCorrected >= 1f)
                {
                    _networkTransforms[update].havePredictionsBeenFullyCorrected = true;
                }
                _networkTransforms[update].correctedRotation = Quaternion.Slerp(_networkTransforms[update].predictedRotation, _networkTransforms[update + 1].rotation, Mathf.Min(lerpToCorrected, 1f));

                if (timeDiff < _networkTransforms[update].rotationPeriod)                 // only reduce the time to get us rotated quicker, extending the time could cause a backward rotation
                {
                    _networkTransforms[update].rotationPeriod = Mathf.Lerp(GetNetworkUpdateInterval(), timeDiff, Mathf.Min(lerpToCorrected, 1f));
                }
            }
        }

        if (displayTime > GetOldestUpdate().time)
        {
            Vector3    extrapolatedPosition = new Vector3();
            Quaternion extrapolatedRotation = new Quaternion();
            _networkTransforms[0].hasTransformBeenFullyBlendedIn = true;

            for (int update = 0; update < _networkTransforms.Count; ++update)
            {
                if (displayTime < _networkTransforms[update].time)
                {
                    break;
                }

                const float ArriveBefore = 0.85f;
                if (_networkTransforms[update].isFirstUsage)
                {
                    _networkTransforms[update].isFirstUsage = false;

                    if (_networkTransforms[update].havePredictionsBeenFullyCorrected) // this means we already have the update following this one
                    {                                                                 // make sure we're fully blended in before we get to the next update
                        _networkTransforms[update].positionBlendInTime = _networkTransforms[update].rotationBlendInTime = (_networkTransforms[update + 1].time - _networkTransforms[update].time) * ArriveBefore;
                    }
                    else
                    {
                        _networkTransforms[update].positionBlendInTime = positionBlendInTimeSetting;
                        _networkTransforms[update].rotationBlendInTime = rotationBlendInTimeSetting;
                    }
                }

                // rotation
                float      extrapolationTime           = displayTime - _networkTransforms[update].time;
                float      rotationExtrapolationPeriod = Mathf.Min(_networkTransforms[update].rotationPeriod * ArriveBefore, _networkTransforms[update].rotationBlendInTime);            // we should be rotated fully into projected rotation before we get the next network update
                Quaternion toRotationExtrapolated      = Quaternion.Slerp(_networkTransforms[update].rotation, _networkTransforms[update].correctedRotation, Mathf.Min(extrapolationTime / rotationExtrapolationPeriod, 1f));

                float rotationLerp = _networkTransforms[update].hasTransformBeenFullyBlendedIn ? 1f : extrapolationTime / _networkTransforms[update].rotationBlendInTime;
                extrapolatedRotation = Quaternion.Slerp(extrapolatedRotation, toRotationExtrapolated, Mathf.Min(rotationLerp, 1f));

                // position	extrapolation
                float positionLerp = _networkTransforms[update].hasTransformBeenFullyBlendedIn ? 1f : extrapolationTime / _networkTransforms[update].positionBlendInTime;
                if (positionLerp >= 1f)
                {
                    _networkTransforms[update].hasTransformBeenFullyBlendedIn = true;                     // rotation lerp time is less than position lerp time, so if position has been fully blended in, rotation must also have
                }

                // this checks whether there is an update after this one which is about to be used for the first time
                if (update + 1 < _networkTransforms.Count && _networkTransforms[update + 1].isFirstUsage && displayTime >= _networkTransforms[update + 1].time)
                {                 // check whether this udate has been fully blended in and corrected (and not had it's motion stopped by reaching destination), meaning it will meet up perfectly wth the next update
                    if (_networkTransforms[update].havePredictionsBeenFullyCorrected && _networkTransforms[update].hasTransformBeenFullyBlendedIn && !_networkTransforms[update].hasDestinationBeenReached)
                    {
                        _networkTransforms[update + 1].hasTransformBeenFullyBlendedIn = true;
                        _networkTransforms[update].isTransitionToNextReady            = true;                               // we will interpolate to next perfectly, so don't need to extrapolate past it
                        _networkTransforms[update].correctedDistToDestination         = NetworkTransform.DistanceUnlimited; // we know we will meet up with next perfectly, so no need to cap anything
                    }
                }

                // isTransitionToNextReady will only be true if we already have the next update (so update + 1 is safe)
                extrapolationTime = _networkTransforms[update].isTransitionToNextReady ? _networkTransforms[update + 1].time - _networkTransforms[update].time : extrapolationTime;
                Vector3 movement = (_networkTransforms[update].correctVelocity * extrapolationTime);

                if (!NetworkTransform.IsDistanceUnlimited(_networkTransforms[update].correctedDistToDestination) &&
                    GameUtils.Square(_networkTransforms[update].correctedDistToDestination) < movement.sqrMagnitude)
                {
                    movement = movement.normalized * _networkTransforms[update].correctedDistToDestination;
                    _networkTransforms[update].hasDestinationBeenReached = true;
                }
                if (!_networkTransforms[update].isTransitionToNextReady && IsStopAhead(update + 1, displayTime)) // we are not perfectly aligned to transition to the next update and we're stopping ahead
                {
                    _networkTransforms[update].hasDestinationBeenReached  = true;                                // let's stop moving seeings as there is a stop ahead (this makes sure we don not extrapolate past the stop)
                    _networkTransforms[update].correctedDistToDestination = movement.magnitude;
                }

                Vector3 toPositionExtrapolated = _networkTransforms[update].extrapolatedPosition = (_networkTransforms[update].position + movement);

                // lerp to our most recent extrapolated position
                extrapolatedPosition = Vector3.Lerp(extrapolatedPosition, toPositionExtrapolated, Mathf.Min(positionLerp, 1f));
            }
            setPosition = extrapolatedPosition;
            setRotation = extrapolatedRotation;
        }
        else
        {
            setPosition = GetOldestUpdate().position;
            setRotation = GetOldestUpdate().rotation;
        }
    }