安卓触摸相机控制与惯性在Unity3d

本文关键字:Unity3d 控制 触摸 相机 | 更新日期: 2023-09-27 17:57:35

我正在Unity3d中为Android平板设备编写一个脚本,用户可以在其中拖动以移动相机。我希望在用户平移时,触摸位置的"地面"保持在用户手指下方。以下是我迄今为止简单的工作代码:

using UnityEngine;
public class CameraMovement : MonoBehaviour
{
    Plane plane = new Plane(Vector3.forward, Vector3.zero);
    Vector2 worldStartPoint;
    void Update()
    {
        if (Input.touchCount == 1)
        {
            Touch touch = Input.GetTouch(0);
            if (touch.phase == TouchPhase.Began)
            {
                this.worldStartPoint = GetWorldPoint(touch.position);
            }
            if (touch.phase == TouchPhase.Moved)
            {
                Vector2 worldDelta = GetWorldPoint(touch.position) - worldStartPoint;
                transform.Translate(-worldDelta.x, -worldDelta.y, 0);
            }
        }
    }
    Vector2 GetWorldPoint(Vector2 screenPoint)
    {
        Ray ray = Camera.main.ScreenPointToRay(screenPoint);
        float rayDistance;
        if (plane.Raycast(ray, out rayDistance))
            return ray.GetPoint(rayDistance);
        return Vector2.zero;
    }
}

现在有问题的部分是:一旦用户举起手指,我希望相机像物理物体一样移动。我试图在拖动时计算当前速度,然后在当前不拖动时将其作为阻尼/类似惯性的效果应用。理论上我会这么做:

Vector2 worldStartPoint;
Vector3 velocity;
void Update()
{
    if (Input.touchCount == 1)
    {
        Touch touch = Input.GetTouch(0);
        if (touch.phase == TouchPhase.Began)
        {
            this.worldStartPoint = GetWorldPoint(touch.position);
        }
        if (touch.phase == TouchPhase.Moved)
        {
            Vector2 worldDelta = GetWorldPoint(touch.position) - worldStartPoint;
            transform.Translate(-worldDelta.x, -worldDelta.y, 0);
            velocity = worldDelta / Time.deltaTime;
        }
    }
    else
    {
        transform.Translate(-velocity * Time.deltaTime);
        velocity = Vector3.MoveTowards(velocity, Vector3.zero, damping * Time.deltaTime);
    }
}

所以,我总是在移动时计算速度,一旦我停止输入,它应该保持在最后一个已知的速度,应用它,然后从那时起减小,直到停止。然而,通常最后的速度为零,因为在屏幕上拖动/滑动时,手指实际上会很快停止,并在触摸阶段结束前报告零速度。移动结束。

现在,我的解决方案/解决方法是保留最后几帧(可能是30帧)的速度数组,一旦手指抬起,我就会计算平均速度。

Vector3[] velocityBuffer = new Vector3[bufferSize];
int nextBufferIndex;
Vector3 GetAverage()
{
    Vector3 sum = Vector3.zero;
    for (int i = 0; i < bufferSize; i++)
        sum += velocityBuffer[i];
    return sum / bufferSize;
}

这效果要好一点,因为通常情况下,它至少会报告一些速度,但总的来说,它并没有好到哪里去,而且感觉很粗糙。根据触摸的速度,我最终可能会有20个零速度条目,使阻尼太强,有时速度会随机变大,以至于相机只轻弹几百个单位。

我的计算有什么问题吗?还是我在监督一些简单的事情来解决这个问题?有人有一个平滑的相机拖动的工作解决方案吗?我看了几款手机游戏,其中很多游戏实际上都感觉有点笨拙,比如在deltaMovement的几个像素后,将相机捕捉到手指位置,然后突然释放,没有任何阻尼。

安卓触摸相机控制与惯性在Unity3d

我不觉得自己找到了完美的解决方案,但至少到目前为止,我已经找到了效果更好、更"物理正确"的解决方案。首先,我现在将相机位置和deltaTime存储在缓冲区中,而不是速度。通过这种方式,我可以用正确的时间因子计算每10帧的滚动平均值。

/// <summary>
/// Stores Vector3 samples such as velocity or position and returns the average.
/// </summary>
[Serializable]
public class Vector3Buffer
{
    public readonly int size;
    Sample[] sampleData;
    int nextIndex;
    public Vector3Buffer(int size)
    {
        if (size < minSize)
        {
            size = minSize;
            Debug.LogWarning("Sample count must be at least one. Using default.");
        }
        this.size = size;
        sampleData = new Sample[size];
    }
    public void AddSample(Vector3 position, float deltaTime)
    {
        sampleData[nextIndex] = new Sample(position, deltaTime);
        nextIndex = ++nextIndex % size;
    }
    public void Clear()
    {
        for (int i = 0; i < size; i++)
            sampleData[i] = new Sample();
    }
    public Vector3 GetAverageVelocity(Vector3 currentPosition, float currentDeltaTime)
    {
        // The recorded sample furthest back in time.
        Sample previous = sampleData[nextIndex % size];
        Vector3 positionDelta = currentPosition - previous.position;
        float totalTime = currentDeltaTime;
        for (int i = 0; i < size; i++)
            totalTime += sampleData[i].deltaTime;
        return positionDelta / totalTime;
    }
    [Serializable]
    struct Sample
    {
        public Vector3 position;
        public float deltaTime;
        public Sample(Vector3 position, float deltaTime)
        {
            this.position = position;
            this.deltaTime = deltaTime;
        }
    }
    public const int minSize = 1;
}

此外,我注意到,我记录了很多零速度值,现在这种情况得到了缓解,因为我正在跟踪位置,我也想在不拖动而是按住时更新:

if (input.phase == TouchPhase.Moved || input.phase == TouchPhase.Stationary)
{
    velocityBuffer.AddSample(transform.position, Time.deltaTime);
}
if (input.phase == TouchPhase.Ended || input.phase == TouchPhase.Canceled)
{
    velocity = -velocityBuffer.GetAverageVelocity(transform.position, Time.deltaTime);
}

最后,我没有将相机设置为每帧的手指世界位置,而是使用了一点Lerp/MoveForward插值来平滑任何抖动。很难在清晰的控制和流畅的外观之间获得最佳值,但我认为这就是用户输入的方式,用户输入可能会迅速变化。

当然,我仍然对其他方法、更好的解决方案或对我当前实现的看法感兴趣。