Wednesday, May 22, 2019

c# - Unity method to get a vector, which is x seconds behind another moving vector (A bit like TrailRenderer)



I want a function, which lets me follow a vector with a bit offset or delay, like it's done in the TrailRenderer. I tried to achieve this with the Lerp function, but that didn't work for me like I want, since I had to wait for a specific time until the lerp was done and then updated my targetVector of the Lerp again, which led to a bad result.


Now a possible solution to my problem would be a List, which holds all Positions of my Vector from the past 50 frames or so and I assign the same values to my other Vector with a specific offset. But I was wondering if there is some built in method in Unity, which could achieve this for me.


Edit: This is about moving a Canvas



Answer



What you describe, in terms of following a position's exact path with a time delay, might be both more complicated and less satisfying than what you want.


Here's a mock-up of two possibilities:


Animated example of two following modes


The object labeled "Path" follows the exact path of the leader object, a constant amount of time behind it. This means:





  • When the leader starts moving, there is a delay before the follower moves. Likewise for stopping.




  • Any kinks or jitter in the leader's movement is repeated exactly by the follower.




The object labeled "Blend" mixes-together its current position with the leader's each frame, so that it attracts toward it asymptotically.




  • The follower always moves directly toward the leader, cutting corners tighter and smoothing out jitter to a degree.





  • The follower moves slower when it's close to the leader, and faster when far away, making its follow distance/delay non-constant (but for some applications this gives a pleasant ease-in and ease-out to its motion effectively "for free")




The great thing about the blend approach is how simple the code is:


public class BlendFollower : MonoBehaviour {

public Transform leader;
public float followSharpness = 0.05f;


void LateUpdate () {
transform.position += (leader.position - transform.position) * followSharpness;
}
}

I use this about 80% of the time when I want some quantity to track toward another in games - it works for floats, positions, colours, rotations, etc. It's a super useful little trick.




Edit: for rotation, you'd use something like this:


transform.rotation = Quaternion.Lerp(

transform.rotation,
leader.rotation,
followSharpness);

Just ensure the Quaternions you start with are valid (not all zeroes or NaNs) or this will blow up. I often write a Quaternion.IsValid() extension method to check this if I'm working with a lot of computed rotations.




Note that since this is blending a constant amount per frame, so at higher framerates it will be sharper and at lower framerates, spongier. For purely visual effects this is often tolerable, but if gameplay outcomes depend on the follow rate then you'll either want to correct for this or move it to FixedUpdate so it's consistent.


Compare this to the path follow:


(Variable framerates account for some of this complexity - if you want to follow a constant number of frames behind, rather than seconds, then the code is a bit simpler. Also, I threw in my path-drawing debug gizmo code too.)


public class PathFollower : MonoBehaviour {


const int MAX_FPS = 60;

public Transform leader;
public float lagSeconds = 0.5f;

Vector3[] _positionBuffer;
float[] _timeBuffer;
int _oldestIndex;
int _newestIndex;


// Use this for initialization
void Start () {
int bufferLength = Mathf.CeilToInt(lagSeconds * MAX_FPS);
_positionBuffer = new Vector3[bufferLength];
_timeBuffer = new float[bufferLength];

_positionBuffer[0] = _positionBuffer[1] = leader.position;
_timeBuffer[0] = _timeBuffer[1] = Time.time;


_oldestIndex = 0;
_newestIndex = 1;
}


void LateUpdate () {
// Insert newest position into our cache.
// If the cache is full, overwrite the latest sample.
int newIndex = (_newestIndex + 1) % _positionBuffer.Length;
if (newIndex != _oldestIndex)

_newestIndex = newIndex;

_positionBuffer[_newestIndex] = leader.position;
_timeBuffer[_newestIndex] = Time.time;

// Skip ahead in the buffer to the segment containing our target time.
float targetTime = Time.time - lagSeconds;
int nextIndex;
while (_timeBuffer[nextIndex = (_oldestIndex + 1) % _timeBuffer.Length] < targetTime)
_oldestIndex = nextIndex;


// Interpolate between the two samples on either side of our target time.
float span = _timeBuffer[nextIndex] - _timeBuffer[_oldestIndex];
float progress = 0f;
if(span > 0f)
{
progress = (targetTime - _timeBuffer[_oldestIndex]) / span;
}

transform.position = Vector3.Lerp(_positionBuffer[_oldestIndex], _positionBuffer[nextIndex], progress);

}

void OnDrawGizmos()
{
if (_positionBuffer == null || _positionBuffer.Length == 0)
return;

Gizmos.color = Color.grey;

Vector3 oldPosition = _positionBuffer[_oldestIndex];

int next;
for(int i = _oldestIndex; i != _newestIndex; i = next)
{
next = (i + 1) % _positionBuffer.Length;
Vector3 newPosition = _positionBuffer[next];
Gizmos.DrawLine(oldPosition, newPosition);
oldPosition = newPosition;
}
}
}

No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...