I have a very simple script that I wish to rotate something on the X-axis, and I'd expect the other two axes to stay put(they flip between 0 and 180), and only X to change.
Below you can see the code intended to do just that.
public class TestScript : MonoBehaviour
{
private void Start()
{
transform.eulerAngles = Vector3.zero;
Debug.Log($"start rotation: {transform.eulerAngles}");
}
private void Update()
{
float time = (Time.time - 1f) * 10f;
if (time < 0f || time > 1f)
{
return;
}
Vector3 rotation = transform.eulerAngles;
float x = Mathf.Lerp(170f, 0, time);
rotation.x = x;
transform.eulerAngles = rotation;
Debug.Log($"rotation: {transform.eulerAngles}, x: {x}");
}
}
The output from the console. You can clearly see that the rotation does not go from 170 to 0, but from 0 to 90 and then back to 0.
Now, I'm pretty sure this has something to do with quaternions and their identity, but not sure how can this be avoided
PS: The same idea but for Y and Z works just fine.
OP:
I have a very simple script that I wish to rotate something on the X-axis, and I'd expect the other two axes to stay put(they flip between 0 and 180), and only X to change.
You can clearly see that the rotation does not go from 170 to 0, but from 0 to 90 and then back to 0. Now, I'm pretty sure this has something to do with quaternions
Well the real culprit is Euler angles.
If we take a look at your code:
Vector3 rotation = transform.eulerAngles;
float x = Mathf.Lerp(170f, 0, time);
rotation.x = x;
transform.eulerAngles = rotation;
Debug.Log($"rotation: {transform.eulerAngles}, x: {x}");
...we can see you are performing rotations via transform.eulerAngles. The thing about 3D rotations is that you should avoid using Euler due to their limitations and problems (gimbal lock anyone) and use quaternions instead. The latter is the source of truth.
Unity (my emphasis):
When you read the .eulerAngles property, Unity converts the Quaternion's internal representation of the rotation to Euler angles. Because, there is more than one way to represent any given rotation using Euler angles, the values you read back out may be quite different from the values you assigned. This can cause confusion if you are trying to gradually increment the values to produce animation.
...which is exactly what is happening with your code.
Consider this:
Notice anything about the 23.5 and the 156.5?
23.5 + 156.5 = 180
In other words both will lead to the same rotation as per "there is more than one way to represent any given rotation".
An arguable simpler approach is:
public class RotateWithTime : MonoBehaviour
{
[SerializeField,Tooltip("Rotation rate in degrees/second")]
private Vector3 rotationSpeed; // e.g. (30,0,0) for 30 deg/sec X-only
private void Reset()
{
rotationSpeed = Vector3.zero;
}
// Update is called once per frame
void Update()
{
var amount = rotationSpeed * Time.deltaTime;
transform.Rotate(amount);
}
}
And a version without Vector3s:
public class RotateWithTimeNoV3 : MonoBehaviour
{
[SerializeField,Tooltip("Rotation rate in degrees/second")]
private float rotationSpeedX; // e.g. 30 for 30 deg/sec X-only
private void Reset()
{
rotationSpeedX = 0f;
}
// Update is called once per frame
void Update()
{
var amount = rotationSpeedX * Time.deltaTime;
transform.Rotate(amount, 0f, 0f);
}
}
In order to avoid this question becoming a chameleon question due to the lack of info in the initial question, I have asked a new one and will be closing this one. You can find it here
Related
I am moving a rigidbody using rb.AddForce(force,ForceMode.Impulse) where force is the target position the rigidbody have to reach.
Now the speed it goes directly depends on the distance it has to cover.
Let's say the time taken to reach the target position is 3sec. I need the rigidbody to cover the same target pos in 5sec.
I dont want to change the timescale as it affects my gameflow
On Changing the velocity of rigidbody it fails to reach the target position
Some basic physics/math:
velocity = change-in-position / travel-time
force = mass * change-in-velocity / acceleration-time
For ease, we're going to call change-in-position as distance, and change-in-velocity/acceleration-time as acceleration
Now, since the acceleration-time component is effectively zero because you're using Impulse, we're going to remove it from the equation (in math terms, we set it at '1')
force = mass * change-in-velocity
Assuming your object starts at zero velocity, we can simplify change-in-velocity to just velocity
force = mass * velocity
force = mass * distance / travel-time
To bring that back into Unity code:
var mass = rb.mass;
var distance = destination.position - transform.position;
var travelTime = 5f; // seconds
var force = mass * distance / travelTime;
rb.AddForce(force, ForceMode.Impulse);
Note that this assumes a frictionless transfer and constant velocity.
If you ignore gravity, this code solves the problem, here I changed the drag according to weight and distance, it may be a little bit away from the destination at the end, the reason should be higher drag friction.
public void ForceToTarget(Transform target, float time = 1f)
{
var rb = GetComponent<Rigidbody>();
var vector = target.position - transform.position;
var distance = vector.magnitude;
rb.drag = distance/time;
rb.AddForce(vector*rb.mass*distance/time, ForceMode.Impulse);
}
If you want precise control over your speed, then stop using ForceMode.Impulse because other physics effects like drag will make your answers wrong. Instead, just set the speed yourself. You can do this with a Coroutine to control timing and ForceMode.VelocityChange to control the speed. Basically, just look at where you are, where the target is, how much time is left, and apply the speed directly.
private bool canMove = true;
public void MoveTo(Vector3 targetPosition, float targetTime)
{
if(canMove)
{
StartCoroutine(MoveToCoroutine(targetPosition,targetTime));
}
}
private IEnumerator MoveToCoroutine(Vector3 targetPosition, float time)
{
canMove = false;
while(time > 0)
{
var positionDelta = transform.position - targetPosition;
var targetSpeed = positionDelta / time;
var speedDelta = targetSpeed - rb.velocity;
rb.AddForce(speedDelta , ForceMode.VelocityChange);
yield return null;
time -= Time.deltaTime;
}
// Bring the object to a stop before fully releasing the coroutine
rb.AddForce(-rb.velocity, ForceMode.VelocityChange);
canMove = true;
}
I wrote this here into the text editor, no IDE and haven't tested it, but I'm pretty sure this'll do what you want.
Assuming you're using the target position as-is then larger vectors will cause larger force to be applied than smaller vectors. Similarly, if using a direction vector as-is then as the rb gets closer to the target the magnitute of the vector gets smaller and thus less force is applied.
To get a constant speed use the direction to the target and Normalise it instead. Regardless of the distance the direction vector will always have a magnitude of 1 so you can multiply it by any value to accurately control the speed of the object:
Rigidbody rb;
public Transform target;
public float dist;
public float speed = 2f; // or whatever
public float targetDistance = 40f; // or whatever
private void Start()
{
rb = GetComponent<Rigidbody>();
StartCoroutine("IMove");
}
IEnumerator IMove()
{
dist = Vector3.Distance(transform.position, target.position);
while (dist > targetDistance)
{
dist = Vector3.Distance(transform.position, target.position);
rb.AddForce(Vector3.Normalize(target.position - transform.position) * speed, ForceMode.Impulse);
yield return new WaitForFixedUpdate();
}
}
Without getting too much into the physics and maths, if you want it to travel slower but the same distance you need to reduce the gravity on it and the initial force.
Note in this example I am assuming the weight is 1 to make the calculation a bit easier for force.
public class TrevelSpeedAdjusted
{
public float speedFactor = 1;
void FixedUpdate()
{
// Reduce the gravity on the object
rb.AddForce(-Physics.gravity * rigidbody.mass * (1 - speedFactor));
}
public float AddAdjustedForce(Vector3 force, ForceMode forceMode)
{
rb.AddForce(force * speedFactor, forceMode);
}
}
So you can try DoTween package to do this pretty easily and its very convenient to use a package instead of using Unity's inbuilt system.
With doTween use this:
DOMove(Vector3 to, float duration, bool snapping) condition to tween your physics Gameobject to a given target position in the duration you require.
Here's documentation you can refer to if you want: http://dotween.demigiant.com/documentation.php
Let me give you an example:
Install the doTween Package. http://dotween.demigiant.com/download
Import it to unity.
Go to your script where you want to achieve the functionality you mentioned on your question and add this header "using DG.Tweening".
Now get access of your RigidBody.
For Example Lets say: I have a cube gameobject with rigidbidy and this script attached.
The Cube Initial Position is at 0,0,0.
And I want it to move to 5,5,5 in 3 seconds or 5 seconds as per your questions request. And lets say I want this to happen when I click SpaceBar on keyboard.
So I would simply do.
Rigidbody rb;
void Start()
{
rb= GetComponent<Rigibody>();
}
void Update()
{
if(Input.GetButtonDown(Keycode.Space))
{
MoveCube(new Vector3(5,5,5),5);
}
}
void MoveCube(Vector3 inTargetPosition , float durationToReachTheTarget)
{
//What this line does is first take in the target position you want your physics object to reach as it first parameter, Then takes in the duration in which you want it to reach there.
rb.DoMove(inTargetPosition,durationToReachTheTarget);
}
This should help you. But remember this is only if you okay with adding an extra package. Personally this package is very good and I would recommend you this.
i am trying to rotate cube smoothly to 90 degrees every time i press space key. here in my code every time i decrease speed to less than 1 its rotation is not consistent at 90 decrease and speed at anything more than 1 its rotating instantly not smoothly. Here is my code
Vector3 to = new Vector3(0, 0, 90);
public float speed = 0.5f;
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
RotateOne();
}
}
void RotateOne()
{
transform.eulerAngles = Vector3.Lerp(transform.rotation.eulerAngles, to, speed * Time.deltaTime);
to += new Vector3(0, 0, 90);
}
You almost had it ;)
The main issue is that you only rotate once a tiny little bit when you click the key.
You rather want to rotate continously and only increase the target rotation once when you click.
A second issue is you using eulerAngles for a continuous rotation. From the API:
When using the .eulerAngles property to set a rotation, it is important to understand that although you are providing X, Y, and Z rotation values to describe your rotation, those values are not stored in the rotation. Instead, the X, Y & Z values are converted to the Quaternion's internal format.
When you read the .eulerAngles property, Unity converts the Quaternion's internal representation of the rotation to Euler angles. Because, there is more than one way to represent any given rotation using Euler angles, the values you read back out may be quite different from the values you assigned. This can cause confusion if you are trying to gradually increment the values to produce animation.
To avoid these kinds of problems, the recommended way to work with rotations is to avoid relying on consistent results when reading .eulerAngles particularly when attempting to gradually increment a rotation to produce animation. For better ways to achieve this, see the Quaternion * operator.
// In general instead of eulerAngles always prefer calculating with
// Quaternion directly where possible
private Quaternion to;
void Start()
{
to = transform.rotation;
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
RotateOne();
}
// You want to do this always, not only in the one frame the key goes down
// Rather use RotateTowards for a linear rotation speed (angle in degrees per second!)
transform.rotation = Quaternion.RotateTowards(transform.rotation, to, speed * Time.deltaTime);
// Or if you still rather want to interpolate
//transform.rotation = Quaternion.Lerp(transform.rotation, to, speed * Time.deltaTime);
}
void RotateOne()
{
to *= Quaternion.Euler(0, 0, 90);
}
NOTE though there will be one little issue with this: The moment you hit the key 3 or 4 times it will suddenly rotate back! This is because RotateTowards and Lerp use both the shortest way towards the target rotation.
In order to fully avoid this in your case you could rather use a Corotuine and stack your inputs like e.g.
private int pendingRotations;
private bool isRotating;
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
pendingRotations++;
if(!isRotating) StartCoroutine(RotateRoutine());
}
}
IEnumerator RotateRoutine()
{
// just in case
if(isRotating) yield break;
isRotating = true;
var targetRotation = transform.rotation * Quaternion.Euler(0, 0, 90);
while (transform.rotation != targetRotation)
{
transform.rotation = Quaternion.RotateTowards(startRotation, targetRotation, speed * Time.deltaTime);
// tells Unity to "pause" the routine here, render this frame
// and continue from here in the next fame
yield return null;
}
// in order to end up with a clean value
transform.rotation = targetRotation;
isRotating = false;
pendingRotations--;
// are there more rotations pending?
if (pendingRotations > 0)
{
// start another routine
StartCoroutine(RotateRoutine());
}
}
Quaternion to = Quaternion.Euler(0,0,90);
transform.rotation = Quaternion.Lerp(transform.rotation, to, speed * Time.deltaTime);
Don't change to and add Time.deltaTime
I have a script where I wish to rotate something on the X-axis, and I'd expect the other two axes to stay put and not be modified(they flip between 0 and 180), and only X to change.
Below you can see the code intended to do just that.
public class TestScript : MonoBehaviour
{
private void Start()
{
//this line is meant to show that I'm reseting the rotation before starting
transform.eulerAngles = Vector3.zero;
Debug.Log($"start rotation: {transform.eulerAngles}");
}
private void Update()
{
float time = (Time.time - 1f) * 10f;
if (time < 0f || time > 1f)
{
return;
}
Vector3 rotation = transform.eulerAngles;
float x = Mathf.Lerp(170f, 0, time);
rotation.x = x;
transform.eulerAngles = rotation;
Debug.Log($"rotation: {transform.eulerAngles}, x: {x}");
}
}
The output from the console. You can clearly see that the rotation does not go from 170 to 0, but from 0 to 90 and then back to 0.
Now, I'm pretty sure this has something to do with quaternions and their identity, but not sure how can this be avoided
PS: Before you answer please read this
I do not want to rotate the whole object by using a Vector. I only want to rotate one axis at a time.
I know that this is due to the fact that quaternions represent a rotation, and there are multiple ways to represent a rotation when converting into euler angles, but that doesn't help me, because I do only want to do one axis rotation and the others not to be modified. At all.
I'm actually trying to do this for the other 2 axes too.
The rotation has to be from one value to another during a specific period of time, rather than rotate a certain amount over an unspecified amount of time.
This script is not actually what I'm trying to achieve but a simplified version of my issue. If any of this is not that clear, check these 2 scripts, where I'm actually trying to achieve this. Abstract Axis Rotate
I would store a Vector3 where all 1-3 TestScript varieties can view and edit it, such that no Transform has more than one of these vector3 associated with it. This could be stored in a dictionary, for instance, where each Transform could be used as a key. There are other ways this could be done but that is out of scope. I'll call the value of this vector3 cache for the sake of simplicity.
You will also need a way for each script to determine which components of the euler angles are not controlled. This can be done in a manner similar to cache and how to implement is also out of scope for this question.
Have each TestScript update cache so that the vector component it controls is set to its current interpolation value. Then have each component assign that cache to the eulerAngles, or optionally, have only the final TestScript make that assignment.
In terms of your question, a good one btw, I'd say 181, 0, 360. I don't really care what you do with the other axes and if your quaternion messes up the rotation.
Since you would want to have this internal state vector not override uncontrolled vector components in the event any outside change occurs, here's what you could do about that:
When the first TestScript variety in a frame executes, compare (using e.g. Quaternion.Angle(a,b) < 0.00001f so that it treats 540,0,0 the same as 180,0,0) the transform's current rotation with the quaternion produced by Quaternion.Euler(cache). If the rotations are determined to be different, assign the uncontrolled components of the transform's eulerAngles to cache and then continue with the usual operation.
Pseudocode, underscore_case stuff is VERY pseudo:
Transform myTransform;
Vector3 startEulers = myTransform.eulerAngles;
Vector3 cache = get_cache(myTransform);
if (is_this_TestScript_first_to_go_this_frame())
{
if (Quaternion.Angle(myTransform.rotation, Quaternion.Euler(cache)) < 0.0001f)
{
if (is_x_uncontrolled(myTransform))
cache.x = startEulers.x;
if (is_y_uncontrolled(myTransform))
cache.y = startEulers.y;
if (is_z_uncontrolled(myTransform))
cache.z = startEulers.z;
set_cache(myTransform, cache);
}
}
switch(my_axis)
{
case X:
cache.x = get_current_interp_val();
break;
case Y:
cache.y = get_current_interp_val();
break;
case Z:
cache.z = get_current_interp_val();
break;
default:
throw exception;
}
myTransform.eulerAngles = cache;
This will get you started toward what you want.
This cannot be achieved due to how Unity interprets quaternions. If you want to do this you might want to go a different route. transform.Rotate(Vector3) did the trick for me. This is not the solution I'm using but this is how the script I originally posted would have to be modified to achieve the same thing.
public class TestScript : MonoBehaviour
{
private float lastX;
private void Start()
{
transform.eulerAngles = Vector3.zero;
lastX = transform.eulerAngles.x;
Debug.Log($"start rotation: {transform.eulerAngles}");
}
private void Update()
{
float time = Time.time - 1f;
if (time < 0 || time > 1f)
{
return;
}
float x = Mathf.Lerp(0f, -170f, time);
Vector3 rotation = new Vector3(x - lastX, 0f, 0f);
lastX = x;
transform.Rotate(rotation);
Debug.Log($"rotation: {transform.eulerAngles}, x: {x}");
}
}
I'm new with Unity development and I'm having some issues rotating GameObjects.
I want to make and object rotate -90 degrees each time you call a function. At this moment I can make it rotate around its Y and Z axis and there's no trouble, however, if i change the parameters so it rotates around X it gets stuck after rotating from -90º (270º actually) to 180º.
Here's the code I'm using for testing:
public class RotateCube : MonoBehaviour
{
GameObject cube;
bool rotating;
private void Start()
{
cube = GameObject.Find("Cube");
rotating = false;
}
private void Update()
{
if (!rotating)
{
StartCoroutine(Rotate());
}
}
private IEnumerator Rotate()
{
rotating = true;
float finalAngle = SubstractNinety(cube.transform.eulerAngles.x);
while (cube.transform.rotation.eulerAngles.x != finalAngle)
{
cube.transform.rotation = Quaternion.RotateTowards(cube.transform.rotation, Quaternion.Euler(finalAngle, cube.transform.rotation.eulerAngles.y, cube.transform.rotation.eulerAngles.z), 100f * Time.deltaTime);
yield return null;
}
cube.transform.rotation = Quaternion.Euler(finalAngle, cube.transform.rotation.eulerAngles.y, cube.transform.rotation.eulerAngles.z);
yield return null;
rotating = false;
}
private float SubstractNinety(float angle)
{
if (angle < 90)
{
return 270f;
}
return angle - 90;
}
}
I'm updating all the coordinates in Quaternion.Euler in each iteration because I want the user to be able drag the object while it's rotating, but I wouldn't mind if the solution requires to define the Quaternion before the loop.
Why do you bother to go through the eulerAngles at all?
When using the .eulerAngles property to set a rotation, it is important to understand that although you are providing X, Y, and Z rotation values to describe your rotation, those values are not stored in the rotation. Instead, the X, Y & Z values are converted to the Quaternion's internal format.
When you read the .eulerAngles property, Unity converts the Quaternion's internal representation of the rotation to Euler angles. Because, there is more than one way to represent any given rotation using Euler angles, the values you read back out may be quite different from the values you assigned. This can cause confusion if you are trying to gradually increment the values to produce animation.
To avoid these kinds of problems, the recommended way to work with rotations is to avoid relying on consistent results when reading .eulerAngles particularly when attempting to gradually increment a rotation to produce animation. For better ways to achieve this, see the Quaternion * operator.
Rather directly use Quaternion! Simply add the desired rotation to the exsting one using the * operator
private IEnumerator Rotate()
{
rotating = true;
// This returns a new Quaternion rotation which is the original
// rotation and then rotated about -90° on the global X axis
var finalRotation = currentRotation * Quaternion.Euler(-90, 0, 0);
// simply directly use Quaternion comparison
while (cube.transform.rotation != finalRotation)
{
cube.transform.rotation = Quaternion.RotateTowards(cube.transform.rotation, finalRotation, 100f * Time.deltaTime);
yield return null;
}
cube.transform.rotation = finalRotation;
rotating = false;
}
I'm developing a Unity tetrominoes game in C#. Each tetromino has a unique label or image and a unique proper landing place. If it lands in the wrong location, I'm using the following to move it to its proper destination before spawning the next piece:
void MoveMisplacedShapeToCorrectLocation(Shape shape, Vector3 vector3)
{
float speed = 15;
Vector3 targetPosition = m_gameBoard.m_dictionaryOfProperLocations[shape.name];
Vector3 currentPosition = shape.transform.position;
// First, check to see if the shape is close enough to the target
if (Vector3.Distance(currentPosition, targetPosition) > .02f)
{
Vector3 directionOfTravel = targetPosition - currentPosition;
// Normalize the direction, since we only want the direction information, not the magnitude.
directionOfTravel.Normalize();
// Scale the movement on each axis by the directionOfTravel vector components
shape.transform.Translate(
(directionOfTravel.x * speed * Time.deltaTime),
(directionOfTravel.y * speed * Time.deltaTime),
(directionOfTravel.z * speed * Time.deltaTime),
Space.World);
}
else
{
isMisplaced = false;
}
}
In my update function, I have the following:
// Update is called once per frame
void Update () {
PlayerInput();
if (isMisplaced == true)
{
MoveMisplacedShapeToCorrectLocation(m_activeShape, transform.position);
}
}
This works fairly well, but some of the shapes jitter vigorously for up to a few seconds when they reach their proper place before settling in, whereas others settle as smoothly as if they snapped in. I don't understand why. I've varied the speed and that doesn't seem to have much effect. I've also varied the distance (0.02 above) in the if-condition and that has some effect. But I want to keep that distance small (>= 0.03), otherwise when all the shapes are in their proper locations, too much looks "gap-toothed."
Is there anything that can be done to suppress the jitters?
UPDATE as I've attempted to follow Ron's advice in the initial comment:
Following Ron's advice in the initial comment below, here's what I've tried using Vector3.MoveTowards:
void MoveMisplacedShapeToCorrectLocation(Shape shape)
{
float speed = 15;
float step = speed * Time.deltaTime; // The step size is equal to speed times the frame time.
Vector3 targetPosition = m_gameBoard.m_dictionaryOfProperLocations[shape.name];
Vector3 currentPosition = shape.transform.position;
// Check if the shape is unacceptably far from the target position:
if (Vector3.Distance(currentPosition, targetPosition) > .02f)
{
Debug.LogWarning("Vector3.Distance");
Debug.LogWarning(Vector3.Distance(currentPosition, targetPosition));
Debug.LogWarning("step");
Debug.LogWarning(step);
// Move our position a step closer to the target.
currentPosition = Vector3.MoveTowards(currentPosition, targetPosition, step);
}
else
{
isMisplaced = false;
}
}
And I've slightly modified my update function to the following (I'm only using one parameter-argument pair rather than two now. In my initial post, the second parameter-argument pair were unneeded.):
// Update is called once per frame
void Update () {
PlayerInput();
if (isMisplaced == true)
{
MoveMisplacedShapeToCorrectLocation(m_activeShape);
}
}
This is not working, however. The mis-landed shape just sits where it landed. I would be grateful for you help. Thanks.
I got this working using Vector3.MoveTowards and without assigning shape.transform.position to a new variable "currentPosition" (contrary to the code I posted in the UPDATE to the original question above) as follows:
void MoveMisplacedShapeToCorrectLocation(Shape shape)
{
float speed = 10;
float step = speed * Time.deltaTime; // The step size is equal to speed times the frame time.
Vector3 targetPosition = m_gameBoard.m_dictionaryOfProperLocations[shape.name];
// Check if the shape is unacceptably far from the target position:
if (Vector3.Distance(shape.transform.position, targetPosition) > .02f)
{
// Move our position a step closer to the target.
shape.transform.position = Vector3.MoveTowards(shape.transform.position, targetPosition, step);
}
else
{
isMisplaced = false;
}
}
and
// Update is called once per frame
void Update () {
PlayerInput();
if (isMisplaced == true)
{
MoveMisplacedShapeToCorrectLocation(m_activeShape);
}
}
Thanks #Ron Beyer for your comment that sent me in the proper direction.