while (transform.position != new Vector3(desX, desY))
{
// 2 - Movement
Vector3 movement = new Vector3(
0.1f * desX,
0.1f * desY,
0);
//movement *= Time.deltaTime;
transform.Translate(movement);
}
This part of my program crashes the Unity engine and I'm pretty sure it's an infinite loop but I can't figure out why or how to fix it.
It's freezing your application because you're are not giving other scripts chance to run when the condition in the while loop is not met.
To fix that put that code in a coroutine function then add yield return null; to the while loop. This makes Unity to wait for a frame after each loop therefore given other scripts the opportunity to run every frame. This should fix the freeze issue whether the while loop exits or not. I would also suggest you use Vector3.Distance to determine when you are close to the destination.
public float reachThreshold = 0.2f;
void Start()
{
StartCoroutine(MoveBject());
}
IEnumerator MoveBject()
{
float distance = Vector3.Distance(transform.position, new Vector3(desX, desY));
while (distance > reachThreshold)
{
// 2 - Movement
Vector3 movement = new Vector3(
0.1f * desX,
0.1f * desY,
0);
//movement *= Time.deltaTime;
transform.Translate(movement);
//Wait a frame
yield return null;
}
}
If you really want to move GameObject to another position over time, see this post.
In general don't do things in while that are meant to happen in a per frame base! (Thanks Ron)
The result of while in the best case would be that your App stucks until eventually the vectors match, making the object "jump" in position. In the worst case they never match and your App freezes forever.
Instead you should use the Update() method, which is called each frame, and just move the object one step per frame.
To compare the Vectors you should better use Vector3.Distance. Using the == operator for Vector3 is basically ok but internally it does something equal to
Vector3.Distance(vectorA, vectorB) <= 0.00001f
which uses a very small thershold that might not match exactly. So Using Vector3.Distance you can set your own threshold e.g. to 0.1 to make it "easier" to match.
bool match = Vector3.Distance(transform.position, new Vector3(desX, desY) < 0.1f
To move an object towards another Unity actually already has a built-in method Vector3.MoveTowards e.g.
transform.position = Vector3.MoveTowards(transform.position, new Vector3 (desX, desY), 0.1f);
This takes care of Vectors3 comparing so you don't even need it anymore.
To make it smooth and framarate-save you were right already to use Time.deltaTime. Without this it would move 0.1 meters / frame but your framerate might not be stabil. Using Time.deltaTime makes it 0.1 meters / second which in almost all cases is what you actually want to achieve.
So to put it together your code should be something like
float desX;
float desY;
float moveSpeed = 0.1f;
void Update()
{
var targetPosition = new Vector3 (desX, desY);
transform.position = Vector3.MoveTowards(transform.position, targetPosition, moveSpeed * Time.deltaTime);
}
Note that in some cases MoveTowards still is not relayable enough e.g. if you want to track collisions or something. In this case refer here
Related
I'm newbie here, but will try to describe as clear as I can. (it's not that important, just to clarify) So I want to rotate an object for 405deg in 1 second only once, when space is pressed. I've thought it should be done using coroutines, so I've wrote something like this:
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
StopAllCoroutines(); //to prevent overlapping
StartCoroutine(Rotate());
}
}
IEnumerator Rotate()
{
//where final finalRotation is a rotation i want to get in a result
Quaternion finalRotation = Quaternion.Euler(0, 0, 405) * transform.rotation;
/*
using do..while to make sure that object will rotate even in case it's in same rotation as finalRotation
*/
do
{
//should smoothly rotate an object for 405deg in 1 sec but it doesnt
transform.Rotate(Vector3.forward, 405f * Time.deltaTime / 1f);
} while (transform.rotation != finalRotation);
yield return 1;
}
The code above works fine and everything is ok except one, it rotates an object instantly and not smoothly. I mean, if I use that line of code in Update, everything go smoothly
transform.Rotate(Vector3.forward, 405f * Time.deltaTime / 1f);
So my questions:
Why does it rotate smoothly in Update, but instantly in the coroutine;
and how can that be fixed?
Thanks in advance, once again, sorry if I made some mistakes when making a topic.
It updates instantly in the coroutine because you only yield when the rotation is complete. The frame wont be rendered until every pending/running coroutine yields or completes, so here your rotation completes and then finally the frame is rendered, making it an instant rotation.
Instead, you should yield in the loop.
You should also be aware that Quaternion finalRotation = Quaternion.Euler(0, 0, 405) * transform.rotation; and transform.Rotate(Vector3.forward, 405f); can have different results depending on parent object rotations, because they rotate in different spaces, the first rotating in global and the second rotating in local. This may result in the loop as written actually never ending.
Another problem is that there is no guarantee that summing up Time.deltaTime values will ever add up to 1f. Suppose on the very first frame, lag occurs, causing Time.deltaTime to be huge, 2f. You can see how it might never end as a result. As an alternative, you should remember the start rotation, then on each frame calcuate the rotation from that frame, then apply it that frame.
Altogether:
IEnumerator Rotate()
{
Quaternion startRotation = transform.rotation;
float endZRot = 405f;
float duration = 1f;
float t = 0;
while (t < 1f)
{
time = Mathf.Min(1f, t + Time.deltaTime/duration);
Vector3 newEulerOffset = Vector3.forward * (endZRot * t);
// global z rotation
transform.rotation = Quaternion.Euler(newEulerOffset) * startRotation;
// local z rotation
// transform.rotation = startRotation * Quaternion.Euler(newEulerOffset);
yield return null;
}
}
yield return null will wait one frame inside a coroutine. Right now you have placed the yield return 1; outside the while loop, so it wont do anything. In this situation I whould lerp between the two rotations in a while loop that goes from 0->1
or (if you dont want to do any of this stuff and dont care for a bit of bloat) use leantween, https://assetstore.unity.com/packages/tools/animation/leantween-3595
In the place where you rotate it one increment you have to wait for some time. Try
yield return new WaitForSeconds(0.1);
Also while I was typing this someone else answered.
I have a character that dashes using AddForce(), after dashing, i want him to auto walk in set speed using MoovePosition().
This project was made using the 3D engine and it work flawlessly. But I've changed it to 2D and after the AddForce, the character abruptly changes to the speed that MovePosition() set, it looks bad.
I thought about checking the velocity after the dash and when it reaches the same velocity as when auto-walk is happening, it calls for MovePosition. But it doesn't seem a good approach.. I'd love to receive some tips on this issue.
if(moveFactor!= 0)
{
Vector2 appliedForce = transform.position + transform.up * moveFactor;
Debug.Log("appliedForce: " + appliedForce.magnitude);
playerRigidbody.AddForce(appliedForce);
autoWalk = true;
moveFactor = 0;
}
if (autoWalk) playerRigidbody.MovePosition(walkForce);
moveFactor is a float value that a swipe detector script returns. The code is in FixedUpdate as well.
It would be helpful to see the problem instead of trying to reconstruct the picture from your description, but I'll try my best.
The Problem
The same frame you start the dash, you set autoWalk to true. As soon as the if statement is exited, the if (autoWalk) is evaluated and you start manually moving the RigidBody using MovePosition() — before the force has even been added.
I'm not sure why that ever worked in 3D, though.
Solution #1
Instead of using MovePosition(), move with AddForce(), with the default ForceMode2D; or with playerRigidbody.velocity, where-in you set it once, not every frame.
Solution #2
Instead of using AddForce(), for the dash, use the same old MovePosition() you use inside if (autoWalk).
Just increase the magnitude of walkForce several times for the duration of the dash.
Solution #3
The problem with using AddForce() for it is that you don't know when it's "finished", so you don't know when to transition to MovePosition().
If the above solutions don't work for you, implement the dash manually. You can do this with Vector2.Lerp() (or Vector3) inside a coroutine.
if (moveFactor != 0)
{
Vector2 endPosition = transform.position + transform.up * moveFactor;
StartCoroutine(Dash(endPosition));
moveFactor = 0;
}
Where Dash() is:
public float dashDuration = 1f;
private IEnumerator Dash(Vector2 endPosition)
{
autoWalk = false;
// Record the original position before you begin moving the object.
float startingPosition = transform.position;
float elapsed = 0f;
while (elapsed < dashDuration )
{
elapsed += Time.deltaTime;
Vector2 nextPosition = Vector2.Lerp(startingPosition, endPosition, elapsed / dashDuration );
playerRigidbody.MovePosition(nextPosition);
// Note: if the RigidBody is kinematic (isKinematic == true), this will simply teleport it to the given position.
// Otherwise, it will comply with its interpolation settings.
// You could directly modify playerRigidbody.position or transform.position (though the former is faster) for the same teleportation.
yield return null;
}
autoWalk = true;
}
appliedForce might not immediately work as endPosition — modify it so it does, in that case. dashDuration is in seconds.
Make sure Dash() is started only once, until it is finished. You can do this by saving the Coroutine returned by StartCoroutine() in a private field member and comparing it to null before you start, but you shouldn't have to. If the code inside the if (moveFactor != 0) statement is executed something like every frame, change your code accordingly.
I've not tested this, so I'm not sure it will check for collisions. If it doesn't but you need it to, you can add that funcionality by raycasting (or box-casting, sphere-casting, or whatever kind of Collider you use):
private IEnumerator Dash(Vector2 endPosition)
{
float startingPosition = transform.position;
float elapsed = 0f;
while (elapsed < dashDuration )
{
elapsed += Time.deltaTime;
Vector2 nextPosition = Vector2.Lerp(startingPosition, endPosition, elapsed / dashDuration );
// Add the raycast here and only execute the MovePosition() if the returned RaycastHit2D's collider property is null.
// (The raycast's should extend to nextPosition.)
playerRigidbody.MovePosition(nextPosition);
yield return null;
}
}
I used from this code but it does not do it:
public Transform[] points;
public float speed;
private void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "door1")
{
transform.position = Vector3.Lerp(transform.position, points[1].position, speed * Time.deltaTime);
}
}
That is, I want the pig to go to a higher point on the ground at a desired speed when it hits the trigger (see the photo attached to see it)
Two problems here:
you are calling that line exactly once when you enter the collider so the movement is applied for I single frame!
Lerp interpolates linear between two positions using a factor between 0 and 1. You every time use the current position as start point so what would happen if if you called this continuously is approximating the position getting slower and slower every frame which is not what you describe. You want to move with a constant speed.
You most likely would rather use a Coroutine and MoveTowards for that
private void OnTriggerEnter2D(Collider2D other)
{
// Better use CompareTag here
if (other.CompareTag("door1"))
{
// Start a routine for the continuous movement
StartCoroutine(MoveTo(points[1].position, speed);
}
}
private IEnumerator MoveTo(Vector3 targetPosition, float linearSpeed)
{
// This uses an approximation of 0.00001 for equality
while(transform.position != targetPosition)
{
// with a constant speed of linearSpeed Units / second
// move towards the target position without overshooting
transform.position = Vector3.MoveTowards(transform.position, targetPosition, linearSpeed * Time.deltaTime);
// Tell Unity to "pause" the routine here, render this frame
// and continue from here in the next frame
yield return null;
}
// to be sure to end up with exact values set the target position fix when done
transform.position = targetPosition;
}
Alternatively a bit more complex looking but more powerful would be to rather calculate the required time depending on the speed but still adding some smoothing like e.g.
private void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("door1"))
{
StartCoroutine (MoveTo(points[1].position, speed);
}
}
private IEnumerator MoveTo(Vector3 targetPosition, float averageSpeed)
{
// store the initial position
var from = transform.position;
// Get the expected duration depending on distance and speed
var duration = Vector3.Distance(from, targetPosition) / averageSpeed;
// This is increased over time
var timePassed = 0;
while(timePassed < duration)
{
// This linear grows from 0 to 1
var factor = timePassed / duration;
// Adds some ease-in and ease-out at beginning and end of the movement
factor = Mathf.SmoothStep(0, 1, factor);
// linear interpolate on the smoothed factor
transform.position = Vector3.Lerp(from, targetPosition, factor);
// increase by time passed since last frame
timePassed += Time.deltaTime;
// Tell Unity to "pause" the routine here, render this frame
// and continue from here in the next frame
yield return null;
}
// to be sure to end up with exact values set the target position fix when done
transform.position = targetPosition;
}
OnTriggerEnter2D is only called once on every collider enter. Given you are only moving it by speed * Time.deltaTime with Time.deltaTime being in the order of 0.008 - 0.100 it may only move slightly.
Depending on what you want, are you sure you don't want to completely move the object or alternatively set a flag that starts moving it in the update() method?
I a new here and i try to start working with Unity Engine.
Could somebody explain me, how works Quaternion.Slerp? Because I want to rotate some object in different angles 90, 180 and 270. My code you can see below. Unfortunately when I add 180 degrees, object make crazy things and than put rotation to (0, 180, 180) for this game object. I would like to get (180,0,0)
public float speed = 0.1F;
private float rotation_x;
void Update()
{
if (Input.GetButtonDown("Fire1"))
{
rotation_x = transform.rotation.eulerAngles.x;
rotation_x += 180;
}
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.Euler(rotation_x, transform.eulerAngles.y, transform.eulerAngles.z), Time.time * speed);
}
Most examples out there including Unity examples from their official website are using Lerp in the wrong way. They didn't even bother to describe how it works in the API documentation. They just starch it in the Update() function and call it a day.
Mathf.Lerp, Vector3.Lerp, and Quaternion.Slerp work by changing from one position/rotation to another with the t value(last parameter) being passed in.That t value is also know as time.
The min of the t value is 0f and the max is 1f.
I will explain this with Mathf.Lerp to make it easier to understand. The Lerp functions are all the-same for both Mathf.Lerp, Vector and Quaternion.
Remember that Lerp takes two values and returns values between them. If we have a value of 1 and 10 and we do Lerp on them:
float x = Mathf.Lerp(1f, 10f, 0f); will return 1.
float x = Mathf.Lerp(1f, 10f, 0.5f); will return 5.5
float x = Mathf.Lerp(1f, 10f, 1f); will return 10
As you can see, the t(0) returns the min of the number passed in, t(1) returns the max value passed in and t(0.5) will return mid point between the min and the max value. You are doing it wrong when you pass any t value that is < 0 or > 1. That code in you Update() function is doing just that. Time.time will increase every second and will be > 1 in a second, so you have problems with that.
It recommended to use Lerp in another function/Coroutine instead of the Updated function.
Note:
Using Lerp has a bad side of it when it comes to rotation. Lerp does not know how to rotate Object with the shortest path. So bear that in mind. For example, you have an Object with 0,0,90 position. Lets say you want to move the rotation from that to 0,0,120 Lerp can sometimes rotate left instead of right to reach that new position which means it take longer to reach that distance.
Let's say we want to make the rotation (0,0,90) from whatever the current rotation is. The code below will change the rotation to 0,0,90 in 3 seconds.
ROTATION OVER TIME:
void Start()
{
Quaternion rotation2 = Quaternion.Euler(new Vector3(0, 0, 90));
StartCoroutine(rotateObject(objectToRotate, rotation2, 3f));
}
bool rotating = false;
public GameObject objectToRotate;
IEnumerator rotateObject(GameObject gameObjectToMove, Quaternion newRot, float duration)
{
if (rotating)
{
yield break;
}
rotating = true;
Quaternion currentRot = gameObjectToMove.transform.rotation;
float counter = 0;
while (counter < duration)
{
counter += Time.deltaTime;
gameObjectToMove.transform.rotation = Quaternion.Lerp(currentRot, newRot, counter / duration);
yield return null;
}
rotating = false;
}
INCREMENTAL ANGULAR ROTATION OVER TIME:
And to just rotate the Object to 90 in z axis, the code below is a great example of that. Please understand there is a difference between moving Object to new rotational point and just rotating it.
void Start()
{
StartCoroutine(rotateObject(objectToRotate, new Vector3(0, 0, 90), 3f));
}
bool rotating = false;
public GameObject objectToRotate;
IEnumerator rotateObject(GameObject gameObjectToMove, Vector3 eulerAngles, float duration)
{
if (rotating)
{
yield break;
}
rotating = true;
Vector3 newRot = gameObjectToMove.transform.eulerAngles + eulerAngles;
Vector3 currentRot = gameObjectToMove.transform.eulerAngles;
float counter = 0;
while (counter < duration)
{
counter += Time.deltaTime;
gameObjectToMove.transform.eulerAngles = Vector3.Lerp(currentRot, newRot, counter / duration);
yield return null;
}
rotating = false;
}
All my examples are based on frame-rate of the device. You can use real-time by replacing Time.deltaTime with Time.delta but more calculation is required.
Before anything, you can't add 180 on euler angles like that, and that's mainly what is causing your problem. You'd better use quaternion directly instead, or work on the transform itself.
You can think of a quaternion as an orientation in space. In contrary to what have been said, I do recommend learning how to use them if you can. However, I don't recommend using euler angles at all... as they're suject to different writing conventions, and will fail sometimes. You can look at 'gimbal lock' if you want details about that.
Simply a slerp or lerp (standing for spherical linear interpolation, or linear interpolation respectively) is a way to interpolate (go from one orientation to another, by increasing t from 0 to 1, in a coroutine or anywhere else) between orientation A and B. The difference between the two is that the slerp is giving you the shortest path from A to B.
In the end, when t = 1, lerp(A,B,t) and slerp(A,B,t) will give you B.
In your case, if you want to instantly rotate an object in space to a specific orientation, I suggest you use Quaternion.AngleAxis which is the most forward way to describe mathematically a quaternion.
If you want to add a rotation, say 90° to you actual orientation (without animation between the two), you can do something like this :
transform.rotation *= Quaternion.AngleAxis(axis_of_rotation, angle)
or use transform.rotate (depending on the parameters, it can be a right multiply, or left : local, or world transform).
Programmers' answer is detailling how to animate your transform. But I do suggest you to investigate quaternion themselves, as it will give you global understanding of space transforms.
I a new here and i try to start working with Unity Engine.
Could somebody explain me, how works Quaternion.Slerp? Because I want to rotate some object in different angles 90, 180 and 270. My code you can see below. Unfortunately when I add 180 degrees, object make crazy things and than put rotation to (0, 180, 180) for this game object. I would like to get (180,0,0)
public float speed = 0.1F;
private float rotation_x;
void Update()
{
if (Input.GetButtonDown("Fire1"))
{
rotation_x = transform.rotation.eulerAngles.x;
rotation_x += 180;
}
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.Euler(rotation_x, transform.eulerAngles.y, transform.eulerAngles.z), Time.time * speed);
}
Most examples out there including Unity examples from their official website are using Lerp in the wrong way. They didn't even bother to describe how it works in the API documentation. They just starch it in the Update() function and call it a day.
Mathf.Lerp, Vector3.Lerp, and Quaternion.Slerp work by changing from one position/rotation to another with the t value(last parameter) being passed in.That t value is also know as time.
The min of the t value is 0f and the max is 1f.
I will explain this with Mathf.Lerp to make it easier to understand. The Lerp functions are all the-same for both Mathf.Lerp, Vector and Quaternion.
Remember that Lerp takes two values and returns values between them. If we have a value of 1 and 10 and we do Lerp on them:
float x = Mathf.Lerp(1f, 10f, 0f); will return 1.
float x = Mathf.Lerp(1f, 10f, 0.5f); will return 5.5
float x = Mathf.Lerp(1f, 10f, 1f); will return 10
As you can see, the t(0) returns the min of the number passed in, t(1) returns the max value passed in and t(0.5) will return mid point between the min and the max value. You are doing it wrong when you pass any t value that is < 0 or > 1. That code in you Update() function is doing just that. Time.time will increase every second and will be > 1 in a second, so you have problems with that.
It recommended to use Lerp in another function/Coroutine instead of the Updated function.
Note:
Using Lerp has a bad side of it when it comes to rotation. Lerp does not know how to rotate Object with the shortest path. So bear that in mind. For example, you have an Object with 0,0,90 position. Lets say you want to move the rotation from that to 0,0,120 Lerp can sometimes rotate left instead of right to reach that new position which means it take longer to reach that distance.
Let's say we want to make the rotation (0,0,90) from whatever the current rotation is. The code below will change the rotation to 0,0,90 in 3 seconds.
ROTATION OVER TIME:
void Start()
{
Quaternion rotation2 = Quaternion.Euler(new Vector3(0, 0, 90));
StartCoroutine(rotateObject(objectToRotate, rotation2, 3f));
}
bool rotating = false;
public GameObject objectToRotate;
IEnumerator rotateObject(GameObject gameObjectToMove, Quaternion newRot, float duration)
{
if (rotating)
{
yield break;
}
rotating = true;
Quaternion currentRot = gameObjectToMove.transform.rotation;
float counter = 0;
while (counter < duration)
{
counter += Time.deltaTime;
gameObjectToMove.transform.rotation = Quaternion.Lerp(currentRot, newRot, counter / duration);
yield return null;
}
rotating = false;
}
INCREMENTAL ANGULAR ROTATION OVER TIME:
And to just rotate the Object to 90 in z axis, the code below is a great example of that. Please understand there is a difference between moving Object to new rotational point and just rotating it.
void Start()
{
StartCoroutine(rotateObject(objectToRotate, new Vector3(0, 0, 90), 3f));
}
bool rotating = false;
public GameObject objectToRotate;
IEnumerator rotateObject(GameObject gameObjectToMove, Vector3 eulerAngles, float duration)
{
if (rotating)
{
yield break;
}
rotating = true;
Vector3 newRot = gameObjectToMove.transform.eulerAngles + eulerAngles;
Vector3 currentRot = gameObjectToMove.transform.eulerAngles;
float counter = 0;
while (counter < duration)
{
counter += Time.deltaTime;
gameObjectToMove.transform.eulerAngles = Vector3.Lerp(currentRot, newRot, counter / duration);
yield return null;
}
rotating = false;
}
All my examples are based on frame-rate of the device. You can use real-time by replacing Time.deltaTime with Time.delta but more calculation is required.
Before anything, you can't add 180 on euler angles like that, and that's mainly what is causing your problem. You'd better use quaternion directly instead, or work on the transform itself.
You can think of a quaternion as an orientation in space. In contrary to what have been said, I do recommend learning how to use them if you can. However, I don't recommend using euler angles at all... as they're suject to different writing conventions, and will fail sometimes. You can look at 'gimbal lock' if you want details about that.
Simply a slerp or lerp (standing for spherical linear interpolation, or linear interpolation respectively) is a way to interpolate (go from one orientation to another, by increasing t from 0 to 1, in a coroutine or anywhere else) between orientation A and B. The difference between the two is that the slerp is giving you the shortest path from A to B.
In the end, when t = 1, lerp(A,B,t) and slerp(A,B,t) will give you B.
In your case, if you want to instantly rotate an object in space to a specific orientation, I suggest you use Quaternion.AngleAxis which is the most forward way to describe mathematically a quaternion.
If you want to add a rotation, say 90° to you actual orientation (without animation between the two), you can do something like this :
transform.rotation *= Quaternion.AngleAxis(axis_of_rotation, angle)
or use transform.rotate (depending on the parameters, it can be a right multiply, or left : local, or world transform).
Programmers' answer is detailling how to animate your transform. But I do suggest you to investigate quaternion themselves, as it will give you global understanding of space transforms.