Questions related to transform.rotate() in Unity Scripting - unity3d

I have four rectangular platforms put around a square platform. All other three rectangular platforms were rotated from the '0' platform. On the end of each rectangular platforms, I set up some spawn points to generate cubes with arrows sign on its face(the image of this cube is shown in the second link). I hope to rotate the cube to change the orientation of the arrow on it. But my original codes that rotating the cube do not work for platform 1 & 3, but work for platform 0&2.
Although I have found how to change it to make it works, I still cannot understand why my original codes failed. The version I used is 2019.4.18f1.
The top view of these platforms are shown below.
enter image description here
Unworkable way
dirCubeTypes is an array that stores all my different kinds of cube objects.
dirCubePoints is an array that stores all my spawn points.
The first statement works well. The problem is in the second statement.
GameObject cube = Instantiate(dirCubeTypes[Random.Range(0, 6)], dirCubePoints[Random.Range(0, 4)]);
cube.transform.Rotate(transform.forward, 90 * Random.Range(0, 4));
The figure below shows the cube generated on the platfrom 3. I want it to rotate around z-axis, but the codes above make it rotate around its x-axis instead of z-aixs. I know the third parameter is relativeTo, and the default value is Space.self. Why doesn't the codes above make it rotate along the z-axis in its own local coordinate system?
enter image description here
Workable ways
I have tried out three workable ways, but for two of them, I still don't know why it works.
This one works well. If I use Vector3.forward in Space.self, I think it should rotate around the x-axis of the cube itself, but it rotate around its z-axis.
GameObject cube = Instantiate(dirCubeTypes[Random.Range(0, 6)], dirCubePoints[Random.Range(0, 4)]);
cube.transform.Rotate(Vector3.forward, 90 * Random.Range(0, 4));
This one also works well. But I am confused either.
GameObject cube = Instantiate(dirCubeTypes[Random.Range(0, 6)], dirCubePoints[Random.Range(0, 4)]);
cube.transform.Rotate(transform.forward, 90 * Random.Range(0, 4), Space.World
);
The last one is the only one that works as I think.
GameObject cube = Instantiate(dirCubeTypes[Random.Range(0, 6)], dirCubePoints[Random.Range(0, 4)]);
cube.transform.RotateAround(transform.position, transform.forward, 90*Random.Range(0,4));

First of all
Vector3.forward just equals new Vector3(0,0,1) so it is just a generic Z axis vector (either in the world or local depending on how it is used)
transform.forward rather is the local Z axis of your object in world space
And Rotate can either be used in world space or in local space (this is the default since the space parameter is optional and defaults to Space.Self).
When you passed in
cube.transform.Rotate(transform.forward * 90 * Random.Range(0,4));
it of course behaves strange since you treat the world space vector transform.forward like a local space one. So as soon as your object is somehow rotated in the world it breaks.
So either you want the world space then do
// Using the world space transform.forward and passing in Space.World
cube.transform.Rotate(transform.forward * 90 * Random.Range(0,4), Space.World);
or the same in local space
// Using the local space Vector3.right
// in this case - since it will use Space.Self if nothing is passed in -
// the Vector3.forward is treated as relative to the local space
cube.transform.Rotate(Vector3.forward * 90 * Random.Range(0,4));

I just saw your question update. I believe your misunderstanding stems from not knowing what Vector3.forward and Transform.forward are and their core difference. Vector3.forward is the unit vector defined by (0, 0, 1). The Transform.forward is a relative direction based on the orientation of your object.
Your first example works in some cases as it just so happens that the Transform.forward and Vector3.forward happen to be the same direction.
Your second example works as you are using Vector3.forward not the relative Transform.forward
Your third example works as you provide the Space.World, so instead of using local orientations, it is rotating the object in world space relative to the scene.
Your fourth example works as you are using RotateAround. RotateAround is different than Rotate in that it will rotate the transform about your inputted axis (transform.forward), which pass through the point (transform.position) in world coordinates by the angle (90*Random.Range(0,4)) degrees. Effectively moving the local rotation around an axis back to world space.
I hope this clears up why the first example does not work while the last three do.

Related

Walk around square platform. Unity2D

I'm trying to make a character that walks around a platform and if the character reach a corner, it rotates and continue walking on the side of the platform, same with the bottom part.
This is a visual representation of what I'm trying to achive.
Movement
The specific problem is when the character reach the corners, the rotation just go crazy. I'm trayng to achive this using a raycast from the character to the platform and if the raycast doesn't find floor, I start the rotation like this:
times++;
transform.rotation = Quaternion.Slerp(transform.rotation,Quaternion.AngleAxis(times*-89.9f,Vector3.forward),Time.deltaTime * RotationSpeed);
_characterGravity.SetGravityAngle(transform.localEulerAngles.z);
I'm using a characterGravity script that allows me to change the gravity direction for the character in order to not fall when is walking upside down or on the sides. But this is not working propperly. Is there a better way to do this?
Assuming your 2D view is aligned on the X-Y plane, with the Z-axis aimed into the screen (the same direction the camera is facing), I suggest using Transform.Rotate() instead of trying to linearly interpolate the rotation between two values:
if (ShouldRotateAroundCorner()) {
transform.Rotate(Vector3.forward, RotationSpeed * Time.deltaTime);
}
You'll just need to make sure your ShouldRotateAroundCorner() knows when to start and stop the 90 degree turn, which will take a little bit of additional code to keep track of when it's in this state change.

Unity3D - Get Mathematical Plane from Primitive Plane in same world position

I have a GameObject.Plane in a given position for visual purposes.
From that, given the plane's rotation and position in world space, I have to make a mathematical plane to use in the next calculations, but something's off.
I've tried doing
new Plane(go.transform.InverseTransformDirection(-go.transform.right), go.transform.InverseTransformPoint(go.transform.position));
where go is the primitive plane object.
Its position in world space seems fine, judging from the later results, but the rotation is not.
The Primitive plane(go) is rotated 90 degrees so instead of facing upwards it's facing the side.
What's going wrong here?
Edit: Current setup is
go.transform.InverseTransformDirection(-go.transform.right);
return new Plane(go.transform.up, go.transform.position);
void OnDrawGizmos()
{
Gizmos.color = Color.magenta;
Gizmos.DrawLine(planePrimitive.transform.position, planePrimitive.transform.position + plane.normal * 3f);
}
This
go.transform.InverseTransformDirection(-go.transform.right)
returns a vector facing to the side and you are using it as the Plane's normal.
A primitive Plane object's normal is simply it's local transform.up vector.
Then note that a Plane takes world space coordinates so it makes no sense to use InverseTransformDirection and InverseTransformPoint since transform.up and transform.position already are in world space.
new Plane(go.transform.up, go.transform.position);
If you are rather talking about a primitive Quad object then the vector you want is -transform.forward instead.
new Plane(-go.transform.forward, go.transform.position);
To your Gizmos:
You are using the testPlane.normal like it was a position. It is not! The normal is a direction and doesn't tell anything about the position.
What you want to do would be e.g. (without using local space gizmos)
Gizmos.DrawLine(go.transform.position, go.transform.position + testPlane.normal * 3f);
or if you can depend only on the plane itself use
Gizmos.DrawLine(plane.distance, plane.distance + testPlane.normal * 3f);
In this second plane the plane.distance is the vector from 0,0,0 to the closest point on the plane. It will therefore still not be exactly in the position of the given plane object.

Which rotation is shown in the Inspector?

The chest bone of my player can be rotated while aiming.
Now I wanted to evaluate how much (minimum and maximum rotation) I should let the chest be rotatable.
To do that, I allowed all degrees of rotation and took a look at the Inspector.
For example, the minimum value that the chest should be rotatable to the left should be Y=-15.
At Y=-15 (seen in the Inspector), it still looked natural.
Now I wanted to code this.
To my surprise, chest.localRotation.Y was a completely different value than what the Inspector is showing.
I have then taken a look at the chest variable and extended the view.
I just can't see the rotation value that the Inspector is showing.
How should I go on in this case, please?
I'm using this to rotate the bone:
Chest.LookAt(ChestLookTarget.position);
Chest.rotation = Chest.rotation * Quaternion.Euler(Offset);
Thank you!
The reason why it doesn't work:
Quaternion is not a human readable value.
One Quaternion is allways unique but can have multiple (infinite?) different representations in Euler space! The other way round one Euler represents allways exactly one Quaternion value.
If you look at the docs it explicitly says
Don't modify this directly unless you know quaternions inside out.
Than as said what you see in the inspector is the localRotation in relation to the parent Transform.
Better said it is one of the many possible Euler inputs that result in the Quaternion. What you see in the debug at localEulerAngles is another possible Euler representation. Unity usually in localEulerAngles also gives you only values > 0.
It seems that the chest anyway will only rotate around the Y axis, right?
If this is the case you can simply get the Angle between the chest's original forward vector and the target. It is way easier to handle Vector3 values than Quaternions ;)
It seems to be the same use case as in this post
// get the target direction
Vector3 targetDir = ChestLookTarget.position - Chest.position;
// Reset any difference in the Y axis
// since it would change the angle as well if there was a difference I the height
// between the two objects
targetDir.y = 0;
// however you currently rotate
// instead rotate only the Vector3 variable without applying it to the transform yet
Vector3 newDir = Vector3.RotateTowards(Chest.forward, targetDir, RotationSpeed * Time.deltaTime, 0.0f);
// Compare the target direction to the parents forward vector
float newAngle = Vector3.Angle(Chest.parent.transform.forward, newDir);
if (newAngle > MaxRotationAngle)
{
// What should happen if angle gets bigger?
return;
}
// If angle still okey set the new direction
Chest.rotation = Quaternion.LookRotation(newDir);

How do I apply rotations to parent child using Rigidbody?

I have a finger object. It is just three cubes representing the finger parts.
The 2nd cube is the child of the 1st one. And the 3rd cube is the child of the 2nd one.
This is the heirarchy: Cube1 -> Cube2 -> Cube3
My goal is to apply a rotation angle to the first cube and let the other cubes do the same locally.
Example: Apply 30 degrees Z rotation to the first cube, 30 degrees Z rotation to the 2nd cube, and also the 3rd one.
This will make a finger that look like this:
(Forgive me if it doesn't look like a finger)
In every Update() frame, I will change the angle (it's just one number) and it will rotate every cube for me.
My question is:
How do I make all these cubes collide properly with other objects?
I tried putting the Rigidbody on all of them and set isKinematic=false because I want to transform them myself. But I still cannot use transform.rotation to update my rotation because it will miss the collision with a ball very easily (especially the tip of the finger because it moves faster than other parts). Continuous detection doesn't help.
So I tried using rigidbody.MoveRotation() and rigidbody.MovePosition() instead, which is a pain because they need absolute values. They worked but the animation is so jumpy when I change the angle quickly.
I'm guessing that the animation is jumpy because there are many Rigidbodies or because the physics engine cannot interpolate the position of each box properly when I use MoveRotation() and MovePosition().
I need to use MovePosition() also because when I use only child.MoveRotation(transform.parent.rotation * originalChildLocalRotation * Quaternion.Euler(0, 0, angle)), the position doesn't move relative to the parent. So I have to compute child.MovePosition(parent.TransformPoint(originalLocalPositionOfTheChild)) every frame too.

Camera movement using WASD on x/z plane only

I've been wearing my enter key down on google searches - I have a camera script based on the MouseOrbit.js asset. That's all working fine, but in addition to the basic orbiting and the zooming that I've added, I would like to use the WASD keys to move the camera around the world.
The W key would move the camera straight forward, however it would ignore the y axis. For example, using
transform.Translate(Vector3.Forward*Time.Delta*20);
moves the camera forward relative to the camera. This results in you quickly hitting the ground. Moving back oibviously does the opposite. The desired effect is sliding across the world without getting any closer/farther to it, regardless of the angle the camera is at.
The closest I can get is using the Space.World parameter of Translate(), but this does not take into account the rotation of my camera. I think if I could take that into account, this would be solved but I'm not clear on how to do that.
Thanks,
Chris
(From Tetrad on http://Gamedev.stackecxchange.com)
You don't need to use transform.Translate. Just calculate how much the camera should move forward for a given frame (something like if the W key is held down do deltaPos = transform.forward * Time.deltaTime * 20), set the Y value of that Vector3 to zero, then add that delta vector to the original position by adding it to the current position transform.position += deltaPos;