I have a large dataset that contains the position data for a vehicle at 0.1 second intervals and I have been trying to take this data and animate a gameobject smoothly along these positions.
I have tried Using AnimationCurves and adding keyframes for the individual axis similar to this
public AnimationCurve ac;
public AnimationCurve ac1;
public Vector3 pos1 = new Vector3(0.0f, 0.0f, 0.0f);
public Vector3 pos2 = new Vector3(0.0f, 0.0f, 0.0f);
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
StartCoroutine(Move(pos1, pos2, ac, ac1, 3.0f));
}
}
IEnumerator Move(Vector3 pos1, Vector3 pos2, AnimationCurve ac, AnimationCurve ac1, float time)
{
float timer = 0.0f;
while (timer <= time)
{
var x = Mathf.Lerp(pos1.x, pos2.x, ac.Evaluate(timer / time));
var y = Mathf.Lerp(pos1.y, pos2.y, ac1.Evaluate(timer / time));
transform.position = new Vector3(x, y, 0);
timer += Time.deltaTime;
yield return null;
}
}
However I couldnt figure out how to turn the splitsecond positional data into the correct values for the AnimationClip.
I also tried using looping over the positions with coroutines similar to this:
var coords = vehicles[0].middleCoords;
var previousStep = coords[0];
foreach(Vector3 step in coords)
{
MoveOverSeconds(step, 0.1f);
}
public IEnumerator MoveOverSeconds(Vector3 end, float seconds)
{
yield return new WaitForSeconds(seconds);
float elapsedTime = 0;
Vector3 startingPos = transform.position;
while (elapsedTime < seconds)
{
transform.position = Vector3.Lerp(startingPos, end, (elapsedTime / seconds));
elapsedTime += Time.deltaTime;
yield return new WaitForEndOfFrame();
}
transform.position = end;
However because the for loop runs independent of time it starts the co-routine before the other one ends.
Any help on how to solve this problem would be greatly appreciated.
Related
I am trying to implement a coroutine that moves an object to a specific point and then comes back to the origin (specified by me) as a coroutine in Unity, but the coroutine is not executed after returning to the origin. What's wrong?
public class LineManager : MonoBehaviour
{
public Vector3 positionToGo = new Vector3(0, -4, 0);
IEnumerator coroutine;
void Start()
{
coroutine = MoveToPosition(transform, positionToGo, 2f);
}
void Update()
{
if(transform.position.y == 4)
{
StartCoroutine(coroutine);
}
}
public IEnumerator MoveToPosition(Transform transform, Vector3 position, float timeToMove)
{
var currentPos = transform.position;
var t = 0f;
while (t < 1 && currentPos.y > position.y)
{
t += Time.deltaTime / timeToMove;
transform.position = Vector3.Lerp(currentPos, position, t);
yield return null;
}
transform.position = new Vector3(0, 4, 0);
StopCoroutine(coroutine);
}
}
You don't really need to restart your routine for that.
But first things first:
transform.position.y == 4
is risky as it is a direct float comparison and might fail. See Is floating point math broken?
Anyways, back to the coroutine restarting. Why not simply move without a routine at all
public Vector3 positionToGo = new Vector3(0, -4, 0);
public float timeToMove = 2f;
private Vector3 originalPosition;
private float factor;
private void Awake()
{
originalPosition = transform.position;
}
private void Update()
{
if(factor >= 1)
{
factor = 0;
transform.position = originalPosition;
}
else
{
factor += Time.deltaTime / timeToMove;
transform.position = Vector3.Lerp(originalPosition, positionToGo, factor);
}
}
As an alternative if you want to use the routine you can always simply make it loop
public Vector3 positionToGo = new Vector3(0, -4, 0);
public float timeToMove = 2f;
private IEnumerator Start()
{
var originalPosition = transform.position;
while(true)
{
for(var factor = 0f; factor < 1f; factor += Time.deltaTime / timeToMove)
{
transform.position = Vector3.Lerp(originalPosition, position, factor);
yield return null;
}
transform.position = originalPosition;
}
}
Call the Coroutine in that IEnumerator with WaitForSeconds
I am rotating my camera to lookat a target object using Quaternion. I am using Quaternion.LookRotation and Quaternion.Slerp to make the camera rotate smoothly inside coroutine. Unfortunately the camera jitters a lot when rotating. How do I make the rotation smooth with my current code?
Quaternion targetRotation;
public Transform lookAtObject;
IEnumerator RotateTowardsTarget () {
var duration = 2.0f;
for (float t = 0.0f; t < duration; t += Time.deltaTime) {
targetRotation = Quaternion.LookRotation (lookAtObject.position - transform.position);
transform.rotation = Quaternion.Slerp (transform.rotation, targetRotation, t / duration);
yield return null;
}
}
You currently always start the lerp at the current rotation towards the target.
In your usecase what you rather want to do is store the initial rotation and rather interpolate between the initial and target rotation like
IEnumerator RotateTowardsTarget ()
{
var duration = 2.0;
// store the initial and target rotation once
var startRotation = transform.rotation;
var targetRotation = Quaternion.LookRotation (lookAtObject.position - transform.position);
for (var timePassed = 0.0f; timePassed < duration; timePassed += Time.deltaTime)
{
var factor = timePassed / duration;
// optionally add ease-in and -out
//factor = Mathf.SmoothStep(0, 1, factor);
transform.rotation = Quaternion.Slerp (startRotation, targetRotation, factor);
yield return null;
}
// just to be sure to end up with clean values
transform.rotation = targetRotation;
}
This will make the camera go from the current to the new rotation linear within 2 seconds
Slerp takes third parameter as progress not as actual time you can do this
void Update () {
StartCoroutine(RotateOverTime(transform.rotation,
lookAtObject.rotation, 1f / speed));
}
IEnumerator RotateOverTime (Quaternion originalRotation, Quaternion
finalRotation, float duration) {
if (duration > 0f) {
float startTime = Time.time;
float endTime = startTime + duration;
transform.rotation = originalRotation;
yield return null;
while (Time.time < endTime) {
float progress = (Time.time - startTime) / duration;
// progress will equal 0 at startTime, 1 at endTime.
transform.rotation = Quaternion.Slerp (originalRotation,
finalRotation, progress);
yield return null;
}
}
transform.rotation = finalRotation;
}
You can now send in duration as seconds
I have a problem with moving from one place to another in Unity over time. I would like my character to move from current position to current + 1 on Y. Unfortunately, looks like it does not get the current position properly or something, since if I debug what I wrote, it says that the magnitude is always 1, so the point is moving with me. Shouldn't it just check the current position and add 1 to Y, move to that position and then check again? I have no idea what's wrong with this code, or if it's strictly connected with how Unity checks positions and things in real time?
public bool moving = false;
private Vector3 dir;
void FrontMovement()
{
Vector3 movement = new Vector3(-Mathf.Sin(transform.eulerAngles.z * Mathf.PI / 180), Mathf.Cos(transform.eulerAngles.z * Mathf.PI / 180), 0f); // always moving front, even after rotation
if (moving == false)
{
dir = movement - transform.position;
moving = true;
return;
}
transform.Translate(dir.normalized * Time.deltaTime);
if(dir.magnitude <= Time.deltaTime)
{
Debug.Log("Finished movement");
moving = false;
}
}
void FixedUpdate()
{
Debug.Log(dir.magnitude);
FrontMovement();
}
I would also like to know how to do rotations over time.
https://docs.unity3d.com/ScriptReference/Vector3.Lerp.html
lerp also works for rotations
// Movement speed in units per second.
public float speed = 1.0F;
// Time when the movement started.
private float startTime;
// Total distance between the markers.
private float journeyLength;
void StartMoving() {
// Keep a note of the time the movement started.
startTime = Time.time;
Vector3 modifiedPosition = transform.position;
transform.position.y += 1.0f;
// Calculate the journey length.
journeyLength = Vector3.Distance(transform.position, modifiedPosition.position);
moving = true;
}
// Move to the target end position.
void Update()
{
if (moving) {
// Distance moved equals elapsed time times speed..
float distCovered = (Time.time - startTime) * speed;
// Fraction of journey completed equals current distance divided by total distance.
float fractionOfJourney = distCovered / journeyLength;
// Set our position as a fraction of the distance between the markers.
transform.position = Vector3.Lerp(startMarker.position, endMarker.position, fractionOfJourney);
if (fractionOfJourney >= 1.0f) {
moving = false;
}
}
}
You could use Coroutine + Vector3.Lerp to move with a specified amount of time:
public IEnumerator MoveToPosition(Transform transform, Vector3 position, float timeToMove)
{
var currentPos = transform.position;
var t = 0f;
while(t <= 1f)
{
t += Time.deltaTime / timeToMove;
transform.position = Vector3.Lerp(currentPos, position, t);
yield return null;
}
transform.position = position;
}
you call the coroutine in Start Method
StartCoroutine(MoveToPosition(transform, newposition, timeToMove))
You could use the same logic for Rotating, with Quaternion.Lerp or Slerp and Quaternion.LookRotation, of course you have lot of sample with rotation over time on WEB!! google is your friend...
public IEnumerator RotateToDirection(Transform transform, Vector3 position, float timeToRotate)
{
var startRotation = transform.rotation;
var direction = position - transform.position;
var finalRotation = Quaternion.LookRotation(direction);
var t = 0f;
while (t <= 1f)
{
t += Time.deltaTime / timeToRotate;
transform.rotation = Quaternion.Lerp(startRotation, finalRotation, t);
yield return null;
}
transform.rotation = finalRotation;
}
I have the following Unity Script
void Update()
{
Ray mouseRay = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit = new RaycastHit();
if(Physics.Raycast(mouseRay, out hit))
{
if (Input.GetMouseButtonDown(0))
{
Camera.main.GetComponent<CameraController>().ZoomIn(hit.transform.position);
}
}
}
Then in the camera I Have
public void ZoomIn(Vector3 target)
{
zoomSavePosition = camera.transform.position;
StartCoroutine(Zoom(zoomSavePosition, target, zoomDuration));
}
// ZoomRoutine
IEnumerator Zoom(Vector3 from, Vector3 to, float duration)
{
float time = 0;
Quaternion rotation = camera.transform.rotation;
Quaternion targetRotation = Quaternion.LookRotation(to - camera.transform.position);
while (time < zoomDuration)
{
camera.transform.position = Vector3.Lerp(from, to, time / zoomDuration);
camera.transform.rotation = Quaternion.Slerp(rotation, targetRotation, time / zoomDuration);
time += TimeManager.instance.deltaTime;
yield return null;
}
camera.transform.position = to;
camera.transform.rotation = targetRotation;
}
This works great, but it zoom inside the object. I would like to, no matter from where and to what, I always zoom and end up at the same distance of the target.
I was thinking to use MoveTowards but inside the coroutine this will make the movement stop, but the rotation will keep going (and it's a big ugly)
So give a point A and a point B how do I calculate a distance in the middle that is set to always the same distance from B ?
For zooming to a specific distance from the target object you need to calculate the distance from the target object in direction of the camera and then subtract it to the target position.
var heading = to - from;
var dist = heading.magnitude;
var direction = heading / dist;
Vector3 newTargetPosition = to - (direction * distance);
Ref: https://docs.unity3d.com/Manual/DirectionDistanceFromOneObjectToAnother.html
With this adjustment you can stop zooming to any provided distance.
Here is how adjusted code would look like:
public void ZoomIn(Vector3 target)
{
float targetDistance = 2f;
zoomSavePosition = camera.transform.position;
StartCoroutine(Zoom(zoomSavePosition, target, zoomDuration, targetDistance));
}
// ZoomRoutine
IEnumerator Zoom(Vector3 from, Vector3 to, float duration, float distance)
{
float time = 0;
Quaternion rotation = camera.transform.rotation;
Quaternion targetRotation = Quaternion.LookRotation(to - camera.transform.position);
var heading = to - from;
var dist = heading.magnitude;
var direction = heading / dist;
Vector3 newTargetPosition = to - (direction * distance);
while (time < zoomDuration)
{
camera.transform.position = Vector3.Lerp(from, newTargetPosition, time / zoomDuration);
camera.transform.rotation = Quaternion.Slerp(rotation, targetRotation, time / zoomDuration);
time += Time.deltaTime;
yield return null;
}
camera.transform.position = newTargetPosition;
camera.transform.rotation = targetRotation;
}
You can set value of targetDistance where you want to stop zooming.
This script is called when the user release the mouse button:
float rot_duration = 3f;
float rot_speed = 1.8f;
Quaternion final_rot;
void Start()
{
cubeMesh = GameObject.FindWithTag("CubeMesh");
Vector3 initial_rot = transform.rotation.eulerAngles;
final_rot = Quaternion.Euler(new Vector3(initial_rot.x, initial_rot.y, 180));
}
public void Update()
{
if (Input.GetMouseButtonUp(0))
{
StartCoroutine (DelayRotate (0.1F));
}
}
IEnumerator DelayRotate(float waitTime)
{
yield return new WaitForSeconds (waitTime);
float rot_elapsedTime = 0.0F;
while (rot_elapsedTime < rot_duration) {
cubeMesh.transform.rotation = Quaternion.Slerp (transform.rotation, final_rot, rot_elapsedTime);
rot_elapsedTime += Time.deltaTime * rot_speed;
yield return null;
}
}
This script makes a GameObject rotate, 0.1 seconds after mouse button release. The problem is that it "flips" the GameObject quickly then starts rotating.
I believe it is flipping due to final_rot2 = Quaternion.Euler(new Vector3(initial_rot.x, initial_rot.y, 180)); (because of 180 value) What should I do instead?
I looked at code carefully and I was able to spot two mistakes.
1. The one mistake that is causing the problem is:
cubeMesh.transform.rotation = Quaternion.Slerp (transform.rotation, final_rot, rot_elapsedTime);
The guy above me said you should replace transform.rotation with cubeMesh.transform.rotation. That is close but wont work. What you are suppose to is to get the current position of the GameObject outside the while loop and store it somewhere, then you can use it later on inside the while loop. For example,
Quaternion currentLocation = cubeMesh.transform.rotation;
while(...){
cubeMesh.transform.rotation = Quaternion.Slerp (currentLocation, final_rot, rot_elapsedTime);
...Other codes
}
2. Another mistake I found is that it looks like you are trying to rotate the object within time because you have a variable called rot_duration.
If this is true then you failed when you did Quaternion.Slerp (transform.rotation, final_rot, rot_elapsedTime);.
If you want the object to rotate within rot_duration amount of time, change rot_elapsedTime to rot_elapsedTime / rot_duration. Also remove rot_speed as that will NOT work if you want to rotate over time.
If this is NOT what you are trying to do then the first mistake I found should fix your problem.
Your final Code should look like something below:
float rot_duration = 10f;
float rot_speed = 3f;
Quaternion final_rot;
GameObject cubeMesh;
void Start()
{
cubeMesh = GameObject.FindWithTag("CubeMesh");
Vector3 initial_rot = transform.rotation.eulerAngles;
final_rot = Quaternion.Euler(new Vector3(initial_rot.x, initial_rot.y, 180));
}
public void Update()
{
if (Input.GetMouseButtonUp(0))
{
StartCoroutine(Delay(1));
}
}
IEnumerator Delay(float waitTime)
{
yield return new WaitForSeconds(waitTime);
float rot_elapsedTime = 0.0F;
//Get the current rotation
Quaternion currentLocation = cubeMesh.transform.rotation;
while (rot_elapsedTime < rot_duration)
{
rot_elapsedTime += Time.deltaTime;
cubeMesh.transform.rotation = Quaternion.Slerp(currentLocation, final_rot, rot_elapsedTime / rot_duration);
yield return null;
}
}
The problem here is that this doesn't update for each frame. I see that you add Time.deltaTime, but it doesn't update per frame, it only uses the value for the current frame several times, since you do everything in one update.
This code might work:
float rot_duration = 10f;
float rot_speed = 3f;
float rot_elapsedTime = 3f;
Quaternion final_rot;
public void Update()
{
if (Input.GetMouseButtonUp(0))
{
StartCoroutine (Delay (1));
}
if (rot_elapsedTime < rot_duration) {
cubeMesh.transform.rotation = Quaternion.Slerp (transform.rotation, final_rot, rot_elapsedTime);
rot_elapsedTime += Time.deltaTime * rot_speed;
}
IEnumerator Delay(float waitTime)
{
yield return new WaitForSeconds (waitTime);
rot_elapsedTime = 0.0F;
}
But as being said, transform.Rotate is probably better.
Edit: Updated after OP's edit with new code, adding a 1 sec delay.