Quarternions in Unity - c#

I am programming an 6DOF space-shooter in 3D with Unity and C#. I am using mouse and keyboard for the movement. With the mouse I use Quarterions for the rotation in X, Y and Z axis.
I tried the following code:
void Update()
{
float test = Input.GetAxis("Pitch");
float test2 = Input.GetAxis("Yaw");
float test3 = Input.GetAxis("Roll");
rotX += test;
Quaternion q1 = Quaternion.AngleAxis(rotX, Vector3.right);
rotY += test2;
Quaternion q2 = Quaternion.AngleAxis(rotY, Vector3.up);
roll += test3;
Quaternion q3 = Quaternion.AngleAxis(roll, Vector3.forward);
transform.rotation = q1 * q2 * q3;
}
The code works, but not for all the angles. It looks that over every 90 degrees the movement is not correct (x-axis and y-axis turn around). I expect that this code works for every angle, what is wrong in this code?

You are using absolute (world) vectors as your frame of reference. change Vector3.up to transform.up, Vector3.forward to transform.forward and Vector3.right to transform right, this way you will be rotating along local axis, and gimbal lock should be less problematic.
Order of operations will still matter, but for relatively small delta it should work as expected.
I think you are doing the right thing multiplying the quaternions before assigning the new rotation, otherwise each local vector would change with each operation - the behaviour would be slightly different

As #Zambari said, make sure you're rotating around local axes. But also, you seem to be storing the orientation in Euler angles, rotX, rotY, rotZ and then setting that as the absolute rotation. This is bad. What you want is to apply the delta/modification to the transform's stored quaternion:
void Update() {
var yaw = Quaternion.AngleAxis(Input.GetAxis("Yaw"), transform.up);
var pitch = Quaternion.AngleAxis(Input.GetAxis("Pitch"), transform.right);
var roll = Quaternion.AngleAxis(Input.GetAxis("Roll"), transform.forward);
transform.rotation = transform.rotation * yaw * pitch * roll;
}
note: the order of multiplication on the final line may be incorrect.
After saying all that, if all you want is to control your character, try (angles in degrees):
transform.Rotate(xAngle, yAngle, zAngle, Space.Self);

Related

How to rotate a 2D gameobject on z axes with Mathf.Clamp on 2 areas?

I want to rotate a 2D game object on the z axis. That game object should only face the Player when he is in between 2 areas. The Z-axis should be clamped between -45 to 45 and 135 to 225.
Example Image of the rotations
(The green are is where the function: LookAtTarget() should be applied to)
Right now I'm using this code to bypass the problem, but it doesn't feel very satisfying:
public virtual void LookAtTarget()
{
float rotationZ = direction.transform.rotation.z;
direction.transform.right = target.position - transform.position;
if (rotationZ > -0.25F && rotationZ < 0.25F || rotationZ > 0.975F)
{
transform.right = target.position - transform.position;
}
Debug.Log(rotationZ);
}
I'm using this type of code because when I'm talking to eulerAngles I can't clamp the value between -45 and 45 and because -45 its like 315 degree for unity, so I can't go into the negative numbers. Also I'm declearing the rotation from another game object that should tell me when the Player is in the specific range.
I hope i could explain my problem. TY
Transform.rotation is the rotation of the transform expressed as a Quaternion. To get the rotation around the z-axis, use direction.transform.eulerAngles.z. Always use eulerAngles unless you understand Quaternions.
I cannot understand what your code is doing, but here is an idea to clamp rotation. This code is untested. The key is Mathf.DeltaAngle(a, b) which returns the shortest angle (range of [-180, 180]) from a to b, even if the angles wrap around.
float maxAngle = 45f;//insert your max/min angles
float minAngle = -45f;
float midAngle = (maxAngle + minAngle) / 2f;//convert max/min angles to a center angle and how far from the center angle
float angleRange = maxAngle - midAngle;
Vector2 targetRelativePos = target.position - transform.position;
float targetAngle = Mathf.Atan2(targetRelativePos.y, targetRelativePos.z) * Mathf.Rad2Deg;//make sure everything is in degrees
float targetAngleDifference = Mathf.DeltaAngle(midAngle, targetAngle);
if(Mathf.Abs(targetAngleDifference) > angleRange){
//outside of min/max angles
if(targetAngleDifference > 0){
//your angle is too big, consider setting it to maxAngle
}else{
//your angle is too small, consider setting it to minAngle
}
}

Unity3D Adjusting CUBE "endRotation" on right ( x or z) axis

I am new in Unity3D scripting especially when it comes to specifics of Transform, Quaternions, Vector3.
Down there is a script. When I roll the cube it flips in the right direction. However when cubes' local axis doesn't match the world axis the cube's endRotation is performed on the wrong axis.
Can anybody help me to resolve this issue : "endRotation" would flip the cube on right axis regardless of the relation between LOCAL axis <=> WorldAxis.
I was trying to resolve it for a week. Of cause no success.
Further down there code and video.
if (Input.GetAxis("Horizontal") > buttonDown)
{
StartCoroutine(FlipTheCube(Vector3.right));
return
}...
.
.
.
public IEnumerator FlipTheCube(Vector3 direction)
{
startFliping = false;
float rollStartTime = 0;
float rollAngle = 90;
float halfWidth = transform.localScale.z / 2;
Vector3 pointAround = transform.position + (Vector3.down * halfWidth) + (direction * halfWidth);
Vector3 rollAxis = Vector3.Cross(Vector3.up, direction);
Quaternion rotation = transform.rotation;
Quaternion **endRotation** = rotation * Quaternion.Euler(rollAxis * rollAngle);
Vector3 endPlacement = transform.position + direction;
float oldAngle = 0;
while (rollStartTime < rollDurtnTime)
{
yield return new WaitForEndOfFrame();
rollStartTime += Time.deltaTime;
float newAngle = (rollStartTime / rollDurtnTime) * rollAngle;
float rotateThrough = newAngle - oldAngle;
oldAngle = newAngle;
transform.RotateAround(pointAround, rollAxis, rotateThrough);
}
transform.position = endPlacement;
transform.rotation = **endRotation**;
startFliping = true;
}
Here is a link to the youtube video
First part of video If cubes local axis is matching world axis the "endRotation" is flipping correctly Second part of video as soon cubes local axis is not matching world axis "endRotation" goes wrong.
Your problems lie within these lines:
Vector3 rollAxis = Vector3.Cross(Vector3.up, direction);
Quaternion rotation = transform.rotation;
Quaternion endRotation = rotation * Quaternion.Euler(rollAxis * rollAngle);
// ...
transform.RotateAround(pointAround, rollAxis, rotateThrough);
First problem is Euler vs angle/axis usage.
If you've only been rotating against Vector3.right, Vector3.forward, and their negatives you might not have even noticed this yet.
In the first part, you use rollAxis as an Euler angle representation, and in the second part as an axis.
The problem is that for a rotation, its axis is usually not the same as a Euler representation of that rotation! For example, the rotation made by rotating around (0.7, 0.0, 0.7) by 90° is completely different from an Euler rotation of (63°, 0°, 63°)!
Instead, just use rollAxis consistently as an angle/axis representation. You can get the quaternion form using Quaternion.AngleAxis.
Second problem is global vs local rotation.
This is a problem whose effects you're definitely already noticing.
In the first part, you apply rollAxis as a local rotation. This is because rollAxis is the second term of the * operator.
In the second part, RotateAround rotates around a global axis defined by rollAxis.
Keep it global or local for both. Global is simpler, and it appears that's what you're trying to do (judging from transform.position + direction), so you should have rotation as the second term of the * operator.
Altogether, the problems can be fixed by just changing the Quaternion endRotation = rotation * Quaternion.Euler(rollAxis * rollAngle); line:
Vector3 rollAxis = Vector3.Cross(Vector3.up, direction);
Quaternion rotation = transform.rotation;
Quaternion endRotation = Quaternion.AngleAxis(rollAngle, rollAxis) * rotation;
// ...
transform.RotateAround(pointAround, rollAxis, rotateThrough);
See here for more information about the quaternion * operator.
I suggest you using DoTween asset (https://assetstore.unity.com/packages/tools/animation/dotween-hotween-v2-27676):
DORotate(Vector3 to, float duration);
Using tweeners will save you a lot of time writing your own animations.

When rotating an object in Unity, the script doesn't rotate the object on the Z-axis, only the X and Y?

I have an rectangular cube that needs to be placed between two other objects, and it properly rotates so that each end faces the two objects. Since this happens on a spherical world, the issue lies when I try to then align it to be properly facing "up". I didn't realize this before, but this code (which works on the other objects as they only need to orient once) only uses the X and Y axes. I can manually orient them properly by modifying the Z-axis in the Editor, so I know it's possible, but since they must be created procedurally that obviously isn't feasible. Changing from transform.up to down or right changes absolutely nothing.
In the following code, parent is a one of the objects it has to orient towards (which it does fine), and target is the center of the sphere.
In theory, the alignment to the two side objects should only need to use the X and Y axes, and the alignment to the center of the sphere then only needs to use the Z axis as represented in the final line of code, yet in practice this doesn't work as both alignments both want to use only the X and Y axes and ignore the Z axis.
Vector3 parentDirection = parent.transform.position - transform.position;
Vector3 targetDirection = target.position - transform.position;
Vector3 DirectionToParent = Vector3.RotateTowards(transform.forward, parentDirection, 100f, 0.0f);
Vector3 CenterDiretion = Vector3.RotateTowards(transform.forward, targetDirection, 100f, 0.0f);
Quaternion ParentRotation = Quaternion.LookRotation(DirectionToParent );
Quaternion CenterRotation = Quaternion.LookRotation(CenterDiretion );
transform.rotation = Quaternion.Euler(DirectionToParent.eulerAngles.x, DirectionToParent.eulerAngles.y, newRotation.eulerAngles.z);
The code shown in the question doesn't work because it doesn't take into account the direction to/away the center of the planet when calculating transform.rotation.
Instead, I would recommend just using Quaternion.LookRotation to assign the rotation that would produce the forward you have with parentDirection, and the up closest to the up direction of the planet.
Vector3 upDirection = transform.position - target.position;
transform.rotation = Quaternion.LookRotation(parentDirection, upDirection);
And if you need to change the rotation based on some max velocity, just assign the result of Quaternion.LookRotation to a variable and Quaternion.RotateTowards from transform.rotation and assign the result to transform.rotation as necessary:
private float turnVelocity = 100f;
// ...
Vector3 upDirection = transform.position - target.position;
Quaternion goalRot = Quaternion.LookRotation(parentDirection, upDirection);
transform.rotation = Quaternion.RotateTowards(transform.rotation, goalRot,
turnVelocity * Time.deltaTime);
}

Moving a sprite using math in Unity

I am currently making a game in Unity, where characters are moving towards a point at the bottom of the screen, from a random x-position at the top of the screen. As they have to keep a constant speed (and later on possibly be able to move in specific patterns), I cannot use Vector3.Lerpfor this.
Instead, I tried using some simple math. StaticInfo.instance.GetPlayerPosition() is the targeted position. The code happens in the character's FixedUpdate().
float aVal = (myTransform.localPosition.y - StaticInfo.instance.GetPlayerPosition().y) /
(myTransform.localPosition.x - StaticInfo.instance.GetPlayerPosition().x);
float degrees = Mathf.Atan(aVal) * Mathf.Rad2Deg;
Vector3 newPos = new Vector3(myTransform.localPosition.x - (distance * speed * Mathf.Cos(degrees)),
myTransform.localPosition.y - (distance * speed * Mathf.Sin(degrees)),
1);
myTransform.localPosition = newPos;
My problem is when these characters are created (instantiated from prefab), they make a small 180-degrees loop before moving in the wanted direction. After that, they move exactly as wanted.
Is using math the best solution? And if it is, why does it do a funky drift when initialized?
This may be a more simple solution to what you are trying to achieve:
Vector3 moveDirection = (StaticInfo.instance.GetPlayerPosition() - myTransform.localPosition).normalized;
Vector3 newPos = myTransform.localPosition + moveDirection * distance * speed;
myTransform.localPosition = newPos;
If you want the object to point in the direction of the movement you can do:
myTransform.rotation = Quaternion.Euler(0, 0, Mathf.atan2(moveDirection.y, moveDirection.x)*Mathf.Rad2Deg - 90); // may not need to offset by 90 degrees here;

Translate object until collision

I want my object to keep moving in the target's direction forever or until it collides, the collision part I have already handled it; However, I am having problems with the movement part.
I first try to rotate my target using these lines of code
Vector2 diff = target - transform.position;
float angle = Mathf.Atan2(diff.y, diff.x) * Mathf.Rad2Deg;
transform.rotation = Quaternion.Euler(0.0f, 0.0f, angle);
This works perfect and my object rotates in the direction I want it to.
In my Update method I have the following
if (isMoving)
{
Vector2 f = transform.forward;
transform.position = Vector3.MoveTowards(transform.position, target + Vector3.forward, speed * Time.deltaTime);
}
Now this runs but fails to accomplish the goal and I know why, it makes sense but not sure how to fix it. The object moves to the point in the correct direction but I don't want it to stop at the target, I want it to keep going.
I also tried
rb.MovePosition(rb.position + f * Time.deltaTime * speed);
rb is a rigidbody2D
as well as
rb.AddForce(rb.position + f * Time.deltaTime * speed);
But in both cases the object rotates but never moves
I also used translate and same behavior as MovePosition
P.S. It's a 2D game
After looking over the internet, I didn't find anything that really does what I am looking for, you are not able to translate a kinematic object forever (Obviously you have to handle that the object should get destroyed at some point or something, but that's not the issue). So I went ahead and decided to write my own library to make this simple. I was able to achieve my goal using the line equation. Simple I took these steps to solve my issue.
At start I get the slope between 2 points
Calculate the y-intercept
translate the object using the line equation with a fixed X value (step), every x value find the corresponding y value and translate the object to it.
Repeat step 3 in a FixedUpdate and that's it.
Of course there is more to it, handling cases where x = 0 which will give a 0 division for the slope or y = 0, etc... I solved all of that in the library. For anyone interested you can check it out here EasyMovement
If you don't want the library then here is a simple code that will do it.
//Start by Defining 5 variables
private float slope;
private float yintercept;
private float nextY;
private float nextX;
private float step;
private Vector3 direction; //This is your direction position, can be anything (e.g. a mouse click)
In the Start Method
//step can be anything you want, how many steps you want your object to take, I prefer 0.5f
step = 0.5f;
//Calculate Slope => (y1-y2)/(x1-x2)
slope = (gameObject.transform.position.y - direction.y) / (gameObject.transform.position.x - direction.x);
//Calculate Y Intercept => y1-x1*m
yintercept = gameObject.transform.position.y - (gameObject.transform.position.x * slope);
Now in FixedUpdate
//Start by calculating the nextX value
//nextX
if (direction.x > 0)
nextX = gameObject.transform.position.x + step;
else
nextX = gameObject.transform.position.x - step;
//Now calcuate nextY by plugging everything into a line equation
nextY = (slope * nextX) + yintercept;
//Finally move your object into the new coordinates
gameObject.transform.position = Vector3.MoveTowards(gameObject.transform.position, new Vector3(nextX, nextY, 0), speed * Time.deltaTime);
Keep in mind this won't do the heavy lifting, there are a lot of conditions that needs to be taken into consideration.

Categories