コード例 #1
0
		/** Called when a requested path has been calculated.
		 * A path is first requested by #UpdatePath, it is then calculated, probably in the same or the next frame.
		 * Finally it is returned to the seeker which forwards it to this function.
		 */
		protected override void OnPathComplete (Path newPath) {
			ABPath p = newPath as ABPath;

			if (p == null) throw new System.Exception("This function only handles ABPaths, do not use special path types");

			waitingForPathCalculation = false;

			// Increase the reference count on the new path.
			// This is used for object pooling to reduce allocations.
			p.Claim(this);

			// Path couldn't be calculated of some reason.
			// More info in p.errorLog (debug string)
			if (p.error) {
				p.Release(this);
				return;
			}

			// Release the previous path.
			if (path != null) path.Release(this);

			// Replace the old path
			path = p;

			// Make sure the path contains at least 2 points
			if (path.vectorPath.Count == 1) path.vectorPath.Add(path.vectorPath[0]);
			interpolator.SetPath(path.vectorPath);

			var graph = AstarData.GetGraph(path.path[0]) as ITransformedGraph;
			movementPlane = graph != null ? graph.transform : (rotationIn2D ? new GraphTransform(Matrix4x4.TRS(Vector3.zero, Quaternion.Euler(-90, 270, 90), Vector3.one)) : GraphTransform.identityTransform);

			// Reset some variables
			reachedEndOfPath = false;

			// Simulate movement from the point where the path was requested
			// to where we are right now. This reduces the risk that the agent
			// gets confused because the first point in the path is far away
			// from the current position (possibly behind it which could cause
			// the agent to turn around, and that looks pretty bad).
			interpolator.MoveToLocallyClosestPoint((GetFeetPosition() + p.originalStartPoint) * 0.5f);
			interpolator.MoveToLocallyClosestPoint(GetFeetPosition());

			// Update which point we are moving towards.
			// Note that we need to do this here because otherwise the remainingDistance field might be incorrect for 1 frame.
			// (due to interpolator.remainingDistance being incorrect).
			interpolator.MoveToCircleIntersection2D(position, pickNextWaypointDist, movementPlane);

			var distanceToEnd = remainingDistance;
			if (distanceToEnd <= endReachedDistance) {
				reachedEndOfPath = true;
				OnTargetReached();
			}
		}
コード例 #2
0
    /** Called during either Update or FixedUpdate depending on if rigidbodies are used for movement or not */
    protected override void MovementUpdate(float deltaTime)
    {
        if (!canMove)
        {
            return;
        }

        if (!interpolator.valid)
        {
            velocity2D = Vector3.zero;
        }
        else
        {
            var currentPosition = tr.position;

            interpolator.MoveToLocallyClosestPoint(currentPosition, true, false);
            interpolator.MoveToCircleIntersection2D(currentPosition, pickNextWaypointDist, movementPlane);
            targetPoint = interpolator.position;
            var dir = movementPlane.ToPlane(targetPoint - currentPosition);

            var distanceToEnd = dir.magnitude + interpolator.remainingDistance;
            // How fast to move depending on the distance to the target.
            // Move slower as the character gets closer to the target.
            float slowdown = slowdownDistance > 0 ? distanceToEnd / slowdownDistance : 1;

            // a = v/t, should probably expose as a variable
            float acceleration = speed / 0.4f;
            velocity2D += MovementUtilities.CalculateAccelerationToReachPoint(dir, dir.normalized * speed, velocity2D, acceleration, speed) * deltaTime;
            velocity2D  = MovementUtilities.ClampVelocity(velocity2D, speed, slowdown, true, movementPlane.ToPlane(rotationIn2D ? tr.up : tr.forward));

            ApplyGravity(deltaTime);

            if (distanceToEnd <= endReachedDistance && !TargetReached)
            {
                TargetReached = true;
                OnTargetReached();
            }

            // Rotate towards the direction we are moving in
            var currentRotationSpeed = rotationSpeed * Mathf.Clamp01((Mathf.Sqrt(slowdown) - 0.3f) / 0.7f);
            RotateTowards(velocity2D, currentRotationSpeed * deltaTime);

            var delta2D = CalculateDeltaToMoveThisFrame(movementPlane.ToPlane(currentPosition), distanceToEnd, deltaTime);
            Move(currentPosition, movementPlane.ToWorld(delta2D, verticalVelocity * deltaTime));

            velocity = movementPlane.ToWorld(velocity2D, verticalVelocity);
        }

        if (GameObject.FindGameObjectWithTag("Goal") != null)
        {
            target = GameObject.FindGameObjectWithTag("Goal").transform;
        }
    }
コード例 #3
0
    public bool Move()
    {
        var position = transform.position;

        _interpolator.MoveToLocallyClosestPoint(position, true, false);
        _interpolator.MoveToCircleIntersection2D(position, 0.5f, _movementPlane);
        var     target = _interpolator.position;
        Vector2 dir    = target - position;

        var distance = dir.magnitude + _interpolator.remainingDistance;

        if (distance <= 1.0f)
        {
            return(false);
        }

        _enemy.AddSpringForce(dir, distance);
        return(true);
    }
コード例 #4
0
ファイル: AIPath.cs プロジェクト: Wookyun/unity-sample
        /** Called during either Update or FixedUpdate depending on if rigidbodies are used for movement or not */
        protected override void MovementUpdateInternal(float deltaTime, out Vector3 nextPosition, out Quaternion nextRotation)
        {
            // a = v/t, should probably expose as a variable
            float acceleration = maxSpeed / 0.4f;

            if (updatePosition)
            {
                // 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)
                simulatedPosition = tr.position;
            }
            if (updateRotation)
            {
                simulatedRotation = tr.rotation;
            }

            var currentPosition = simulatedPosition;

            // Update which point we are moving towards
            interpolator.MoveToCircleIntersection2D(currentPosition, pickNextWaypointDist, movementPlane);
            var dir = movementPlane.ToPlane(steeringTarget - currentPosition);

            // Calculate the distance to the end of the path
            float distanceToEnd = dir.magnitude + Mathf.Max(0, interpolator.remainingDistance);

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

            reachedEndOfPath = distanceToEnd <= endReachedDistance && interpolator.valid;
            if (!prevTargetReached && reachedEndOfPath)
            {
                OnTargetReached();
            }
            float slowdown;

            // Check if we have a valid path to follow and some other script has not stopped the character
            if (interpolator.valid && !isStopped)
            {
                // 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.
                slowdown = distanceToEnd < slowdownDistance?Mathf.Sqrt(distanceToEnd / slowdownDistance) : 1;

                if (reachedEndOfPath && whenCloseToDestination == CloseToDestinationMode.Stop)
                {
                    // Slow down as quickly as possible
                    velocity2D -= Vector2.ClampMagnitude(velocity2D, acceleration * deltaTime);
                }
                else
                {
                    velocity2D += MovementUtilities.CalculateAccelerationToReachPoint(dir, dir.normalized * maxSpeed, velocity2D, acceleration, maxSpeed) * 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(rotationIn2D ? tr.up : tr.forward));

            ApplyGravity(deltaTime);


            // Set how much the agent wants to move during this frame
            var delta2D = lastDeltaPosition = CalculateDeltaToMoveThisFrame(movementPlane.ToPlane(currentPosition), distanceToEnd, deltaTime);

            nextPosition = currentPosition + movementPlane.ToWorld(delta2D, verticalVelocity * lastDeltaTime);
            CalculateNextRotation(slowdown, out nextRotation);
        }
コード例 #5
0
ファイル: AIPath.cs プロジェクト: yellowshq/myGameFramework
    /** Called during either Update or FixedUpdate depending on if rigidbodies are used for movement or not */
    protected override void MovementUpdate(float deltaTime)
    {
        if (!canMove)
        {
            return;
        }

        if (!interpolator.valid)
        {
            velocity2D = Vector3.zero;
        }
        else
        {
            var currentPosition = tr.position;

            interpolator.MoveToLocallyClosestPoint(currentPosition, true, false);
            interpolator.MoveToCircleIntersection2D(currentPosition, pickNextWaypointDist, movementPlane);
            targetPoint = interpolator.position;
            var dir = movementPlane.ToPlane(targetPoint - currentPosition);

            var distanceToEnd = dir.magnitude + interpolator.remainingDistance;
            // How fast to move depending on the distance to the target.
            // Move slower as the character gets closer to the target.
            float slowdown = slowdownDistance > 0 ? distanceToEnd / slowdownDistance : 1;

            // a = v/t, should probably expose as a variable
            float acceleration = speed / 0.4f;
            velocity2D += MovementUtilities.CalculateAccelerationToReachPoint(dir, dir.normalized * speed, velocity2D, acceleration, speed) * deltaTime;
            velocity2D  = MovementUtilities.ClampVelocity(velocity2D, speed, slowdown, true, movementPlane.ToPlane(rotationIn2D ? tr.up : tr.forward));

            ApplyGravity(deltaTime);

            if (distanceToEnd <= endReachedDistance && !TargetReached)
            {
                TargetReached = true;
                OnTargetReached();
            }

            // Rotate towards the direction we are moving in
            var currentRotationSpeed = rotationSpeed * Mathf.Clamp01((Mathf.Sqrt(slowdown) - 0.3f) / 0.7f);
            RotateTowards(velocity2D, currentRotationSpeed * 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, speed);
            }
            var delta2D = CalculateDeltaToMoveThisFrame(movementPlane.ToPlane(currentPosition), distanceToEnd, deltaTime);
            Move(currentPosition, movementPlane.ToWorld(delta2D, verticalVelocity * deltaTime));

            velocity = movementPlane.ToWorld(velocity2D, verticalVelocity);
        }
    }
コード例 #6
0
    protected new void Update()
    {
        base.Update();

        if (Role == CharacterRole.Drone && !_isSearchingPath && Input.GetButtonDown("Fire2"))
        {
            var     ray   = Camera.main.ScreenPointToRay(Input.mousePosition);
            Vector3 point = ray.IntersectXY();
            Destination = point;
            _seeker.StartPath(transform.position, Destination);
            _isSearchingPath = true;
        }

        {
            Vector2 direction;

            if (Role == CharacterRole.Main)
            {
                direction.x = Input.GetAxisRaw(_hAxis);
                direction.y = Input.GetAxisRaw(_vAxis);

                if (Mathf.Abs(direction.x) < DeadZone)
                {
                    direction.x = 0.0f;
                }
                else
                {
                    direction.x = Mathf.Sign(direction.x);
                }

                if (Mathf.Abs(direction.y) < DeadZone)
                {
                    direction.y = 0.0f;
                }
                else
                {
                    direction.y = Mathf.Sign(direction.y);
                }
                Velocity += direction * Acceleration * Time.deltaTime;
            }
            else
            {
                if (!_interpolator.valid || _isAtDestination)
                {
                    Velocity = Vector3.zero;
                }
                else
                {
                    var position = transform.position;

                    _interpolator.MoveToLocallyClosestPoint(position, true, false);
                    _interpolator.MoveToCircleIntersection2D(position, 0.5f, _movementPlane);
                    var     target = _interpolator.position;
                    Vector2 dir    = target - position;

                    var distance = dir.magnitude + _interpolator.remainingDistance;

                    if (distance <= StopThreshold)
                    {
                        _isAtDestination = true;
                    }
                    else
                    {
                        AddSpringForce(dir, distance);
                    }
                }
            }
        }

        if (Input.GetButton("Fire1") && _game.Energy >= _game.EnergyNeedForFire)
        {
            var     ray   = Camera.main.ScreenPointToRay(Input.mousePosition);
            Vector3 point = ray.IntersectXY();

            var position = transform.position;

            var direction = (point - position).normalized;
            position += direction * 0.32f;

            var points = _laserLines[0].points3;
            points.Clear();
            points.Add(position);

            for (var i = 0; i < 2; i++)
            {
                var hit = Physics2D.Raycast(position, direction, 10.0f, _laserLayerMask);
                if (hit.collider == null)
                {
                    points.Add(position + direction * 10.0f);
                    break;
                }

                points.Add(hit.point);

                var enemy = hit.collider.GetComponent <Enemy>();
                if (enemy != null)
                {
                    enemy.DoLaserHit(_game.LaserDamage * Time.deltaTime);
                    float ed = _game.EnergyDrain * Time.deltaTime;
                    if (_game.Energy >= ed && ed >= 0)
                    {
                        _game.Energy      -= ed;
                        _game.EnergySpent += ed;
                    }
                    else
                    {
                        _game.Energy = 0;
                    }
                    break;
                }

                direction = Vector2.Reflect(direction, hit.normal);
                position  = (Vector3)hit.point + direction * 0.05f;
            }

            //var laserPosition = position;
            //laserPosition.z -= 5.0f;
            //_laserParticles.transform.position = laserPosition;
            //var rotDir = new Vector3(-direction.y, direction.x);
            //_laserParticles.transform.rotation =
            //    Quaternion.Euler(0.0f, 0.0f, Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg);
            //var scale = Vector3.one * 0.08f;
            //scale.x = Vector3.Distance(point, position) * scale.x;
            //_laserParticles.transform.localScale = scale;

            //_laserParticles.SetActive(true);

            _laserLines[0].active = true;
            _laserLines[0].Draw();

            if (_as.Length > 0 && _as[0].clip != GameManager.Instance.LaserStartClip)
            {
                _as[0].clip = GameManager.Instance.LaserStartClip;
                _as[0].Play();

                _as[1].clip = GameManager.Instance.LaserShotClip;
                _as[1].Play();
            }
        }
        else
        {
            if (_as.Length > 0 && _as[0].clip != GameManager.Instance.LaserEndClip)
            {
                _as[0].clip = GameManager.Instance.LaserEndClip;
                _as[0].Play();

                _as[1].Stop();
            }

            _laserLines[0].active = false;
            //_laserParticles.SetActive(false);
        }
    }
コード例 #7
0
    /** Called during either Update or FixedUpdate depending on if rigidbodies are used for movement or not */
    protected override void MovementUpdate(float deltaTime)
    {
        if (Zone.changeRange)
        {
            playerRange      = beginPlayerRange;
            playerRange      = playerRange * TutorialStates.rangeMultiplier;
            Zone.changeRange = false;
        }
        if (playerRange <= 0)
        {
            playerRange = beginPlayerRange;
        }
        // draw gizmos bij de civilian
        SurvivorMovement.ranget = targetRange;
        SurvivorMovement.rangeP = playerRange;
        collide = false;
        // target zetten als er geen target is
        if (target == null)
        {
            ChooseTarget();
            previousWaypoint = target;
        }
        //target = GetClosestWaypoint(TutorialWaypoints.pathfindingWaypoints);
        dToPlayer = Vector3.Distance(player.transform.position, transform.position);
        dToTarget = Vector3.Distance(target.transform.position, transform.position);
        // verandert de target naar player als de waypoint in de cirkel is
        waypointMax = TutorialStates.waypointMax;
        if (dToPlayer < playerRange)
        {
            if (targetRange > dToTarget)
            {
                target = previousWaypoint;
                time   = timeReset;
                if (j <= TutorialStates.waypointMax)
                {
                    if (j < TutorialWaypoints.waypoints)
                    {
                        target = TutorialWaypoints.pathfindingWaypoints[j];
                        // Debug.Log("waypoint = " + TutorialWaypoints.pathfindingWaypoints[j] + " J is " + j);
                    }
                    if (j == TutorialStates.waypointMax && TutorialStates.stateName == "Defend")
                    {
                        endPoint = true;
                    }
                    j++;
                    previousWaypoint = target;
                }
            }
        }
        else
        {
            target = gameObject.transform;
        }
        stateName = TutorialStates.stateName;
        /////////////////////////////////////////////////////////////////////////////////
        if (!canMove)
        {
            return;
        }

        if (!interpolator.valid)
        {
            velocity2D = Vector3.zero;
        }
        else
        {
            var currentPosition = tr.position;

            interpolator.MoveToLocallyClosestPoint(currentPosition, true, false);
            interpolator.MoveToCircleIntersection2D(currentPosition, pickNextWaypointDist, movementPlane);
            targetPoint = interpolator.position;
            var dir = movementPlane.ToPlane(targetPoint - currentPosition);

            var distanceToEnd = dir.magnitude + interpolator.remainingDistance;
            // How fast to move depending on the distance to the target.
            // Move slower as the character gets closer to the target.
            float slowdown = slowdownDistance > 0 ? distanceToEnd / slowdownDistance : 1;

            // a = v/t, should probably expose as a variable
            float acceleration = speed / 0.4f;
            velocity2D += MovementUtilities.CalculateAccelerationToReachPoint(dir, dir.normalized * speed, velocity2D, acceleration, speed) * deltaTime;
            velocity2D  = MovementUtilities.ClampVelocity(velocity2D, speed, slowdown, true, movementPlane.ToPlane(rotationIn2D ? tr.up : tr.forward));

            ApplyGravity(deltaTime);

            if (distanceToEnd <= endReachedDistance && !TargetReached)
            {
                TargetReached = true;
                OnTargetReached();
            }

            // Rotate towards the direction we are moving in
            var currentRotationSpeed = rotationSpeed * Mathf.Clamp01((Mathf.Sqrt(slowdown) - 0.3f) / 0.7f);
            RotateTowards(velocity2D, currentRotationSpeed * deltaTime);

            var delta2D = CalculateDeltaToMoveThisFrame(movementPlane.ToPlane(currentPosition), distanceToEnd, deltaTime);
            Move(currentPosition, movementPlane.ToWorld(delta2D, verticalVelocity * deltaTime));

            velocity = movementPlane.ToWorld(velocity2D, verticalVelocity);
        }
    }
コード例 #8
0
    /** Called during either Update or FixedUpdate depending on if rigidbodies are used for movement or not */
    protected override void MovementUpdate(float deltaTime)
    {
        // draw gizmos bij de civilian
        CivilianMovement.ranget = targetRange;
        collide = false;
        // target zetten als er geen target is
        if (target == null)
        {
            target = GetClosestWaypoint(PathfindingWaypoints.pathfindingWaypoints);
        }

        dToTarget = Vector3.Distance(target.transform.position, transform.position);
        if (targetRange > dToTarget && target != player.transform)
        {
            time   = timeReset;
            target = player.transform;
        }
        // verandert de target naar player als de waypoint in de cirkel is
        if (survivor != null)
        {
            if (targetRange > dToTarget && target != survivor.transform)
            {
                time = timeReset;

                tutorial = true;
                target   = survivor.transform;
            }
        }

        //Afstand tussen civilian en bus wordt berekend en als in range veranderd target
        safePlaceRange    = safePlace.transform.position - transform.position;
        distanceSafePlace = safePlaceRange.magnitude;
        if (distanceSafePlace < range)
        {
            target = safePlace.transform;
        }
        /////////////////////////////////////////////////////////////////////////////////
        if (!canMove)
        {
            return;
        }

        if (!interpolator.valid)
        {
            velocity2D = Vector3.zero;
        }
        else
        {
            var currentPosition = tr.position;

            interpolator.MoveToLocallyClosestPoint(currentPosition, true, false);
            interpolator.MoveToCircleIntersection2D(currentPosition, pickNextWaypointDist, movementPlane);
            targetPoint = interpolator.position;
            var dir = movementPlane.ToPlane(targetPoint - currentPosition);

            var distanceToEnd = dir.magnitude + interpolator.remainingDistance;
            // How fast to move depending on the distance to the target.
            // Move slower as the character gets closer to the target.
            float slowdown = slowdownDistance > 0 ? distanceToEnd / slowdownDistance : 1;

            // a = v/t, should probably expose as a variable
            float acceleration = speed / 0.4f;
            velocity2D += MovementUtilities.CalculateAccelerationToReachPoint(dir, dir.normalized * speed, velocity2D, acceleration, speed) * deltaTime;
            velocity2D  = MovementUtilities.ClampVelocity(velocity2D, speed, slowdown, true, movementPlane.ToPlane(rotationIn2D ? tr.up : tr.forward));

            ApplyGravity(deltaTime);

            if (distanceToEnd <= endReachedDistance && !TargetReached)
            {
                TargetReached = true;
                OnTargetReached();
            }

            // Rotate towards the direction we are moving in
            var currentRotationSpeed = rotationSpeed * Mathf.Clamp01((Mathf.Sqrt(slowdown) - 0.3f) / 0.7f);
            RotateTowards(velocity2D, currentRotationSpeed * deltaTime);

            var delta2D = CalculateDeltaToMoveThisFrame(movementPlane.ToPlane(currentPosition), distanceToEnd, deltaTime);
            Move(currentPosition, movementPlane.ToWorld(delta2D, verticalVelocity * deltaTime));

            velocity = movementPlane.ToWorld(velocity2D, verticalVelocity);
        }
    }
コード例 #9
0
    /** Called during either Update or FixedUpdate depending on if rigidbodies are used for movement or not */
    protected override void MovementUpdate(float deltaTime)
    {
        EnemyBehaviour.ranget     = targetRange;
        EnemyBehaviour.chaseRange = chaseRange;
        // target zetten als er geen target is
        if (target == null)
        {
            if (!EnemiesKilled.KillMode)
            {
                //Kiest tussen de player, defendpoints en civilians
                target = GetClosestWaypoint(PathfindingWaypoints.pathfindingWaypoints);
            }
            else
            {
                // met kill count mission wordt het altijd de player
                target        = player.transform;
                playerInRange = true;
            }
        }
        dToTarget = Vector3.Distance(target.transform.position, transform.position);
        // verandert de target naar player als de waypoint in de cirkel is
        if (targetRange > dToTarget && target != player.transform)
        {
            time   = timeReset;
            target = GetClosestWaypoint(PathfindingWaypoints.pathfindingWaypoints);
        }
        //bepaalt of de defenndpoint, rescuepoint of player in range is
        if (dToTarget < chaseRange)
        {
            playerInRange = true;
        }
        else
        {
            playerInRange = false;
        }

        if (!playerInRange)
        {
            waypointTimer++;

            if (waypointTimer % (60 * 10) == 0 || !placeFound)
            {
                MoveToRandomPoint();
                waypointTimer = 0;
            }

            speed = idleSpeed;
        }
        else
        {
            speed = chaseSpeed;
        }

        ////////////////////////////////////////////////////////////////////////////
        if (!canMove)
        {
            return;
        }

        if (!interpolator.valid)
        {
            velocity2D = Vector3.zero;
        }
        else
        {
            var currentPosition = tr.position;

            interpolator.MoveToLocallyClosestPoint(currentPosition, true, false);
            interpolator.MoveToCircleIntersection2D(currentPosition, pickNextWaypointDist, movementPlane);
            targetPoint = interpolator.position;
            var dir = movementPlane.ToPlane(targetPoint - currentPosition);

            var distanceToEnd = dir.magnitude + interpolator.remainingDistance;
            // How fast to move depending on the distance to the target.
            // Move slower as the character gets closer to the target.
            float slowdown = slowdownDistance > 0 ? distanceToEnd / slowdownDistance : 1;

            // a = v/t, should probably expose as a variable
            float acceleration = speed / 0.4f;
            velocity2D += MovementUtilities.CalculateAccelerationToReachPoint(dir, dir.normalized * speed, velocity2D, acceleration, speed) * deltaTime;
            velocity2D  = MovementUtilities.ClampVelocity(velocity2D, speed, slowdown, true, movementPlane.ToPlane(rotationIn2D ? tr.up : tr.forward));

            ApplyGravity(deltaTime);

            if (distanceToEnd <= endReachedDistance && !TargetReached)
            {
                TargetReached = true;
                OnTargetReached();
            }

            // Rotate towards the direction we are moving in
            var currentRotationSpeed = rotationSpeed * Mathf.Clamp01((Mathf.Sqrt(slowdown) - 0.3f) / 0.7f);
            RotateTowards(velocity2D, currentRotationSpeed * deltaTime);

            var delta2D = CalculateDeltaToMoveThisFrame(movementPlane.ToPlane(currentPosition), distanceToEnd, deltaTime);
            Move(currentPosition, movementPlane.ToWorld(delta2D, verticalVelocity * deltaTime));

            velocity = movementPlane.ToWorld(velocity2D, verticalVelocity);
        }
    }