[英]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.