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.
Related
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
}
}
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);
}
So, here is what i have:
SteamVR's hand gameobject
3D sphere.
what i want:
The sphere to move to same direction/position as the hand does, but it to move further with a multiplier. E.g. i move the VR controller and the hand moves 1 unit. I want that the sphere moves to the same direction in a same amount of time but e.g. 2 units. how do i do this?
i tried simple
sphere.transform.position = ControllerToFollow.position +2f;
but then the sphere is always offset.
position is a Vector3, which is essentially 3 floats - you can't plus a Vector3 with a float unless you overload the + operator. Otherwise what you can do is the following:
Vector3 followPos = new Vector3(ControllerToFollow.position.x + 2f,
ControllerToFollow.position.y + 2f,
ControllerToFollow.position.z + 2f);
sphere.transform.position = followPos;
If you only want it to follow on one axis, then you can do the following:
Vector3 followPos = new Vector3(ControllerToFollow.position.x + 2f, // Follow on x Axis
ControllerToFollow.position.y, // Y axis is the same
ControllerToFollow.position.z); // X Axis is the same
sphere.transform.position = followPos;
Edit: I think I understand your problem better now. Here's a better version.
if (Vector3.Distance(sphere.transform.position, ControllerToFollow.position) >= 2f)
{
// Code that makes the sphere follow the controlling
}
Just track the movement Delta of the hand and multiply it by a certain multiplier.
At the beginning of the manipulation store
private Vector3 lastControllerPosition;
...
lastControllerPosition = ControllerToFollow.position;
then in every frame compare
var delta = ControllerToFollow.position - lastHandPosition;
// Don't forget to update lastControllerPosition for the next frame
lastControllerPosition = ControllerToFollow.position;
Now in delta you have a movement of the controller since the last frame. So you can assign it to the sphere with a multiplier using Transform.Translate
sphere.transform.Translate(delta * multiplier, Space.World);
or simply using
sphere.transform.position += delta * multiplier;
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);
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;