Почему камера вообще не движется, если значение скорости вращения установлено, например, на 0,01? - PullRequest
0 голосов
/ 01 марта 2019

Если я установлю, например, скорость вращения 5, она будет вращаться лицом к следующей целевой путевой точке, а затем перейдет к ней.Но вращение камеры будет слишком быстрым.

Изменение скорости до 0,01 заставит ее вращаться с хорошей медленно плавной скоростью.Но затем при 0,01 камера поворачивается лицом к следующей путевой точке, но никогда не двигается к ней.Он остается на месте.

Это сценарий путевых точек:

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

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;

    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;
                    lookAtCam.setTime(0.0f);
                    cameraState = CameraState.Rotating;

                    break;
                }

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

                    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");
    }
}

И сценарий просмотра камеры:

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;

    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;
        }
    }

    public void setTime(float time)
    {
        timer = time;
    }
}

1 Ответ

0 голосов
/ 01 марта 2019

Задача

Ваш скрипт в основном работает!Проблема в

private void Update()
{
    if (target != null && timer < 0.99f)
    {
        transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation((target.position - transform.position).normalized), timer);

        timer += Time.deltaTime * RotationSpeed;
    }
}

. Есть две проблемы с этим:

  1. Вы добавляете Time.deltaTime * RotationSpeed, поэтому время, необходимое для достижения 1 иливаш случай 0.99 просто занимает 1/RotationSpeed = 100 раз дольше, чем обычно.Таким образом, ваша камера будет оставаться в состоянии Rotating около 100 секунд - после этого она будет двигаться очень хорошо!

  2. (Эта может быть преднамеренной, но см. Нижедля лучшего решения ) Quaternion.Slerp интерполирует между первым и вторым поворотом.Но вы всегда используете текущее вращение в качестве начальной точки, поэтому, поскольку timer никогда не достигает 1, вы получаете очень быстрое вращение в начале, но очень медленное (фактически никогда не заканчивающееся) вращение в конце, так как расстояние между текущим вращениеми целевое вращение со временем уменьшается.


Быстрые исправления

Эти исправления исправляют ваше текущее решение, но вы должны проверить раздел Лучшее решение ниже;)

  1. Обычно для сравнения обоих значений float лучше использовать Mathf.Approximately , а не использовать фактическое целевое значение 1.

    if (target != null && !Mathf.Approximately(timer, 1.0f))
    {
        //...
    
        timer += Time.deltaTime * RotationSpeed;
    
        // clamps the value between 0 and 1
        timer = Mathf.Clamp01(timer);
    }
    

    и

    public bool IsRotationFinished
    {
        get { return Mathf.Approximately(timer, 1.0f); }
    }  
    
  2. Вы должны использовать Quaternion.Slerp для сохранения исходного поворота и использовать его в качестве первого параметра (чем вы увидите, что вынужно больше RotationSpeed)

    private Quaternion lastRotation;
    
    private void Update()
    {
        if (target != null && !Mathf.Approximately(timer, 1.0f))
        {
            transform.rotation = Quaternion.Slerp(lastRotation, Quaternion.LookRotation((target.position - transform.position).normalized), timer);
    
            timer += Time.deltaTime * RotationSpeed;
        }
        else
        {
            lastRotation = transform.rotation;
        }
    }
    

    Или вместо Quaternion.Slerp используйте Quaternion.RotateTowards как

    transform.rotation = Quaternion.RotateTowards(transform.rotation, Quaternion.LookRotation((target.position - transform.position).normalized), RotationSpeed * Time.deltaTime);
    

Лучшее решение

Я настоятельно рекомендую использоватьe Сопрограммы для всего, вместо обработки такого рода вещей в Update.Они намного проще в управлении и делают ваш код очень чистым.

Посмотрите, как уменьшатся ваши сценарии, и вам больше не понадобятся все свойства, поля и сравнение floats.Вы можете сделать большинство вещей, которые вы получаете в настоящее время, и настроить ожидание того, что определенная вещь произойдет, всего за несколько строк.

В случае, если вы не знали: на самом деле вы можете просто yield return другой IEnumerator чтобы дождаться его окончания:

Маршрутные точки

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

    public GameObject player;
    public float speed = 5;
    public float WPradius = 1;
    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);

            //once waypoint reached wait 3 seconds than start over
            yield return new WaitForSeconds(3);
        }
    }

    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 = currentWaypoint.position;
    }

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

LookAtCamera

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;
    }
}

enter image description here

Примечание

Снова вместо Quaternion.Slerp и currentRotation вы также можете просто использовать Quaternion.RotateTowards как

transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRotation, RotationSpeed * Time.deltaTime);

И для движения вы также можете по-прежнемуиспользуйте Vector3.MoveTowards, если хотите

while (Vector3.Distance(currentWaypoint.position, transform.position) < WPradius)
{
    transform.position = Vector3.MoveTowards(transform.position, currentWaypoint.position, Time.deltaTime * speed);
    yield return null;
}

, но Я бы предпочел использовать решения Lerp.Почему я предпочитаю использовать Lerp?

  1. Теперь вы можете очень легко контролировать, хотите ли вы двигаться / вращаться с определенной скоростью или, вернее, установите фиксированное значение duration, при которомперемещение / вращение должны быть закончены независимо от того, насколько велика разница - или даже иметь некоторые дополнительные проверки, чтобы выбрать один из этих вариантов!

  2. Вы можете облегчить и отменитьдвижение / вращение!См. Ниже;)


Подсказка для облегчения движений Лерпа

Для того, чтобы все еще поддерживать ослабленное движение и / или ослабленное движение и вращение, я нашел этот блок Как Лерп как профессионал очень полезно!(адаптировано к моим примерам)

Например, мы могли бы "ослабить" с помощью sinerp:

var lerpFactor = Mathf.Sin(passedTime / duration * Mathf.PI * 0.5f);

Или мы могли бы "облегчить" с помощью coserp:

var lerpFactor = 1f - Mathf.Cos(passedTime / duration * Mathf.PI * 0.5f);

Мы могли бы даже создать экспоненциальное движение:

var lerpFactor = Mathf.Pow(passedTime / duration, 2);

Упомянутое выше свойство умножения является основной концепцией некоторых методов интерполяции, которые упрощаются и упрощаются, например, известной формулой «smoothstep»:

var lerpFactor = Mathf.Pow(passedTime / duration, 2) * (3f - 2f * passedTime / duration);

Или мой личный фаворит, "Smootherstep":

var lerpFactor = Mathf.Pow(passedTime / duration, 3) * (6f * (passedTime / duration) - 15f) + 10f);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...