我正在为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之后拍摄相机到手指位置,然后突然释放它而没有任何阻尼。
答案 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插值来消除任何抖动。在清晰控制和平滑外观之间很难获得最佳价值,但我认为这是用户输入的方式,可以快速变化。
当然,我仍然对其他方法,更好的解决方案或对我当前实施的意见感兴趣。