在Unity3d中具有惯性的Android触控相机控制

时间:2016-07-18 19:27:25

标签: c# mobile unity3d drag inertia

我正在为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);
    }
}

所以,我总是在移动时计算速度,一旦我停止输入,它应该保持在最后已知的速度,应用它并从那里开始减少直到停止。但是,通常最后一个速度为零,因为看起来当在屏幕上拖动/滑动时,手指实际上会很快停止并在 TouchPhase.Moved 结束之前报告零速度。

现在我的解决方案/解决方法是保持最后几帧(可能是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之后拍摄相机到手指位置,然后突然释放它而没有任何阻尼。

1 个答案:

答案 0 :(得分:0)

我感觉不像是找到了完美的解决方案,但至少到现在为止我有一些更好的东西,也更“物理上正确”。首先,我现在将相机位置和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 / MoveTowards插值来消除任何抖动。在清晰控制和平滑外观之间很难获得最佳价值,但我认为这是用户输入的方式,可以快速变化。

当然,我仍然对其他方法,更好的解决方案或对我当前实施的意见感兴趣。