I want to move GameObject from position A to B with Vector3.MoveTowards within x seconds and in a coroutine function. I know how to do this with Vector3.Lerp but this time would prefer to do it with Vector3.MoveTowards since both functions behave differently.
With Vector3.Lerp, this is done like this:
IEnumerator moveToX(Transform fromPosition, Vector3 toPosition, float duration)
{
float counter = 0;
//Get the current position of the object to be moved
Vector3 startPos = fromPosition.position;
while (counter < duration)
{
counter += Time.deltaTime;
fromPosition.position = Vector3.Lerp(startPos, toPosition, counter / duration);
yield return null;
}
}
I tried to do the the-same thing with Vector3.MoveTowards, but it's not working properly. The problem is that the move finishes before the x time or duration. Also, it doesn't move smoothly. It jumps to the middle of both positions than to the end of position B.
This is the function that uses Vector3.MoveTowards with the issue above:
IEnumerator MoveTowards(Transform objectToMove, Vector3 toPosition, float duration)
{
float counter = 0;
while (counter < duration)
{
counter += Time.deltaTime;
Vector3 currentPos = objectToMove.position;
float time = Vector3.Distance(currentPos, toPosition) / duration;
objectToMove.position = Vector3.MoveTowards(currentPos, toPosition,
time);
Debug.Log(counter + " / " + duration);
yield return null;
}
}
How do you move GameObject from position A to B with Vector3.MoveTowards within x seconds and in a coroutine function?
Please do not suggest Vector3.Lerp as that's not what I want to use.
EDIT:
Replacing
float time = Vector3.Distance(currentPos, toPosition) / duration;
with
float time = Vector3.Distance(startPos, toPosition) / (duration * 60f);
works but introduces a problem when focus is shifted from Unity to another application. Doing that causes the movement to not finish. Calculating it every frame instead of once before starting the timer seems more reasonable.
MatrixTai's answer fixed both problems.
As I have long time not touching Unity... but I do believe you mess up in the calculation.
First of all, Vector3.MoveTowards(currentPos, toPosition, time) is talking about
Walking from currentPos to toPosition with each frame moving
certain distance time.
Thus using time as name is confusing, but fine lets keep it.
However, you will notice in the statement, time is something move each frame. But Vector3.Distance(currentPos, toPosition) / duration is velocity (m/s), not (m/frame). To make it (m/frame), simply times Time.deltatime which is (s/frame).
Secondly,
When in coroutine, which means the function is iterated each frame. In the line float time = Vector3.Distance(currentPos, toPosition) / duration * Time.deltatime;, what you will notice is that the time keeps on decreasing when going next frame as distance become smaller and smaller.
To be more concrete, we can do some math. Typically this should be done with calculus, but lets simplify it by considering just 2 points. When object postion d = 0, and object postion d ~= 9.9, assume endpoint at 10.
At point 1, the object has time = (10-0)/duration, full speed.
At point 2, the object has time = (10-9.9)/duration, 1/10 of full speed.
Unless you want it to move slower every frame, you cannot hold the value of duration unchanged. As after each frame, you want the velocity to be kept, duration should thus decreases with distance.
To make that physics work, minus the duration for the time passed.
So the final solution is
float time = Vector3.Distance(currentPos, toPosition) / (duration-counter) * Time.deltaTime;
Here is the complete function:
IEnumerator MoveTowards(Transform objectToMove, Vector3 toPosition, float duration)
{
float counter = 0;
while (counter < duration)
{
counter += Time.deltaTime;
Vector3 currentPos = objectToMove.position;
float time = Vector3.Distance(currentPos, toPosition) / (duration - counter) * Time.deltaTime;
objectToMove.position = Vector3.MoveTowards(currentPos, toPosition, time);
Debug.Log(counter + " / " + duration);
yield return null;
}
}
Related
IEnumerator ThrowObject(Transform objectToMove, Transform toPosition, float duration)
{
Vector3 originPos = objectToMove.position;
float counter = 0;
while (counter < duration)
{
counter += Time.deltaTime;
Vector3 currentPos = objectToMove.position;
float time = Vector3.Distance(currentPos, toPosition.position) / (duration - counter) * Time.deltaTime;
objectToMove.position = Vector3.MoveTowards(currentPos, toPosition.position, time);
yield return null;
}
var naviParent = GameObject.Find("Navi Parent");
StartCoroutine(ThrowBack(objectToMove, naviParent.transform.position , duration));
}
IEnumerator ThrowBack(Transform objectToMove,Vector3 originalPosition , float duration)
{
float counter = 0;
while (counter < duration)
{
counter += Time.deltaTime;
Vector3 currentPos = objectToMove.position;
float time = Vector3.Distance(currentPos, originalPosition) / (duration - counter) * Time.deltaTime;
objectToMove.position = Vector3.MoveTowards(currentPos, originalPosition, time);
//yield return null essentially means "return here every frame"
yield return null;
}
}
When the game start NAVI is child of Navi Parent. Navi Parent position is set to 0,0,0
Also NAVI is at position 0,0,0
Now NAVI is start moving to the target in the method ThrowObject and when NAVI reached the target a new Coroutine start and NAVI is moving back to his parent :
var naviParent = GameObject.Find("Navi Parent");
But when NAVI is reaching to Navi Parent back his position is not at 0,0,0 :
You can see NAVI is not back to the Navi Parent(The player hand) but he is back close to it.
Now NAVI position is : X 0.002350773 Y 0.0007914092 Z 0.002649077
And if I change it on my own the NAVI position to 0,0,0 now NAVI is in the player hand as it should be :
I want that when the NAVI is moving back in the ThrowBack that he will end at 0,0,0 and not that I will have to change it back to 0,0,0
And if I will change NAVI in the end after the second Coroutine ends it will looks like navi change position like jumping or changing position rude and I want it to move smooth back to 0,0,0 in the while loop.
I suspect your issue is with:
float time = Vector3.Distance(currentPos, originalPosition) / (duration - counter) * Time.deltaTime;
objectToMove.position = Vector3.MoveTowards(currentPos, originalPosition, time);
You are evaluating the currentPos every frame which then decreases time, eventually, due to also applying deltaTime, this time gets very small as it approaches the destination.
MoveTowards' third parameter is a maxDistanceDelta, which I assume is what you're trying to use time for, variable name notwithstanding. However, it is intended as a max speed limiter.
Try replacing the above two lones of code with:
objectToMove.position = Vector3.MoveTowards(currentPos, originalPosition, Speed * Time.deltaTime);
Or, at the very least, let time be no less than some small positive value (eg, time = Mathf.Max(time, 0.001f))
I think it happens because of your counter. Sometimes counter gets a bigger value than the duration and that might effect the calculations. Also using Vectore.Lerp is better. Check this out and see if your problem gets fixed.
private IEnumerator ThrowObject(Transform objectToMove, Transform toPosition, float duration)
{
Vector3 originPos = objectToMove.position;
float counter = 0;
while (objectToMove.position != toPosition.position)
{
counter += Time.deltaTime;
if(counter > duration)
{
counter = duration;
}
objectToMove.position = Vector3.Lerp(originPos, toPosition.position, counter / duration);
yield return null;
}
var naviParent = GameObject.Find("Navi Parent");
StartCoroutine(ThrowBack(objectToMove, naviParent.transform.position, duration));
}
private IEnumerator ThrowBack(Transform objectToMove, Vector3 originalPosition, float duration)
{
Vector3 originPos = objectToMove.position;
float counter = 0;
while (objectToMove.position != originalPosition)
{
counter += Time.deltaTime;
if (counter > duration)
{
counter = duration;
}
objectToMove.position = Vector3.Lerp(originPos, originalPosition, counter / duration);
yield return null;
}
}
My assumption is that you are trying to move the object back to the parent with a set time no matter the distance.
If I'm right the method you should use is this.
float duration = 1f;
float time = 0f;
while (time < 1)
{
time += Time.deltaTime / duration;
object.localPosition = Vector3.Lerp(object.localPosition, targetPosition, time);
yield return null;
}
That will move an object to a new position with a set duration.
I want to move an object slowly from his original position to a little higher position but this code moves the object instantly to the highest position even when i use really slow speed like 0.0001f. I call LiftObj() inside another code 1 time only and i tell it run until it reaches the liftOffset. What is wrong with this code?
void LiftObj(GameObject Obj) {
float origianlPos = Obj.transform.position.y;
while (Obj.transform.position.y < origianlPos + liftOffset) {
Obj.transform.position += Vector3.up * 0.0001f;
float newPos = Obj.transform.position.y;
newPos = Mathf.Clamp (newPos, newPos, newPos + liftOffset);
Obj.transform.position += Vector3.up * 0.0001f;
}
but this code moves the object instantly to the highest position even
when i use really slow speed like 0.0001f.
You are not waiting at-all. The while loop will execute as fast as possible no matter how low your variable is.You should be doing this in a coroutine function then wait with the yield keyword yield return null.
IEnumerator LiftObj(GameObject Obj)
{
float origianlPos = Obj.transform.position.y;
while (Obj.transform.position.y < origianlPos + liftOffset)
{
Obj.transform.position += Vector3.up * 0.0001f;
float newPos = Obj.transform.position.y;
newPos = Mathf.Clamp(newPos, newPos, newPos + liftOffset);
Obj.transform.position += Vector3.up * 0.0001f;
yield return null;
}
Debug.Log("Done Moving Up!");
}
then move it with:
StartCoroutine(LiftObj(myGameObject));
Not even sure that would work as expected because you are missing Time.delta so the movement may not be smooth. If all you want to do is move from one position to another overtime, use the sample code below:
IEnumerator LiftObj(GameObject playerToMove, Vector3 toPoint, float byTime)
{
Vector3 fromPoint = playerToMove.transform.position;
float counter = 0;
while (counter < byTime)
{
counter += Time.deltaTime;
playerToMove.transform.position = Vector3.Lerp(fromPoint, toPoint, counter / byTime);
yield return null;
}
}
Move from current myGameObject position to Vector3(0, 5, 0) in 4 seconds:
StartCoroutine(LiftObj(myGameObject, new Vector3(0, 5, 0), 4));
You're using a while loop in an incorrect place here.
While loops continue to execute with in one frame as long as the statement is true.
This means that regardless of how low your magnitude multiplier is (the 0.001f) it will just take more iterations with in the same frame to reach the target.
You should check to see each frame whether the object has reached its goal yet, and if it hasn't apply the transformation.
I want to move an object slowly from his original position to a little higher position but this code moves the object instantly to the highest position even when i use really slow speed like 0.0001f. I call LiftObj() inside another code 1 time only and i tell it run until it reaches the liftOffset. What is wrong with this code?
void LiftObj(GameObject Obj) {
float origianlPos = Obj.transform.position.y;
while (Obj.transform.position.y < origianlPos + liftOffset) {
Obj.transform.position += Vector3.up * 0.0001f;
float newPos = Obj.transform.position.y;
newPos = Mathf.Clamp (newPos, newPos, newPos + liftOffset);
Obj.transform.position += Vector3.up * 0.0001f;
}
but this code moves the object instantly to the highest position even
when i use really slow speed like 0.0001f.
You are not waiting at-all. The while loop will execute as fast as possible no matter how low your variable is.You should be doing this in a coroutine function then wait with the yield keyword yield return null.
IEnumerator LiftObj(GameObject Obj)
{
float origianlPos = Obj.transform.position.y;
while (Obj.transform.position.y < origianlPos + liftOffset)
{
Obj.transform.position += Vector3.up * 0.0001f;
float newPos = Obj.transform.position.y;
newPos = Mathf.Clamp(newPos, newPos, newPos + liftOffset);
Obj.transform.position += Vector3.up * 0.0001f;
yield return null;
}
Debug.Log("Done Moving Up!");
}
then move it with:
StartCoroutine(LiftObj(myGameObject));
Not even sure that would work as expected because you are missing Time.delta so the movement may not be smooth. If all you want to do is move from one position to another overtime, use the sample code below:
IEnumerator LiftObj(GameObject playerToMove, Vector3 toPoint, float byTime)
{
Vector3 fromPoint = playerToMove.transform.position;
float counter = 0;
while (counter < byTime)
{
counter += Time.deltaTime;
playerToMove.transform.position = Vector3.Lerp(fromPoint, toPoint, counter / byTime);
yield return null;
}
}
Move from current myGameObject position to Vector3(0, 5, 0) in 4 seconds:
StartCoroutine(LiftObj(myGameObject, new Vector3(0, 5, 0), 4));
You're using a while loop in an incorrect place here.
While loops continue to execute with in one frame as long as the statement is true.
This means that regardless of how low your magnitude multiplier is (the 0.001f) it will just take more iterations with in the same frame to reach the target.
You should check to see each frame whether the object has reached its goal yet, and if it hasn't apply the transformation.
I currently have an object that I'm moving a long a Bezier curve by sampling a few points along the curve and then just using Unity's MoveTowards to move the object to each point at a constant speed.
Before I do this though, I want to approximate the time that the object will reach the each of the points which I am currently doing by calculating the distance between the points and then just dividing that by the speed of the car.
The values I get are off usually off by one or two decimal places and what I've noticed is that the time values change. Does Unity do some things under the hood that I should take into account and is it possible to get more accurate results, at least to 1 decimal point?
EDIT: Here's some of the code so you can see what I'm trying to do:
This is the internal simulation
float initialTime = Time.time;
Debug.Log("Initial time simulation: " + initialTime);
float distance = 0;
Vector3 curr = transform.position;
Vector3 next;
for (int s = 1; s < steps+1; ++s)
{
float segment = (1.0f - (s/ (float)steps));
next = curve.GetPointAt(segment);
next.y = curr.y; //Only using x and z of bezier curves
distance += (next - curr).magnitude;
curr = next;
float elapsedTime = distance / speed;
output[s - 1] = new KeyValuePair<float, Vector3>(initialTime+elapsedTime, point);
}
This is what actually gets simulated
int counter = 1;
Debug.Log("Initial Time Actual: " + Time.time);
Vector3 toPoint = curve.GetPointAt((float)(steps - counter) / (steps));
toPoint.y = transform.position.y;
transform.LookAt(toPoint);
while (counter <= steps)
{
Vector3 newPos = Vector3.MoveTowards(transform.position, toPoint, speed * Time.deltaTime);
transform.LookAt(newPos);
transform.position = newPos;
if (transform.position == toPoint)
{
Debug.Log("CurrTime: " + Time.time + " " + transform.position);
++counter;
toPoint = curve.GetPointAt((float)(steps - counter) / (steps));
toPoint.y = transform.position.y;
transform.LookAt(toPoint);
}
yield return null;
}
Unless you provide more details I can only guess you are not taking Time.deltaTime into account when calculating the distance you move the car on each frame.
See, Update() function executes on every frame, but the time between frames is not necesarily uniform. Time.deltaTime gives you the time passed between the last frame and the current one.
You have to do something like this, as suggested in the Unity documentation:
private Vector3[] bezierTargets;
private float speed;
private int currentTarget;
// ...
void Update() {
// ...
float step = speed * Time.deltaTime;
transform.position = Vector3.MoveTowards(transform.position, bezierTargets[currentTarget].position, step);
}
Short version: Can I use Vector3.Lerp in transform.Translate(new Vector3(-6,0)); (without calculating a speed variable into the movement)?
I have a very simple game where the Player can move from 1 lane to another (max. 3 lanes (like Temple Run)). Right now my character is just teleporting to the other lanes and I want to make it look more smooth with Vector3.Lerp. My problem now is, I cant figure out how to implement it into my code. I don't calculate my movement with a speed variable, because my character starts moving uneven distances, which I don't want. I have 3 set lanes to move to.
if (Input.GetKeyDown("a"))
{
// Do I need a speed variable here?
transform.Translate(new Vector3(-6, 0));
}
If the code is not in Update(), you can use StartCoroutine
if (Input.GetKeyDown("a")) {
changing = true;
StartCoroutine(ChangeLane(int n));
}
IEnumerator ChangeLane(int n) {
float changeProgress = 0f;
float changeDuration = 1f;
originPosition = tranform.position;
//the speed should be run speed
targetPosition = originPosition + new Vector3(n * lanWidth, speed * changeDuration);
while(changeProgress < changeDuration) {
tranform.position = Vector3.Lerp(originPosition, targetPosition, changeProgress / changeDuration);
changeProgress += Time.DeltaTime;
yield return 0;
}
tranform.position = targetPosition;
changing = false;
}
If the code is in Update(), you can also use this, but while changing lane, you should not start a new change