Daniel Lip
Daniel Lip

Reputation: 11341

Why is the camera not rotating to face the first waypoint?

When the game starts, a random waypoint is selected from an array. The camera should then rotate to face the selected random waypoint and start moving towards it.

Once the camera has reached the waypoint, it should wait 3 seconds before rotating to face and move towards the next random waypoint.

The problem I have is in Start(). The camera does not rotate to face the first waypoint before it starts moving towards it. Instead, it moves towards the first waypoint backwards. Then, when it reaches the waypoint, it waits 3 seconds to rotate and move towards the next waypoint.

It's working fine except that the camera does not rotate to face the first selected random waypoint. It's moving to it backward without first rotating to face it.

Here's my code:

The waypoints script

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

public class Waypoints : MonoBehaviour
{
    public GameObject[] waypoints;
    public GameObject player;
    public float speed = 5;
    public float WPradius = 1;
    public LookAtCamera lookAtCam;

    private int current = 0;
    private bool rot = false;

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

        if(waypoints.Length > 0)
        {
            StartCoroutine(RotateFacingTarget(waypoints[UnityEngine.Random.Range(0, waypoints.Length)].transform));
        }
    }

    void Update()
    {
        if (waypoints.Length > 0)
        {
            if (Vector3.Distance(waypoints[current].transform.position, transform.position) < WPradius)
            {
                current = UnityEngine.Random.Range(0, waypoints.Length);
                rot = false;
                StartCoroutine(RotateFacingTarget(waypoints[current].transform));

                if (current >= waypoints.Length)
                {
                    current = 0;
                }
            }

            if (rot)
                transform.position = Vector3.MoveTowards(transform.position, waypoints[current].transform.position, Time.deltaTime * speed);
        }
    }

    IEnumerator RotateFacingTarget(Transform target)
    {
        yield return new WaitForSeconds(3);

        lookAtCam.target = target;
        rot = true;
    }
}

The look at camera script

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

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

    //values for internal use
    private Quaternion _lookRotation;
    private Vector3 _direction;

    // Update is called once per frame
    void Update()
    {
        //find the vector pointing from our position to the target
        if (target)
        {
            _direction = (target.position - transform.position).normalized;

            //create the rotation we need to be in to look at the target
            _lookRotation = Quaternion.LookRotation(_direction);

            //rotate us over time according to speed until we are in the required rotation
            transform.rotation = Quaternion.Slerp(transform.rotation, _lookRotation, Time.deltaTime * RotationSpeed);
        }
    }
}

How can I fix this?

Upvotes: 2

Views: 205

Answers (1)

Eliasar
Eliasar

Reputation: 1074

Let's assume that Waypoints.Init() is being called and your waypoints variable has an array of 3.

  • Waypoints.Init() starts a coroutine
    • Your coroutine waits 3 seconds
    • After 3 seconds, you set your camera target which Slerps to face the position
  • Update on its first frame says waypoints.Length > 0 == true
    • It's not close to its target, and rot is false, so it does not move

Now, you're waiting 3 seconds, not rotating, and not moving.

  • Your coroutine's 3 second wait time is up and starts the rotation toward your target
  • rot is now true at the start of your rotation, so your Update method starts moving toward the target as well

It would seem that your logic is off in how the order of operations works. If it needs to act as you describe, I suggest that you operate on the target differently.

I've implemented the following using an enum:

Waypoints

public class Waypoints : MonoBehaviour
{
    private GameObject[] waypoints;
    private Transform currentWaypoint;

    private enum CameraState
    {
        StartRotating,
        Rotating,
        Moving,
        Waiting
    }

    private CameraState cameraState;

    public GameObject player;
    public float speed = 5;
    public float WPradius = 1;
    public LookAtCamera lookAtCam;

    private int current = 0;
    private bool isCameraRotating = false;

    void Start()
    {
        cameraState = CameraState.StartRotating;
    }

    void Update()
    {
        switch (cameraState)
        {
            // This state is used as a trigger to set the camera target and start rotation
            case CameraState.StartRotating:
            {
                // Sanity check in case the waypoint array was set to length == 0 between states
                if (waypoints.Length == 0)
                    break;

                // Tell the camera to start rotating
                currentWaypoint = waypoints[UnityEngine.Random.Range(0, waypoints.Length)].transform;
                lookAtCam.target = currentWaypoint;
                cameraState = CameraState.Rotating;

                break;
            }

            // This state only needs to detect when the camera has completed rotation to start movement
            case CameraState.Rotating:
            {
                if (lookAtCam.IsFinishedRotating)
                    cameraState = CameraState.StartMoving;

                break;
            }

            case CameraState.Moving:
            {
                // Move
                transform.position = Vector3.MoveTowards(transform.position, currentWaypoint.position, Time.deltaTime * speed);

                // Check for the Waiting state
                if (Vector3.Distance(currentWaypoint.position, transform.position) < WPradius)
                {
                    // Set to waiting state
                    cameraState = CameraState.Waiting;

                    // Call the coroutine to wait once and not in CameraState.Waiting
                    // Coroutine will set the next state
                    StartCoroutine(WaitForTimer(3));
                }

                break;
            }
            case CameraState.Waiting:
                // Do nothing. Timer has already started
                break;
        }
    }

    IEnumerator WaitForTimer(float timer)
    {
        yield return new WaitForSeconds(timer);
        cameraState = CameraState.StartRotating;
    }

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

LookAtCamera

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

    private float timer = 0.0f;
    public bool IsRotationFinished
    {
        get { return timer > 0.99f; }
    }

    // Update is called once per frame
    void Update()
    {
        if (target != null && timer < 0.99f)
        {
            // Rotate us over time according to speed until we are in the required rotation
            transform.rotation = Quaternion.Slerp(transform.rotation,
                Quaternion.LookRotation((target.position - transform.position).normalized),
                timer);

            timer += Time.deltaTime * RotationSpeed;
        }
    }
}

Upvotes: 2

Related Questions