public void Update()
        {
            if (Time.time >= nextRepath && canSearchAgain)
            {
                RecalculatePath();
            }

            Vector3 pos = transform.position;

            if (vectorPath != null && vectorPath.Count != 0)
            {
                while ((controller.To2D(pos - vectorPath[wp]).sqrMagnitude < moveNextDist * moveNextDist && wp != vectorPath.Count - 1) || wp == 0)
                {
                    wp++;
                }

                // Current path segment goes from vectorPath[wp-1] to vectorPath[wp]
                // We want to find the point on that segment that is 'moveNextDist' from our current position.
                // This can be visualized as finding the intersection of a circle with radius 'moveNextDist'
                // centered at our current position with that segment.
                var p1 = vectorPath[wp - 1];
                var p2 = vectorPath[wp];

                // Calculate the intersection with the circle. This involves some math.
                var t = VectorMath.LineCircleIntersectionFactor(controller.To2D(transform.position), controller.To2D(p1), controller.To2D(p2), moveNextDist);
                // Clamp to a point on the segment
                t = Mathf.Clamp01(t);
                Vector3 waypoint = Vector3.Lerp(p1, p2, t);

                // Calculate distance to the end of the path
                float remainingDistance = controller.To2D(waypoint - pos).magnitude + controller.To2D(waypoint - p2).magnitude;
                for (int i = wp; i < vectorPath.Count - 1; i++)
                {
                    remainingDistance += controller.To2D(vectorPath[i + 1] - vectorPath[i]).magnitude;
                }

                // Set the target to a point in the direction of the current waypoint at a distance
                // equal to the remaining distance along the path. Since the rvo agent assumes that
                // it should stop when it reaches the target point, this will produce good avoidance
                // behavior near the end of the path. When not close to the end point it will act just
                // as being commanded to move in a particular direction, not toward a particular point
                var rvoTarget = (waypoint - pos).normalized * remainingDistance + pos;
                // When within [slowdownDistance] units from the target, use a progressively lower speed
                var desiredSpeed = Mathf.Clamp01(remainingDistance / slowdownDistance) * maxSpeed;
                Debug.DrawLine(transform.position, waypoint, Color.red);
                controller.SetTarget(rvoTarget, desiredSpeed, maxSpeed);
            }
            else
            {
                // Stand still
                controller.SetTarget(pos, maxSpeed, maxSpeed);
            }

            // Get a processed movement delta from the rvo controller and move the character.
            // This is based on information from earlier frames.
            var movementDelta = controller.CalculateMovementDelta(Time.deltaTime);

            pos += movementDelta;

            // Rotate the character if the velocity is not extremely small
            if (Time.deltaTime > 0 && movementDelta.magnitude / Time.deltaTime > 0.01f)
            {
                var         rot           = transform.rotation;
                var         targetRot     = Quaternion.LookRotation(movementDelta, controller.To3D(Vector2.zero, 1));
                const float RotationSpeed = 5;
                if (controller.movementPlane == MovementPlane.XY)
                {
                    targetRot = targetRot * Quaternion.Euler(-90, 180, 0);
                }
                transform.rotation = Quaternion.Slerp(rot, targetRot, Time.deltaTime * RotationSpeed);
            }

            if (controller.movementPlane == MovementPlane.XZ)
            {
                RaycastHit hit;
                if (Physics.Raycast(pos + Vector3.up, Vector3.down, out hit, 2, groundMask))
                {
                    pos.y = hit.point.y;
                }
            }

            transform.position = pos;
        }
Example #2
0
        /// <summary>
        /// 僵尸变聪明
        /// </summary>
        public void BeAutoPath()
        {
            m_AutomaticPath = true;
            // 如果不笨则增加寻路算法
            if (m_AiPath == null)
            {
                // 获取碰撞体
                m_collider = GetComponent <CircleCollider2D>();

                // 增加RVO网格控制
                m_rvoController = gameObject.AddComponent <RVOController>();
                m_rvoController.agentTimeHorizon    = 0.4f; // 检测代理碰撞的时间间隔
                m_rvoController.obstacleTimeHorizon = 0.6f; // 检测其他障碍碰撞的时间间隔
                //m_rvoController.collidesWith = (RVOLayer)(-1);
                m_rvoController.collidesWith  = RVOLayer.DefaultAgent;
                m_rvoController.maxNeighbours = 2;
                m_rvoController.locked        = false;

                // 寻路插件
                m_AiPath                         = gameObject.AddComponent <AIPath>();
                m_AiPath.radius                  = GetComponent <CircleCollider2D>().radius;
                m_AiPath.height                  = 1;
                m_AiPath.slowdownDistance        = 0; // 无需减速
                m_AiPath.enableRotation          = true;
                m_AiPath.orientation             = OrientationMode.YAxisForward;
                m_AiPath.slowWhenNotFacingTarget = false;
                m_AiPath.whenCloseToDestination  = CloseToDestinationMode.ContinueToExactDestination;
                m_AiPath.rotationSpeed           = 240 * (m_BaseSpeed / 2.5f);
                m_AiPath.pickNextWaypointDist    = 1;



                // 增加平滑曲线
                m_simpleSmooth = gameObject.AddComponent <SimpleSmoothModifier>();
                m_simpleSmooth.maxSegmentLength = 1f;
                m_simpleSmooth.iterations       = 5;
                RaycastModifier m_raycast = gameObject.AddComponent <RaycastModifier>();
                m_raycast.quality = RaycastModifier.Quality.Low;
                //m_raycast.use2DPhysics = false;
                m_raycast.useRaycasting      = false;
                m_raycast.useGraphRaycasting = true;

                m_seeker               = GetComponent <Seeker>();
                m_seeker.drawGizmos    = false;
                m_seeker.pathCallback += pathCall; // 路径完成后的回调
                Transform targetTsf = GetTargetNode();
                //transform.localEulerAngles = new Vector3(0, 0, EZMath.SignedAngleBetween(m_Player.transform.position - transform.position, Vector3.up));
                m_seeker.StartPath(transform.position, targetTsf.position);

                // 设置初始速度
                m_rvoController.velocity = (targetTsf.position - transform.position).normalized * m_BaseSpeed * m_HitSpeedScale; // 直接设置初始速度
                m_rvoController.SetTarget(targetTsf.position, m_rvoController.velocity.magnitude, m_rvoController.velocity.magnitude);
                m_AiPath.velocity2D = m_rvoController.velocity;

                PathInit.curPathNum++;
            }
            else
            {
                EnablePath();
            }
        }
Example #3
0
        /** Update is called once per frame */
        protected virtual void Update()
        {
            deltaTime = Mathf.Min(Time.smoothDeltaTime * 2, Time.deltaTime);

            if (rp != null)
            {
                RichPathPart currentPart = rp.GetCurrentPart();
                var          fn          = currentPart as RichFunnel;
                if (fn != null)
                {
                    // Clamp the current position to the navmesh
                    // and update the list of upcoming corners in the path
                    // and store that in the 'nextCorners' variable
                    Vector3 position = UpdateTarget(fn);

                    // Only get walls every 5th frame to save on performance
                    if (Time.frameCount % 5 == 0 && wallForce > 0 && wallDist > 0)
                    {
                        wallBuffer.Clear();
                        fn.FindWalls(wallBuffer, wallDist);
                    }

                    // Target point
                    int     tgIndex     = 0;
                    Vector3 targetPoint = nextCorners[tgIndex];
                    Vector3 dir         = targetPoint - position;
                    dir.y = 0;

                    bool passedTarget = Vector3.Dot(dir, currentTargetDirection) < 0;
                    // Check if passed target in another way
                    if (passedTarget && nextCorners.Count - tgIndex > 1)
                    {
                        tgIndex++;
                        targetPoint = nextCorners[tgIndex];
                    }

                    // Check if the target point changed compared to last frame
                    if (targetPoint != lastTargetPoint)
                    {
                        currentTargetDirection   = targetPoint - position;
                        currentTargetDirection.y = 0;
                        currentTargetDirection.Normalize();
                        lastTargetPoint = targetPoint;
                    }

                    // Direction to target
                    dir   = targetPoint - position;
                    dir.y = 0;

                    // Normalized direction
                    Vector3 normdir = VectorMath.Normalize(dir, out distanceToWaypoint);

                    // Is the endpoint of the path (part) the current target point
                    bool targetIsEndPoint = lastCorner && nextCorners.Count - tgIndex == 1;

                    // When very close to the target point, move directly towards the target
                    // instead of using accelerations as they tend to be a bit jittery in this case
                    if (targetIsEndPoint && distanceToWaypoint < 0.01f * maxSpeed)
                    {
                        // Velocity will be at most 1 times max speed, it will be further clamped below
                        velocity = (targetPoint - position) * 100;
                    }
                    else
                    {
                        // Calculate force from walls
                        Vector3 wallForceVector = CalculateWallForce(position, normdir);
                        Vector2 accelerationVector;

                        if (targetIsEndPoint)
                        {
                            accelerationVector = CalculateAccelerationToReachPoint(To2D(targetPoint - position), Vector2.zero, To2D(velocity));
                            //accelerationVector = Vector3.ClampMagnitude(accelerationVector, acceleration);

                            // Reduce the wall avoidance force as we get closer to our target
                            wallForceVector *= System.Math.Min(distanceToWaypoint / 0.5f, 1);

                            if (distanceToWaypoint < endReachedDistance)
                            {
                                // END REACHED
                                NextPart();
                            }
                        }
                        else
                        {
                            var nextNextCorner = tgIndex < nextCorners.Count - 1 ? nextCorners[tgIndex + 1] : (targetPoint - position) * 2 + position;
                            var targetVelocity = (nextNextCorner - targetPoint).normalized * maxSpeed;

                            accelerationVector = CalculateAccelerationToReachPoint(To2D(targetPoint - position), To2D(targetVelocity), To2D(velocity));
                        }

                        // Update the velocity using the acceleration
                        velocity += (new Vector3(accelerationVector.x, 0, accelerationVector.y) + wallForceVector * wallForce) * deltaTime;
                    }

                    var currentNode = fn.CurrentNode;

                    Vector3 closestOnNode;
                    if (currentNode != null)
                    {
                        closestOnNode = currentNode.ClosestPointOnNode(position);
                    }
                    else
                    {
                        closestOnNode = position;
                    }

                    // Distance to the end of the path (as the crow flies)
                    var distToEndOfPath = (fn.exactEnd - closestOnNode).magnitude;

                    // Max speed to use for this frame
                    var currentMaxSpeed = maxSpeed;
                    currentMaxSpeed *= Mathf.Sqrt(Mathf.Min(1, distToEndOfPath / (maxSpeed * slowdownTime)));

                    // Check if the agent should slow down in case it is not facing the direction it wants to move in
                    if (slowWhenNotFacingTarget)
                    {
                        // 1 when normdir is in the same direction as tr.forward
                        // 0.2 when they point in the opposite directions
                        float directionSpeedFactor = Mathf.Max((Vector3.Dot(normdir, tr.forward) + 0.5f) / 1.5f, 0.2f);
                        currentMaxSpeed *= directionSpeedFactor;
                        float currentSpeed = VectorMath.MagnitudeXZ(velocity);
                        float prevy        = velocity.y;
                        velocity.y   = 0;
                        currentSpeed = Mathf.Min(currentSpeed, currentMaxSpeed);

                        // Make sure the agent always moves in the forward direction
                        // except when getting close to the end of the path in which case
                        // the velocity can be in any direction
                        velocity = Vector3.Lerp(velocity.normalized * currentSpeed, tr.forward * currentSpeed, Mathf.Clamp(targetIsEndPoint ? distanceToWaypoint * 2 : 1, 0.0f, 0.5f));

                        velocity.y = prevy;
                    }
                    else
                    {
                        velocity = VectorMath.ClampMagnitudeXZ(velocity, currentMaxSpeed);
                    }

                    // Apply gravity
                    velocity += deltaTime * gravity;

                    if (rvoController != null && rvoController.enabled)
                    {
                        // Send a message to the RVOController that we want to move
                        // with this velocity. In the next simulation step, this velocity
                        // will be processed and it will be fed back the rvo controller
                        // and finally it will be used by this script when calling the
                        // CalculateMovementDelta method below

                        // Make sure that we don't move further than to the end point of the path
                        // If the RVO simulation FPS is low and we did not do this, the agent
                        // might overshoot the target a lot.
                        var rvoTarget = position + VectorMath.ClampMagnitudeXZ(velocity, distToEndOfPath);
                        rvoController.SetTarget(rvoTarget, VectorMath.MagnitudeXZ(velocity), maxSpeed);
                    }

                    // Direction and distance to move during this frame
                    Vector3 deltaPosition;
                    if (rvoController != null && rvoController.enabled)
                    {
                        // Use RVOController to get a processed delta position
                        // such that collisions will be avoided if possible
                        deltaPosition = rvoController.CalculateMovementDelta(position, deltaTime);

                        // The RVOController does not know about gravity
                        // so we copy it from the normal velocity calculation
                        deltaPosition.y = velocity.y * deltaTime;
                    }
                    else
                    {
                        deltaPosition = velocity * deltaTime;
                    }

                    if (targetIsEndPoint)
                    {
                        // Rotate towards the direction that the agent was in
                        // when the target point was seen for the first time
                        // TODO: Some magic constants here, should probably compute them from other variables
                        // or expose them as separate variables
                        Vector3 trotdir = Vector3.Lerp(deltaPosition.normalized, currentTargetDirection, System.Math.Max(1 - distanceToWaypoint * 2, 0));
                        RotateTowards(trotdir);
                    }
                    else
                    {
                        // Rotate towards the direction we are moving in
                        RotateTowards(deltaPosition);
                    }

                    if (controller != null && controller.enabled)
                    {
                        // Use CharacterController
                        tr.position = position;
                        controller.Move(deltaPosition);
                        // Grab the position after the movement to be able to take physics into account
                        position = tr.position;
                    }
                    else
                    {
                        // Use Transform
                        float lastY = position.y;
                        position += deltaPosition;
                        // Position the character on the ground
                        position = RaycastPosition(position, lastY);
                    }

                    // Clamp the position to the navmesh after movement is done
                    var clampedPosition = fn.ClampToNavmesh(position);

                    if (position != clampedPosition)
                    {
                        // The agent was outside the navmesh. Remove that component of the velocity
                        // so that the velocity only goes along the direction of the wall, not into it
                        var difference = clampedPosition - position;
                        velocity -= difference * Vector3.Dot(difference, velocity) / difference.sqrMagnitude;

                        // Make sure the RVO system knows that there was a collision here
                        // Otherwise other agents may think this agent continued to move forwards
                        // and avoidance quality may suffer
                        if (rvoController != null && rvoController.enabled)
                        {
                            rvoController.SetCollisionNormal(difference);
                        }
                    }

                    tr.position = clampedPosition;
                }
                else
                {
                    if (rvoController != null && rvoController.enabled)
                    {
                        //Use RVOController
                        rvoController.Move(Vector3.zero);
                    }
                }
                if (currentPart is RichSpecial)
                {
                    // The current path part is a special part, for example a link
                    // Movement during this part of the path is handled by the TraverseSpecial coroutine
                    if (!traversingSpecialPath)
                    {
                        StartCoroutine(TraverseSpecial(currentPart as RichSpecial));
                    }
                }
            }
            else
            {
                if (rvoController != null && rvoController.enabled)
                {
                    // Use RVOController
                    rvoController.Move(Vector3.zero);
                }
                else
                if (controller != null && controller.enabled)
                {
                }
                else
                {
                    tr.position = RaycastPosition(tr.position, tr.position.y);
                }
            }
        }
Example #4
0
        public void Update()
        {
            if (Time.time >= nextRepath && canSearchAgain)
            {
                RecalculatePath();
            }

            Vector3 pos = transform.position;

            if (vectorPath != null && vectorPath.Count != 0)
            {
                //Good Game
                //while ((controller.To2D(pos - vectorPath[wp]).sqrMagnitude < moveNextDist*moveNextDist && wp != vectorPath.Count-1) || wp == 0)
                //while ((controller.To2D((VInt3)pos - vectorPath[wp]).sqrMagnitude < moveNextDist*moveNextDist && wp != vectorPath.Count-1) || wp == 0)
                //Debug.Log($"--waypoint--{gameObject.name}--{(controller.To2D((VInt3)pos - vectorPath[wp])).sqrMagnitude / 1000000f}--{moveNextDist * moveNextDist}");
                while ((((Vector2)controller.To2D((VInt3)pos - vectorPath[wp])).sqrMagnitude < moveNextDist * moveNextDist && wp != vectorPath.Count - 1) || wp == 0)
                {
                    wp++;
                    //Debug.Log($"--agent{transform.GetSiblingIndex()}--wp--{wp}");
                }

                // Current path segment goes from vectorPath[wp-1] to vectorPath[wp]
                // We want to find the point on that segment that is 'moveNextDist' from our current position.
                // This can be visualized as finding the intersection of a circle with radius 'moveNextDist'
                // centered at our current position with that segment.
                var p1 = vectorPath[wp - 1];
                var p2 = vectorPath[wp];

                // Calculate the intersection with the circle. This involves some math.
                //Good Game
                //var t = VectorMath.LineCircleIntersectionFactor(controller.To2D(transform.position), controller.To2D(p1), controller.To2D(p2), moveNextDist);
                var t = VectorMath.LineCircleIntersectionFactor((Vector2)controller.To2D((VInt3)transform.position), (Vector2)controller.To2D(p1), (Vector2)controller.To2D(p2), moveNextDist);
                // Clamp to a point on the segment
                t = Mathf.Clamp01(t);
                //Good Game
                //Vector3 waypoint = Vector3.Lerp(p1, p2, t);
                VInt3 waypoint = VInt3.Lerp(p1, p2, t);

                // Calculate distance to the end of the path
                //Good Game

                /*float remainingDistance = controller.To2D(waypoint - pos).magnitude + controller.To2D(waypoint - p2).magnitude;
                 *              for (int i = wp; i < vectorPath.Count - 1; i++)
                 *                  remainingDistance += controller.To2D(vectorPath[i+1] - vectorPath[i]).magnitude;*/
                float remainingDistance = controller.To2D(waypoint - (VInt3)pos).magnitude + controller.To2D(waypoint - p2).magnitude;
                for (int i = wp; i < vectorPath.Count - 1; i++)
                {
                    remainingDistance += controller.To2D(vectorPath[i + 1] - vectorPath[i]).magnitude;
                }

                // Set the target to a point in the direction of the current waypoint at a distance
                // equal to the remaining distance along the path. Since the rvo agent assumes that
                // it should stop when it reaches the target point, this will produce good avoidance
                // behavior near the end of the path. When not close to the end point it will act just
                // as being commanded to move in a particular direction, not toward a particular point
                //GG
                remainingDistance /= 1000000;
                //Debug.Log("--remian--" + remainingDistance.ToString("f2"));
                //var rvoTarget = (waypoint - pos).normalized * remainingDistance + pos;
                var rvoTarget = ((Vector3)waypoint - pos).normalized * remainingDistance + pos;
                // When within [slowdownDistance] units from the target, use a progressively lower speed
                var desiredSpeed = Mathf.Clamp01(remainingDistance / slowdownDistance) * maxSpeed;
                Debug.DrawLine(transform.position, waypoint, Color.red);
                //GG
                //controller.SetTarget(rvoTarget, desiredSpeed, maxSpeed);
                //Debug.Log($"--target--{rvoTarget}--de speed--{desiredSpeed}--maxSpeed--{maxSpeed}");
                controller.SetTarget((VInt3)rvoTarget, (int)(desiredSpeed * 1000), (int)(maxSpeed * 1000));
                //controller.SetTarget((VInt3)rvoTarget, 50, (int)(maxSpeed* 1000));
            }
            else
            {
                // Stand still
                //GG
                //controller.SetTarget(pos, maxSpeed, maxSpeed);
                controller.SetTarget((VInt3)pos, (int)(maxSpeed * 1000), (int)(maxSpeed * 1000));
            }

            // Get a processed movement delta from the rvo controller and move the character.
            // This is based on information from earlier frames.
            //GG
            //var movementDelta = controller.CalculateMovementDelta(Time.deltaTime);
            //var movementDelta = controller.CalculateMovementDelta((int)(Time.deltaTime * 1000));
            var movementDelta = controller.CalculateMovementDelta(50);

            //GG
            //pos += movementDelta;
            pos += (Vector3)movementDelta;
            //Debug.Log("--" + movementDelta.ToString());

            //Rotate the character if the velocity is not extremely small
            //GG
            //if (Time.deltaTime > 0 && movementDelta.magnitude / Time.deltaTime > 0.01f)
            if (Time.deltaTime > 0 && ((Vector3)movementDelta).magnitude / Time.deltaTime > 0.01f)
            {
                var rot = transform.rotation;
                //GG
                //var targetRot = Quaternion.LookRotation((Vector3)movementDelta, (Vector3)controller.To3D(Vector2.zero, 1));
                var         targetRot     = Quaternion.LookRotation((Vector3)movementDelta, (Vector3)controller.To3D(VInt2.zero, 1000));
                const float RotationSpeed = 5;
                if (controller.movementPlane == MovementPlane.XY)
                {
                    targetRot = targetRot * Quaternion.Euler(-90, 180, 0);
                }
                transform.rotation = Quaternion.Slerp(rot, targetRot, Time.deltaTime * RotationSpeed);
            }

            if (controller.movementPlane == MovementPlane.XZ)
            {
                RaycastHit hit;
                if (Physics.Raycast(pos + Vector3.up, Vector3.down, out hit, 2, groundMask))
                {
                    pos.y = hit.point.y;
                }
            }

            transform.position = pos;
            //Good Game

            /*if(pos.x > 200 || pos.y > 200 || pos.z > 200 || pos.x > -200 || pos.z < -200)
             *  Debug.LogError("--" + gameObject.name + "--" + pos);*/
        }
Example #5
0
        private void MovementUpdateInternal(float deltaTime)
        {
            // a = v/t, should probably expose as a variable
            var acceleration = MaxSpeed / 0.4f;

            // Get our current position. We read from transform.position as few times as possible as it is relatively slow
            // (at least compared to a local variable)
            var currentPosition = Tr.position;

            // Update which point we are moving towards
            _interpolator.MoveToCircleIntersection2D(currentPosition, _pickNextWaypointDist, _movementPlane);
            var dir = _movementPlane.ToPlane(SteeringTargetV3 - currentPosition);

            // Calculate the distance to the end of the path
            var distanceToEnd = dir.magnitude + MathEx.Max(0, _interpolator.remainingDistance);

            // Check if we have reached the target
            var prevTargetReached = TargetReached;

            TargetReached = distanceToEnd <= _endReachedDistance && _interpolator.valid;
            if (!prevTargetReached && TargetReached)
            {
                MovementCompleted();
            }
            // Check if we have a valid path to follow and some other script has not stopped the character
            float slowdown = 1;

            if (_interpolator.valid)
            {
                // How fast to move depending on the distance to the destination.
                // Move slower as the character gets closer to the destination.
                // This is always a value between 0 and 1.
                if (distanceToEnd < _slowdownDistance)
                {
                    slowdown          = Mathf.Sqrt(distanceToEnd / _slowdownDistance);
                    _slowLerp.Enabled = false;
                }
                else
                {
                    if (_interpolator.ShouldSlow())
                    {
                        if (!_slowLerp.Enabled)
                        {
                            _slowLerp.RestartLerp(1, 0, _slowdownTime);
                        }
                        slowdown = _slowLerp.GetLerpValue();
                    }
                    else
                    {
                        _slowLerp.Enabled = false;
                    }
                }
                if (TargetReached && _goToExactEndPoint)
                {
                    // Slow down as quickly as possible
                    _velocity2D -= Vector2.ClampMagnitude(_velocity2D, acceleration * deltaTime);
                }
                else
                {
                    // Normalized direction of where the agent is looking
                    var forwards = _movementPlane.ToPlane(Tr.rotation * Vector3.forward);
                    _velocity2D += MovementUtilities.CalculateAccelerationToReachPoint(dir, dir.normalized * MaxSpeed, _velocity2D, acceleration, _rotationSpeed, MaxSpeed, forwards) * deltaTime;
                }
            }
            else
            {
                slowdown = 1;
                // Slow down as quickly as possible
                _velocity2D -= Vector2.ClampMagnitude(_velocity2D, acceleration * deltaTime);
            }

            _velocity2D = MovementUtilities.ClampVelocity(
                _velocity2D, MaxSpeed, slowdown, _slowWhenNotFacingTarget,
                _movementPlane.ToPlane(Tr.forward));

            ApplyGravity(deltaTime);

            if (_rvoController != null && _rvoController.enabled)
            {
                // Send a message to the RVOController that we want to move
                // with this velocity. In the next simulation step, this
                // velocity will be processed and it will be fed back to the
                // rvo controller and finally it will be used by this script
                // when calling the CalculateMovementDelta method below

                // Make sure that we don't move further than to the end point
                // of the path. If the RVO simulation FPS is low and we did
                // not do this, the agent might overshoot the target a lot.
                var rvoTarget = currentPosition + _movementPlane.ToWorld(Vector2.ClampMagnitude(_velocity2D, distanceToEnd), 0f);
                _rvoController.SetTarget(rvoTarget, _velocity2D.magnitude, MaxSpeed);
            }

            Vector2 desiredRotationDirection;

            if (_rvoController != null && _rvoController.enabled)
            {
                // When using local avoidance, use the actual velocity we are moving with (delta2D/deltaTime) if that velocity
                // is high enough, otherwise fall back to the velocity that we want to move with (velocity2D).
                // The local avoidance velocity can be very jittery when the character is close to standing still
                // as it constantly makes small corrections. We do not want the rotation of the character to be jittery.
                //var actualVelocity = delta2D / deltaTime;
                var actualVelocity = _lastDeltaPosition / _lastDeltaTime;
                desiredRotationDirection = Vector2.Lerp(_velocity2D, actualVelocity, 4 * actualVelocity.magnitude / (MaxSpeed + 0.0001f));
            }
            else
            {
                desiredRotationDirection = _velocity2D;
            }
            var delta2D = _lastDeltaPosition = CalculateDeltaToMoveThisFrame(_movementPlane.ToPlane(currentPosition), distanceToEnd, deltaTime);

            // Rotate towards the direction we are moving in
            var currentRotationSpeed = _rotationSpeed * MathEx.Max(0, (slowdown - 0.3f) / 0.7f);

            RotateTowards(desiredRotationDirection, currentRotationSpeed * deltaTime);

            var deltaPosition = _movementPlane.ToWorld(delta2D, _verticalVelocity * deltaTime);

            Move(currentPosition, deltaPosition);
        }