0
\$\begingroup\$
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Waypoints : MonoBehaviour
{
    public GameObject[] waypoints;

    public GameObject player;
    public float speed;
    public float distanceFromPoint;
    public float waitTimeAtPoint;
    public LookAtCamera lookAtCam;

    private Transform currentWaypoint;

    private void Start()
    {
        // maybe refresh here?
        //RefreshWaypoints();
        StartCoroutine(RunWaypoints());
    }

    private IEnumerator RunWaypoints()
    {
        // Sanity check in case the waypoint array has length == 0
        if (waypoints.Length == 0)
        {
            Debug.Log("No Waypoints!", this);
            yield break;
        }

        // this looks dnagerous but as long as you yield somewhere it's fine ;)
        while (true)
        {
            // maybe refresh here?
            //RefreshWaypoints();

            // Sanity check in case the waypoint array was set to length == 0 between states
            if (waypoints.Length == 0)
            {
                Debug.Log("No Waypoints!", this);
                yield break;
            }

            // first select the next waypoint
            // Note that you might get the exact same waypoint again you currently had
            // this will throw two errors in Unity:
            // - Look rotation viewing vector is zero
            // - and transform.position assign attempt for 'Main Camera' is not valid. Input position is { NaN, NaN, NaN }.
            //
            // so to avoid that rather use this (not optimal) while loop
            // ofcourse while is never good but the odds that you will
            // always get the same value over a longer time are quite low
            //
            // in case of doubt you could still add a yield return null
            // than your camera just waits some frames longer until it gets a new waypoint
            Transform newWaypoint = waypoints[Random.Range(0, waypoints.Length)].transform;
            while (newWaypoint == currentWaypoint)
            {
                newWaypoint = waypoints[Random.Range(0, waypoints.Length)].transform;
            }
            currentWaypoint = newWaypoint;

            // tell camera to rotate and wait until it is finished in one line!
            yield return lookAtCam.RotateToTarget(currentWaypoint);

            // move and wait until in correct position in one line!
            yield return MoveToTarget(currentWaypoint);

            yield return new WaitForSeconds(waitTimeAtPoint);
        }
    }

    private IEnumerator MoveToTarget(Transform currentWaypoint)
    {
        var currentPosition = transform.position;
        var duration = Vector3.Distance(currentWaypoint.position, transform.position) / speed;
        var passedTime = 0.0f;

        do
        {
            // for easing see last section below
            var lerpFactor = passedTime / duration;

            transform.position = Vector3.Lerp(currentPosition, currentWaypoint.position, lerpFactor);

            passedTime += Time.deltaTime;
            yield return null;
        } while (passedTime <= duration);

        // to be sure to have the exact position in the end set it fixed
        transform.position = new Vector3(currentWaypoint.position.x - distanceFromPoint,
            currentWaypoint.position.y, currentWaypoint.position.z);
    }

    public void RefreshWaypoints()
    {
        waypoints = GameObject.FindGameObjectsWithTag("Target");
    }
}

I tried this :

transform.position = new Vector3(currentWaypoint.position.x - distanceFromPoint,
            currentWaypoint.position.y, currentWaypoint.position.z);

Before it was just :

transform.position = currentWaypoint.position;

but in both cases it's just moving the transform to the currentWaypoint position. it's good if I want it to be on the exact waypoint position.

but I want also to be able with a float variable to control the slow down speed before the waypoint/s. for example to set a distance like 10 and then slow down speed for example 3 so if the speed is 10 then start slowdown from distance 10 to speed 3 and then slowly smooth stop and wait at the waypoint.

but the idea is to add a slow down effect when getting closer to the next waypoint. it could be nice to add also some speed up effect from the current waypoint to the next waypoint. speed up and slow down.

This script is for the camera. Both scripts sits on the camera :

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LookAtCamera : MonoBehaviour
{
    // Values that will be set in the Inspector
    public float RotationSpeed;

    public IEnumerator RotateToTarget(Transform target)
    {
        var timePassed = 0f;

        var targetDirection = (target.position - transform.position).normalized;

        var targetRotation = Quaternion.LookRotation(targetDirection);
        var currentRotation = transform.rotation;

        var duration = Vector3.Angle(targetDirection, transform.forward) / RotationSpeed;

        do
        {
            // for easing see last section below
            var lerpFactor = timePassed / duration;

            transform.rotation = Quaternion.Slerp(currentRotation, targetRotation, lerpFactor);

            timePassed += Time.deltaTime;

            yield return null;
        } while (timePassed <= duration);

        // to be sure you have the corrcet rotation in the end set it fixed
        transform.rotation = targetRotation;
    }
}

The main goal is to make a fly camera around the scenes that will make some stuff at each waypoint. In some waypoint the camera will wait X seconds in another waypoint the camera will not wait at all and in another waypoint the camera will make a rotate around the waypoint object to show the whole waypoint and so on thing like that.

\$\endgroup\$

1 Answer 1

0
\$\begingroup\$

You're moving the object using Vector3.Lerp(). When you move with linear interpolation (lerp), the speed is constant. To add speed up at the beginning or slow down at the end, you'd need to use easing.

Ironically the source you obtained this code from seems to have left a comment referencing easing but then forgotten to actually include the easing section.

There are many ways to implement easing. One way is to use easing functions. You can search the web to find free libraries of easing functions for Unity. You'd generally use them something like this:

var t = passedTime / duration;
var lerpFactor = EasingLibrary.EaseInOutCubic(t);
transform.position = Vector3.Lerp(currentPosition, currentWaypoint.position, lerpFactor);

Another solution is to use an Animation Curve. With this approach, you'd declare a curve field at the top of your script:

[SerializeField] private AnimationCurve easingCurve;

This will show a curve field in the Inspector. We'll use this curve to define the interpolated motion of our GameObject. Start with something like this (note that the curve should start at (0,0) and end at (1,1):

Easing curve

Then we'll use the curve in our movement coroutine:

do {
    var t = passedTime / duration;
    var lerpFactor = easingCurve.Evaluate(t); //use the curve to apply easing
    transform.position = Vector3.Lerp(currentPosition, currentWaypoint.position, lerpFactor);
    passedTime += Time.deltaTime;
    yield return null;
} while (passedTime <= duration);

Our curve defines a function which takes an input and returns an output. The "time" (x-axis) of the curve represents the input (in our case, a time from 0 - 1) and the "value" (y-axis) represents the corresponding output (in our case, how far the GameObject has moved between the start and end positions).

If the curve was a perfectly straight line from (0,0) to (1,1) the output would be linear (in fact, it would be exactly the same value as the input). However, this s-shaped curve will ease the motion in at the beginning and ease it out at the end. The flatter the curve is in a particular area, the slower our motion will be at that time.

If you find this confusing, try messing around with the shape of the curve and study how this affects the animation.


Easing functions usually are easier to use. If there's a specific style of easing that you want, you would just call the appropriate function. Animation curves take a little more work to set up but give you more control over the exact motion.

For a project which makes heavy use of easing, I'll usually rely on an easing library and only use an animation curve if the easing functions don't give me enough fine control. If I only need to use easing in one or two places, I'll usually just use an animation curve rather than adding an easing library to the project.

\$\endgroup\$
4
  • \$\begingroup\$ For start I'm using the animation curve like in your solution. but the speed is speeding up when start and when moving to the next waypoint but it's not slowing down when getting close to the next waypoint. what should I do to make it slow down when close to the next waypoint ? \$\endgroup\$ Mar 4, 2021 at 22:12
  • \$\begingroup\$ My main goal is to move a camera around the scene in waypoints to make the camera like flying around when the game start. The camera should fly around between the waypoints and in each waypoint to do something. For example in one waypoint it should wait 3 second then continue to the next waypoint in another waypoint it should not wait at all in another one it will make a rotation around the waypoint to show the waypoint object from all angles. 360 degrees rotate around the waypoint. and so on the idea is to make some kind of a fly camera over the scene. \$\endgroup\$ Mar 4, 2021 at 22:18
  • \$\begingroup\$ I edited my question with the look at camera script and explain what is the main goal. forgot to add it before. \$\endgroup\$ Mar 4, 2021 at 22:21
  • \$\begingroup\$ If you used an s-curve similar to my example it should slow down at the end. Try playing around with the curve shape and see how that affects the motion. \$\endgroup\$
    – Kevin
    Mar 4, 2021 at 22:24

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .