Quaternion rotation in coroutine does not work properly? - unity3d

Alright, I'm trying to make an object called 'pathblock' rotate 90 degrees clockwise every time the player presses 'E'. The player should be able to spam the button, with the block turning 360 degrees without fail.
I want to see the rotation animation, so I am using coroutine in combination with a slerp function. The coroutine is called in update, if E is pressed.
//Rotates the selected pathblock by 90 degrees over a specified time. A coroutine is necessary to render each slerp() result per seperate frame
IEnumerator RotatePathblock()
{
Debug.Log("Start rotation!");
Quaternion start = Quaternion.Euler(new Vector3(pathblock.transform.rotation.x, pathblock.transform.rotation.y, pathblock.transform.rotation.z)); //Set start variable for Slerp(), the current rotation of the pathblock
Quaternion end = Quaternion.Euler(new Vector3(start.x, start.y, start.z-90.0f));
Debug.Log(string.Format("Target Angle: {0}", end.eulerAngles.z));
float normalizationFactor = 1.0f / pathblockRotationTime; //We need to normalize time since slerp() works with values between 0-1; we can convert values by multiplying with this factor
float timePassed = 0.0f; //Time passed since the start of the linear interpolation. Starting at 0, it increases until it reaches 1. All values are rendered.
while(timePassed < 1.0f) //While the time passes is less than 1 (the maximum of a linear interpolation)
{
timePassed += Time.deltaTime * normalizationFactor; //Increase the timePassed with the time passed since the last frame; the time is first normalized
pathblock.transform.rotation = Quaternion.Slerp(start, end, timePassed); //Set the pathblock rotation to a new value defined by linear interpolation
yield return null; //Stop the function, finish Update() and return to this while loop; this will cause all slerp() values to render, resulting in a smooth animation
}
}
The first press turns the pathblock 90 degrees, as expected.
The second press sets the pathblock back to its original rotation, and turns it 90 degrees again.
This leads me to believe that the 'start' variable never changes, even though it should set itself to the new pathblock.transform.rotation when the function is called, getting the new rotation.
If anyone could look at what's wrong, I'd appreciate it.
Any other critique on my code, comments etc. would also be appreciated!

Quaternion start = Quaternion.Euler(new Vector3(pathblock.transform.rotation.x, pathblock.transform.rotation.y, pathblock.transform.rotation.z)); //Set start variable for Slerp(), the current rotation of the pathblock
Should be:
Quaternion start = Quaternion.Euler(new Vector3(pathblock.transform.eulerAngles.x, pathblock.transform.eulerAngles.y, pathblock.transform.eulerAngles.z)); //Set start variable for Slerp(), the current rotation of the pathblock

Related

Unity make Joystick always result in speed 1

So I have a joystick object, which gives me values of -1 to 1 for each axis.
float horizontalMove = joystick.Horizontal * speed;
float verticalMove = joystick.Vertical * speed;
rb.velocity = new Vector3(horizontalMove, verticalMove, 0);
Now, what I want is that no matter how far you pull the joystick in each direction, it will always result in speed 1. Just like how my current code works, but my joystick is always pulled to the edge. I also made it so max. 1 directions can be set to 0.
You can use the .normalized property of the vector, which ensures it either has length 1, or is equal to Vector3.zero.
rb.velocity = new Vector3(horizontalMove, verticalMove, 0).normalized;
Unlike using Mathf.Sign on each axis, the angle of the vector is preserved, so the player will still be able to move in any orientation, not just along axes and diagonals.

Rotate Rigidbody in Unity starting from a value and proportionally to controller delta angle

I have a disk and I want the disk to rotate in solidarity with the rotation change of the controller, the moment I press the trigger button on the controller. And then, for the disc to stop when I release the button. If I then press the button again, the disk must rotate again, but starting from its current position, and not starting from zero.
For example, suppose that at first the controller is at zero degrees.
I press the button and rotate the controller 20 degrees --> the disk must rotate 20 degrees.
Then subsequently my controller, say, is at 30 degrees. I press the button and rotate the controller up to 70 degrees (so with the button pressed I make 40 degrees -from 30° to 70°-) --> I want the disk to rotate another 40 degrees, that is, I have to make it rotate from 20 degrees (i.e. its current rotation), to 60 degrees (i.e. 20 degrees before + 40 degrees made now with the controller).
I'm going crazy! I can make it rotate. However, I attach the source code...
A - in case A) the disk keeps the same angle as the controller. That is, the first time the disk rotates from zero to 20° (correct). But the second time it rotates from 30° to 70° (copying the same degrees of the controller, wrong!)
B - instead in case B) the disk seems to rotate as I want, except that it perpetrates the rotation in loop, so the disk instead of stopping, rotates infinitely (with a speed proportional
to the angle of the controller).
I take the screenshot of the code, which I execute in FixedUpdate().
Instead, the variable inputRotationStartRight is equal to the angle of the controller at the time I press the button and I value it in Update();
The variable _inputRotationRight.z is the real-time angle of the controller.
Ps. I need to call the method MoveRotation() because the disk has some teeth in the border and I need them to collide during the rotation.
Thank you all very much.
//Rigidbody disk ;
void FixedUpdate()
{
...
if (_isTriggerPressedRight)
{
Quaternion deltaRotation = Quaternion.Euler((_inputRotationRight.z - inputRotationStartRight) * 180, 0, 0);
//A) This way, infinite rotation
disk.MoveRotation(disk.transform.rotation * deltaRotation);
//B) This way, the disk has the identical angle of the controller
disk.MoveRotation(disk.transform.rotation * deltaRotation);
}
Here is how I set the variable inputRotationStartRight :
void Update()
{
_deviceRight.TryGetFeatureValue(CommonUsages.deviceRotation, out _inputRotationRight);
_deviceRight.TryGetFeatureValue(CommonUsages.triggerButton, out _isTriggerPressedRight);
//If I click the button for the first time then I set the "initial angle" (on click) of the controller
if (_isTriggerPressedRight && !isTriggerAlmostPressedRight) {
isTriggerAlmostPressedRight = true;
inputRotationStartRight = _inputRotationRight.z;
}
//When I unclick the button, then I unset isTriggerAlmostPressedRight
if (!_isTriggerPressedLeft) {
isTriggerAlmostPressedLeft = false;
}
if (!_isTriggerPressedRight)
{
isTriggerAlmostPressedRight = false;
}
}
You could do something like this
//i'm assuming you original angle was in radians
//if this is not the case, remove Mathf.Rad2Deg
float deltaRotationAngle = (_inputRotationRight.z - inputRotationStartRight) * Mathf.Rad2Deg;
Quaternion deltaRotation = Quaternion.Euler(deltaRotationAngle , 0, 0);
disk.transform.rotation *= deltaRotation;
Here the disk.transform.rotation *= deltaRotation is the equivalent of first rotating by the disk's original rotation, then by the deltaRotation.
If this gives you issues with precision and drift, then you could cache your angles at the start of each rotation, and add them back at the end.
I did.
void Update()
{
_deviceRight.TryGetFeatureValue(CommonUsages.deviceRotation, out _inputRotationRight); //
_deviceRight.TryGetFeatureValue(CommonUsages.gripButton, out _isGripPressedRight);
_deviceRight.TryGetFeatureValue(CommonUsages.triggerButton, out _isTriggerPressedRight);
if (_isTriggerPressedRight && !isTriggerAlmostPressedRight) {
print("BUTTON PRESSED!");
isTriggerAlmostPressedRight = true;
rotationStartDisk = disk.transform.rotation.x;
inputRotationStartRight = _inputRotationRight.z;
}
if (!_isTriggerPressedRight) {
isTriggerAlmostPressedRight = false;
}
}
void FixedUpdate()
{
if (_isTriggerPressedRight) {
Quaternion deltaRotation = Quaternion.Euler((_inputRotationRight.z - inputRotationStartRight + rotationStartDisk) * 180, 0, 0);
disk.MoveRotation(deltaRotation);
}
}
I'm starting in these days with Unity, so I keep the question opened, waiting for further answers or suggestions.

How do I make something happen at a specified rotation which I incrementally go towards and may over shoot?

So I have a script for a day/night cycle attached to the directional light in unity. It slowly rotates the light which creates an effective day/night cycle. There's an event that I want to call once every sunset, or more specifically when the x rotation of the light is at 200 degrees. The problem is my script rotates a little bit each frame, according to Time.deltatime which is obviously not perfectly consistent. Because of this, I might be at a rotation just below 199 and then at the next frame, I might be at a rotation just above 200 degrees, overshooting it so that it's never actually 200 degrees. I tried to get around this by checking if the x rotation is above 200 AND the x rotation - my rotate amount in that frame is below 200, then calling the event. That was the idea but it didn't work for some reason. It never calls the event. Here's my script.
using UnityEngine;
using UnityEngine.Events;
public class DayNightCycle : MonoBehaviour
{
public TerrainGenerator terrainGenerator;
public float dayLength = 3;
float rotationSpeed;
public UnityEvent night;
public float timeNightStarts = 200;
// Start is called before the first frame update
void Start()
{
rotationSpeed = 360 / (dayLength * 60);
}
// Update is called once per frame
void Update()
{
if (terrainGenerator.mapLoaded)
{
Vector3 rotateAmount = Vector3.right * rotationSpeed * Time.deltaTime;
transform.Rotate(rotateAmount, Space.World);
float xRotation = transform.eulerAngles.x;
if (xRotation >= timeNightStarts && xRotation - rotateAmount.x < timeNightStarts)
{
night.Invoke();
}
}
}
}
The problem you are facing is expected since Unity uses Quaternions under the hood and quaternion to euler conversions are not stable.
Quote from Unity docs:
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.
If you want to avoid Quaternions, you can represent the eulerX angle as a float variable in your code. Increment its value, always set the transform.euler.x from it, but never read it back from the transform. If no other script or physics affects your transform (which should be the case for sun) you will be fine.

How to move an object by a certain angle over a period of time in a circle [duplicate]

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.

Quaternion correction

I'm trying to implement network correction for a client simulated Rigidbody. 30 times a seconds I get the target rotation from which I calculate the rotation correction. Then I apply this correction over a number of frames.
Network update:
rotationCorrection = receivedRotation * Quaternion.Inverse(transform.rotation);
Every frame:
var a = Mathf.Min(1, Time.deltaTime * 8);
var final = Quaternion.Slerp(transform.rotation, transform.rotation * rotationCorrection, a);
var actualCorrection = final * Quaternion.Inverse(transform.rotation);
rotationCorrection *= Quaternion.Inverse(actualCorrection);
//rotationCorrection = Quaternion.Slerp(rotationCorrection, Quaternion.identity, a); // First try
_rigidbody.MoveRotation(final);
The reason I don't just interpolate the current rotation to the corrected on is the GameObject contains a Rigidbody which should simulate the object in addition to the correction.
This does work sometimes (at least the first try version), except at angles between roughly -90 and 90. I suspect its the code to slerp the rotationCorrection to identity.
(work in progress since the question was not 100% clear)
The subtraction formulae yield wrong results. Please try using euler, subtract the angles and you can make the desired quaternion.
Are you sure you don't need Lerp instead of Slerp?
Anyway final should not use Lerp or Slerp.
If as final you want transform.rotation * rotationCorrection (which is the current rotation plus the correction), then just use it as that. Multiplications actually add the rotation.
Lerp and Slerp are to be used with an a parametric to time, where a==0 will return the starting rotation, a==1 will return the final rotation. So a should be scaled with time. If, on the other hand, the network correction is exactly what you need and you get it at specific fixed-time updates, then without any Lerp, might as well try transform.rotation = transform.rotation*rotationCorrection.
rotationCorrection = receivedRotation * Quaternion.Inverse(transform.rotation);
What this does is return a rotation that starts from receivedRotation and ends to the opposite rotation of the current transform rotation. So neither the start nor the end point match with the current one.
so:
You set a final quaternion as currentRot * change
a starting as currentRot
and then update transform.rotation by making it = (S)Lerp with start, end and a parameter that equals 0 at starting time and 1 at ending time.
The network update was wrong. Corrected:
rotationCorrection = Quaternion.Inverse(transform.rotation) * receivedRotation;