簡體   English   中英

Unity3d中具有慣性的Android觸摸相機控制

[英]Android touch camera control with inertia in Unity3d

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

這工作得更好一點,因為它通常至少報告一些速度,但總的來說它並沒有好多少,而且感覺很hacky。 根據觸摸的速度,我最終可能會得到 20 個零速度條目,這使得阻尼太強,有時速度隨機變得如此之大,以至於相機只是輕彈了幾百個單位。

我的計算有什么問題,還是我正在監督一些簡單的事情來解決這個問題? 有人有平滑相機拖動的工作解決方案嗎? 我看了幾款手游,其實很多都感覺有點笨拙,比如把相機卡到手指位置,經過幾個像素的deltaMovement,然后突然松開,沒有任何阻尼。

我不覺得我找到了完美的解決方案,但至少現在我有一些更好的東西,也更“物理上正確”。 首先,我現在將相機位置和 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 插值來消除任何抖動。 很難在清晰的控制和流暢的外觀之間獲得最佳價值,但我認為這是用戶輸入的方式,用戶輸入可能會迅速變化。

當然,我仍然對其他方法、更好的解決方案或關於我當前實現的意見感興趣。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM