Aceleração na Unidade
Estou tentando imitar aceleração e desaceleração no Unity.
Eu escrevi no código para gerar uma trilha no Unity e colocar um objeto em um local específico na trilha com base no tempo. O resultado se parece um pouco com isso.
O problema que tenho atualmente é que cada seção do spline tem um comprimento diferente e o cubo se move através de cada seção em uma velocidade diferente, mas uniforme. Isso causa saltos repentinos na mudança da velocidade do cubo ao fazer a transição entre seções.
Para tentar corrigir esse problema, tentei usarAs equações de flexibilização de Robert Penner noGetTime(Vector3 p0, Vector3 p1, float alpha)
método. No entanto, embora isso tenha ajudado um pouco, não foi suficiente. Ainda havia saltos de velocidade entre as transições.
Alguém tem alguma idéia de como eu poderia dinamicamente facilitar a posição do cubo para parecer que estava acelerando e desacelerando, sem grandes saltos de velocidade entre os segmentos da pista?
Eu escrevi um script que mostra uma implementação simples do meu código. Pode ser anexado a qualquer objeto do jogo. Para facilitar a visualização do que está acontecendo quando o código é executado, anexe-o a algo como um cubo ou esfera.
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
public class InterpolationExample : MonoBehaviour {
[Header("Time")]
[SerializeField]
private float currentTime;
private float lastTime = 0;
[SerializeField]
private float timeModifier = 1;
[SerializeField]
private bool running = true;
private bool runningBuffer = true;
[Header("Track Settings")]
[SerializeField]
[Range(0, 1)]
private float catmullRomAlpha = 0.5f;
[SerializeField]
private List<SimpleWayPoint> wayPoints = new List<SimpleWayPoint>
{
new SimpleWayPoint() {pos = new Vector3(-4.07f, 0, 6.5f), time = 0},
new SimpleWayPoint() {pos = new Vector3(-2.13f, 3.18f, 6.39f), time = 1},
new SimpleWayPoint() {pos = new Vector3(-1.14f, 0, 4.55f), time = 6},
new SimpleWayPoint() {pos = new Vector3(0.07f, -1.45f, 6.5f), time = 7},
new SimpleWayPoint() {pos = new Vector3(1.55f, 0, 3.86f), time = 7.2f},
new SimpleWayPoint() {pos = new Vector3(4.94f, 2.03f, 6.5f), time = 10}
};
[Header("Debug")]
[Header("WayPoints")]
[SerializeField]
private bool debugWayPoints = true;
[SerializeField]
private WayPointDebugType debugWayPointType = WayPointDebugType.SOLID;
[SerializeField]
private float debugWayPointSize = 0.2f;
[SerializeField]
private Color debugWayPointColour = Color.green;
[Header("Track")]
[SerializeField]
private bool debugTrack = true;
[SerializeField]
[Range(0, 1)]
private float debugTrackResolution = 0.04f;
[SerializeField]
private Color debugTrackColour = Color.red;
[System.Serializable]
private class SimpleWayPoint
{
public Vector3 pos;
public float time;
}
[System.Serializable]
private enum WayPointDebugType
{
SOLID,
WIRE
}
private void Start()
{
wayPoints.Sort((x, y) => x.time.CompareTo(y.time));
wayPoints.Insert(0, wayPoints[0]);
wayPoints.Add(wayPoints[wayPoints.Count - 1]);
}
private void LateUpdate()
{
//This means that if currentTime is paused, then resumed, there is not a big jump in time
if(runningBuffer != running)
{
runningBuffer = running;
lastTime = Time.time;
}
if(running)
{
currentTime += (Time.time - lastTime) * timeModifier;
lastTime = Time.time;
if(currentTime > wayPoints[wayPoints.Count - 1].time)
{
currentTime = 0;
}
}
transform.position = GetPosition(currentTime);
}
#region Catmull-Rom Math
public Vector3 GetPosition(float time)
{
//Check if before first waypoint
if(time <= wayPoints[0].time)
{
return wayPoints[0].pos;
}
//Check if after last waypoint
else if(time >= wayPoints[wayPoints.Count - 1].time)
{
return wayPoints[wayPoints.Count - 1].pos;
}
//Check time boundaries - Find the nearest WayPoint your object has passed
float minTime = -1;
float maxTime = -1;
int minIndex = -1;
for(int i = 1; i < wayPoints.Count; i++)
{
if(time > wayPoints[i - 1].time && time <= wayPoints[i].time)
{
maxTime = wayPoints[i].time;
int index = i - 1;
minTime = wayPoints[index].time;
minIndex = index;
}
}
float timeDiff = maxTime - minTime;
float percentageThroughSegment = 1 - ((maxTime - time) / timeDiff);
//Define the 4 points required to make a Catmull-Rom spline
Vector3 p0 = wayPoints[ClampListPos(minIndex - 1)].pos;
Vector3 p1 = wayPoints[minIndex].pos;
Vector3 p2 = wayPoints[ClampListPos(minIndex + 1)].pos;
Vector3 p3 = wayPoints[ClampListPos(minIndex + 2)].pos;
return GetCatmullRomPosition(percentageThroughSegment, p0, p1, p2, p3, catmullRomAlpha);
}
//Prevent Index Out of Array Bounds
private int ClampListPos(int pos)
{
if(pos < 0)
{
pos = wayPoints.Count - 1;
}
if(pos > wayPoints.Count)
{
pos = 1;
}
else if(pos > wayPoints.Count - 1)
{
pos = 0;
}
return pos;
}
//Math behind the Catmull-Rom curve. See here for a good explanation of how it works. https://stackoverflow.com/a/23980479/4601149
private Vector3 GetCatmullRomPosition(float t, Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float alpha)
{
float dt0 = GetTime(p0, p1, alpha);
float dt1 = GetTime(p1, p2, alpha);
float dt2 = GetTime(p2, p3, alpha);
Vector3 t1 = ((p1 - p0) / dt0) - ((p2 - p0) / (dt0 + dt1)) + ((p2 - p1) / dt1);
Vector3 t2 = ((p2 - p1) / dt1) - ((p3 - p1) / (dt1 + dt2)) + ((p3 - p2) / dt2);
t1 *= dt1;
t2 *= dt1;
Vector3 c0 = p1;
Vector3 c1 = t1;
Vector3 c2 = (3 * p2) - (3 * p1) - (2 * t1) - t2;
Vector3 c3 = (2 * p1) - (2 * p2) + t1 + t2;
Vector3 pos = CalculatePosition(t, c0, c1, c2, c3);
return pos;
}
private float GetTime(Vector3 p0, Vector3 p1, float alpha)
{
if(p0 == p1)
return 1;
return Mathf.Pow((p1 - p0).sqrMagnitude, 0.5f * alpha);
}
private Vector3 CalculatePosition(float t, Vector3 c0, Vector3 c1, Vector3 c2, Vector3 c3)
{
float t2 = t * t;
float t3 = t2 * t;
return c0 + c1 * t + c2 * t2 + c3 * t3;
}
//Utility method for drawing the track
private void DisplayCatmullRomSpline(int pos, float resolution)
{
Vector3 p0 = wayPoints[ClampListPos(pos - 1)].pos;
Vector3 p1 = wayPoints[pos].pos;
Vector3 p2 = wayPoints[ClampListPos(pos + 1)].pos;
Vector3 p3 = wayPoints[ClampListPos(pos + 2)].pos;
Vector3 lastPos = p1;
int maxLoopCount = Mathf.FloorToInt(1f / resolution);
for(int i = 1; i <= maxLoopCount; i++)
{
float t = i * resolution;
Vector3 newPos = GetCatmullRomPosition(t, p0, p1, p2, p3, catmullRomAlpha);
Gizmos.DrawLine(lastPos, newPos);
lastPos = newPos;
}
}
#endregion
private void OnDrawGizmos()
{
#if UNITY_EDITOR
if(EditorApplication.isPlaying)
{
if(debugWayPoints)
{
Gizmos.color = debugWayPointColour;
foreach(SimpleWayPoint s in wayPoints)
{
if(debugWayPointType == WayPointDebugType.SOLID)
{
Gizmos.DrawSphere(s.pos, debugWayPointSize);
}
else if(debugWayPointType == WayPointDebugType.WIRE)
{
Gizmos.DrawWireSphere(s.pos, debugWayPointSize);
}
}
}
if(debugTrack)
{
Gizmos.color = debugTrackColour;
if(wayPoints.Count >= 2)
{
for(int i = 0; i < wayPoints.Count; i++)
{
if(i == 0 || i == wayPoints.Count - 2 || i == wayPoints.Count - 1)
{
continue;
}
DisplayCatmullRomSpline(i, debugTrackResolution);
}
}
}
}
#endif
}
}