Как сделать камеру ртс гладкой при масштабировании в Unity - PullRequest
3 голосов
/ 01 апреля 2019

прямо сейчас я пытаюсь сделать зум камеры RTS с панорамированием, когда близко к земле. Проблема, с которой я столкнулся сейчас, заключается в том, что я использую колесо прокрутки мыши для масштабирования, и это делает масштабирование таким, будто оно запаздывает. Похоже, что он скачет некоторое значение Y и телепортируется, а не плавно перемещается в нужную позицию. Кроме того, я хотел бы знать, как заставить камеру останавливаться на минимальном значении Y, потому что сейчас происходит то, что она останавливается на отметке 22, а не 20, что является моим минимальным значением Y для движения камеры.

Я попытался увеличить с помощью + и - на моей цифровой клавиатуре, и это сработало так, как я хотел (плавное увеличение и уменьшение без пропуска), но не у всех игроков есть цифровая клавиатура, и я чувствую, что это больше подходит для масштабирования с помощью колесика мыши.

{

private const int levelArea = 100;

private const int scrollArea = 25;
private const int scrollSpeed = 25;
private const int dragSpeed = 70;

private const int zoomSpeed = 50;

// Maximum/minimum zoom distance from the ground
public int zoomMin = 20;
public int zoomMax = 120;

private const int panSpeed = 40;

// Minimal/maximal angles for camera
private const int panAngleMin = 30;
private const int panAngleMax = 90;


void Update()
{
    // Init camera translation for this frame.
    var translation = Vector3.zero;

    // Zoom in or out
    var zoomDelta = Input.GetAxis("Mouse ScrollWheel") * zoomSpeed * Time.deltaTime;
    if (zoomDelta != 0)
    {
        translation -= Vector3.up * zoomSpeed * zoomDelta;
    }

    // Start panning camera if zooming in close to the ground or if just zooming out.
    var pan = transform.eulerAngles.x - zoomDelta * panSpeed;
    pan = Mathf.Clamp(pan, panAngleMin, panAngleMax);
    // When to start panning up the camera
    if (zoomDelta < 0 || transform.position.y < (zoomMax -20))
    {
        transform.eulerAngles = new Vector3(pan, 0, 0);
    }

    // Move camera with arrow keys
    translation += new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));

    // Move camera with mouse
    if (Input.GetMouseButton(2)) // MMB
    {
        // Hold button and drag camera around
        translation -= new Vector3(Input.GetAxis("Mouse X") * dragSpeed * Time.deltaTime, 0,
                           Input.GetAxis("Mouse Y") * dragSpeed * Time.deltaTime);
    }
    else
    {
        // Move camera if mouse pointer reaches screen borders
        if (Input.mousePosition.x < scrollArea)
        {
            translation += Vector3.right * -scrollSpeed * Time.deltaTime;
        }

        if (Input.mousePosition.x >= Screen.width - scrollArea)
        {
            translation += Vector3.right * scrollSpeed * Time.deltaTime;
        }

        if (Input.mousePosition.y < scrollArea)
        {
            translation += Vector3.forward * -scrollSpeed * Time.deltaTime;
        }

        if (Input.mousePosition.y > Screen.height - scrollArea)
        {
            translation += Vector3.forward * scrollSpeed * Time.deltaTime;
        }
    }

    // Keep camera within level and zoom area
    var desiredPosition = transform.position + translation;
    if (desiredPosition.x < -levelArea || levelArea < desiredPosition.x)
    {
        translation.x = 0;
    }
    if (desiredPosition.y < zoomMin || zoomMax < desiredPosition.y)
    {
        translation.y = 0;
    }
    if (desiredPosition.z < -levelArea || levelArea < desiredPosition.z)
    {
        translation.z = 0;
    }

    // Move camera parallel to world axis
    transform.position += translation;
}

}

Я бы хотел плавно перейти из положения, в котором сейчас находится камера, и в желаемое положение после прокрутки входа / выхода. А также мне хотелось бы знать, как заставить камеру останавливаться на минимальном / максимальном расстоянии увеличения, а не останавливаться рядом с ней. Спасибо за помощь. Видео как выглядит движение камеры: https://youtu.be/Lt3atJEaOjA

1 Ответ

1 голос
/ 02 апреля 2019

Хорошо, я собираюсь сделать три вещи здесь. Прежде всего, я порекомендую вам, если вы работаете с расширенным поведением камеры, вы, вероятно, захотите хотя бы рассмотреть возможность использования Cinemachine . Я бы сам провел вас через это, но, учитывая отсутствие у меня личного опыта, я, вероятно, даже оказал бы вам медвежью услугу. Есть много хороших уроков. Youtube и Google должны предоставить.

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

Итак, ключевой момент здесь заключается в том, что входное колесо прокрутки Unity является довольно двоичным. Когда вы проверяете ось колеса прокрутки, результат напрямую зависит от того, сколько «щелчков» прошло вашим колесом с момента последнего обновления кадра, но то, что вы действительно хотите, - это что-то с небольшим преимуществом. По умолчанию Unity может делать это с большинством входов оси: вы можете заметить, что если вы используете WASD в проекте Unity по умолчанию, это своего рода «отставание», когда вы убираете пальцы с клавиш, но вы все равно будете получать положительные значения от Input.GetAxis() в течение нескольких кадров. Это связано со значением Gravity в ваших настройках ввода, и Input.GetAxisRaw() фактически используется, чтобы полностью обойти это. По какой-то причине оси гравитации не зависят от силы тяжести оси, поэтому нам, по сути, приходится реализовывать нечто подобное самим.

// Add this property to your class definition (so it persists between updates):
private float wheelAxis = 0;

// Delete this line:
var zoomDelta = Input.GetAxis("Mouse ScrollWheel") * zoomSpeed * Time.deltaTime;

// And put these three new lines in its place:
wheelAxis += Input.GetAxis("Mouse ScrollWheel");
wheelAxis = Mathf.MoveTowards(wheelTotal, 0f, Time.deltaTime);
var zoomDelta = Mathf.Clamp(wheelAxis, -0.05f, 0.05f) * zoomSpeed * Time.deltaTime;

Правильно, поэтому мы делаем несколько вещей здесь. При каждом обновлении мы добавляем текущие значения колесика прокрутки к нашему wheelAxis. Затем мы применяем ток Time.deltatime как «гравитацию» через функцию Mathf.MoveTowards(). Наконец, мы называем то, что в основном является вашим старым zoomDelta кодом, с простой модификацией: мы ограничиваем wheelAxis с помощью Mathf.Clamp, чтобы попытаться отрегулировать, как быстро может происходить увеличение.

Вы можете изменить этот код несколькими способами. Если вы умножите параметр Time.deltaTime, вы можете указать, как долго ваш ввод будет «сохраняться». Если вы возитесь со значениями Mathf.Clamp(), вы будете увеличивать или уменьшать масштаб. В общем, если вам нужно плавное масштабирование с минимальными изменениями в коде, это, вероятно, ваш лучший выбор.

Так!

Теперь, когда мы это сделали, давайте поговорим о вашем коде и о том, как вы подходите к проблеме, и посмотрим, не найдем ли мы более чистого решения.

Получение хорошей работы камеры на удивление нетривиально. Ваш код выглядит как большой код, который, как я вижу, пытается решить сложную проблему: похоже, вы добавили какую-то функцию, а затем протестировали ее, нашли некоторые крайние случаи, когда она развалилась, а затем исправили эти случаи, а затем попытались реализовать новую функцию поверх старого кода, но она вроде как сломалась различными другими способами, и т. д., и т. д., и то, что мы получили в этот момент, немного грязно.

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

Итак, вот рабочий скрипт камеры, который вы можете подключить и просто запустить:

using UnityEngine;

public class RTSCamera : MonoBehaviour
{
    public float zoomSpeed = 100f;
    public float zoomTime = 0.1f;

    public float maxHeight = 100f;
    public float minHeight = 20f;

    public float focusHeight = 10f;
    public float focusDistance = 20f;

    public int panBorder = 25;
    public float dragPanSpeed = 25f;
    public float edgePanSpeed = 25f;
    public float keyPanSpeed = 25f;

    private float zoomVelocity = 0f;
    private float targetHeight;

    void Start()
    {
        // Start zoomed out
        targetHeight = maxHeight;
    }

    void Update()
    {
        var newPosition = transform.position;

        // First, calculate the height we want the camera to be at
        targetHeight += Input.GetAxis("Mouse ScrollWheel") * zoomSpeed * -1f;
        targetHeight = Mathf.Clamp(targetHeight, minHeight, maxHeight);

        // Then, interpolate smoothly towards that height
        newPosition.y = Mathf.SmoothDamp(transform.position.y, targetHeight, ref zoomVelocity, zoomTime);

        // Always pan the camera using the keys
        var pan = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical")) * keyPanSpeed * Time.deltaTime;

        // Optionally pan the camera by either dragging with middle mouse or when the cursor touches the screen border
        if (Input.GetMouseButton(2)) {
            pan -= new Vector2(Input.GetAxis("Mouse X"), Input.GetAxis("Mouse Y")) * dragPanSpeed * Time.deltaTime;
        } else {
            var border = Vector2.zero;
            if (Input.mousePosition.x < panBorder) border.x -= 1f;
            if (Input.mousePosition.x >= Screen.width - panBorder) border.x += 1f;
            if (Input.mousePosition.y < panBorder) border.y -= 1f;
            if (Input.mousePosition.y > Screen.height - panBorder) border.y += 1f;
            pan += border * edgePanSpeed * Time.deltaTime;
        }

        newPosition.x += pan.x;
        newPosition.z += pan.y;

        var focusPosition = new Vector3(newPosition.x, focusHeight, newPosition.z + focusDistance);

        transform.position = newPosition;
        transform.LookAt(focusPosition);
    }
}

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

Ключевая идея здесь заключается в том, что вместо непосредственного управления высотой и ориентацией камеры мы просто позволяем колесу прокрутки определять, где хочет высота камеры, а затем мы используем Mathf.SmoothDamp() для перемещения камеры. плавно в эту позицию в течение нескольких кадров. (У Unity есть много полезных функций, подобных этой. Рассмотрим Mathf.MoveTowards() для альтернативного метода интерполяции.) В самом конце, вместо того, чтобы пытаться напрямую поиграть со значениями вращения камеры, мы просто выбираем точку перед нами возле земли и направьте камеру прямо на это место.

Сохраняя положение камеры и ее ориентацию полностью независимыми друг от друга, а также выделяя «анимацию» высоты камеры, мы избегаем многих головных болей и устраняем большой потенциал для беспорядочных переплетенных ошибок.

Надеюсь, это поможет.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...