Here is my code:
#pragma strict
public var spinAxis : Vector3 = Vector3(0,45,90);
function Start () {
}
function Update () {
// Slowly rotate the object around its X axis at 1 degree/second.
transform.Rotate(spinAxis * Time.deltaTime);
}
I just want the cube to spin on two axes (in this case, y and z) but it spins on all three. I know I'm making some dumb mistake here. I can do it successfully by saying
transform.Rotate(new Vector3(0,0,100) * Time.deltaTime);
for example. But I'd like the axes to be available from the inspector.
This will do what you want:
#pragma strict
public var spinSpeed : Vector3 = Vector3(0,45,90); // Degrees per second per axis
private var rotation = Vector3.zero;
function Update ()
{
rotation += spinSpeed * Time.deltaTime;
transform.rotation.eulerAngles = rotation;
}
The reason your code gives unexpected results is because I think you misread what the Transform.Rotate function does. It does not give you an axis and an amount to spin around like the spinAxis variable name suggests. That is achieved by Transform.RotateAround.
If you use Transform.Rotate then the rotations are applied sequentially. But in the process the axes get mixed up. For instance if you first rotate 90 degrees on the Y-axis then the X- and Z-axes have switched place. Rotate sequentially on any two axes and the third one will always get involved like this. Using the above code you essentially reset or recreate the entire rotation every update which allows you to keep the uninvolved axis at 0.
Related
I'm working with this tutorial on using a mouse to test a VR game, and for the most part I got it working, except it seems to "reset" the X or Y position every time I switch which axis I'm moving on. For example, if I move the mouse x axis, it rotates the view, but then if I move the y axis, the x-position (.localRotation in the code) goes back to 0 (looking straight ahead in the original position). Then if I move the x axis again, the y-position goes back to zero, but the x position picks up where I left it last time.
I have a feeling it isn't this code that's doing it, but I'll paste here just in case:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
public class MouseLook : MonoBehaviour
{
public InputActionReference horizontalLook;
public InputActionReference verticalLook;
public float lookSpeed = 0.25f;
public Transform cameraTransform;
float pitch;
float yaw;
// Start is called before the first frame update
void Start()
{
Cursor.lockState = CursorLockMode.Locked;
horizontalLook.action.performed += HandleHorizontalLookChange;
verticalLook.action.performed += HandleVerticalLookChange;
}
void HandleHorizontalLookChange(InputAction.CallbackContext obj)
{
yaw += obj.ReadValue<float>();
transform.localRotation = Quaternion.AngleAxis(yaw * lookSpeed, Vector3.up);
}
void HandleVerticalLookChange(InputAction.CallbackContext obj)
{
pitch += obj.ReadValue<float>();
transform.localRotation = Quaternion.AngleAxis(pitch * lookSpeed, Vector3.right);
}
}
You set the rotation to explicit values based on a sum of mouse position or delta (isn't clear without seeing the Input Asset, but I assume delta), thus your rotation gets "reset" to the exact rotations only given by the current axis everytime.
Solution 1
Add the new rotation to your existing rotation by multiplying it to the left of your current rotation, e.g. localRotation = Quaternion.AngleAxis(...) * localRotation;
Solution 2 (recommended)
Since Quaternions are not as intuitively understood as angles, Unity provides a convenience function called Rotate(axis, angle) on every Transform object
=> transform.Rotate(<Vector3 as axis>, <your angle value>);
In both approaches, however, the angle value should be calculated from mouse delta and not absolute screen position nor a sum of deltas.
Make sure to read through the Public Methods section on this manual page for a thorough understandung of how to manipulate transforms in Unity: https://docs.unity3d.com/ScriptReference/Transform.html
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.
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.
I have already this function from this question. I changed the sign of the rotation:
void rotateBotConnector()
{
Vector3 diff = (player.transform.position - botConnector.transform.position).normalized;
float rot_z = Mathf.Atan2(diff.y, diff.x) * Mathf.Rad2Deg;
botConnector.transform.localRotation = Quaternion.Euler(0f, 0f, -(rot_z - 90f));
}
But the problem is, that now my object follows the player on the XZ plane but when the rotation reaches a certain degree, left or right, the object stops to rotate towards my player.
For better understanding: http://imgur.com/vWaqc31
Why not just use:
var target : Transform;
transform.LookAt(Vector3(target.transform.position.x, target.transform.position.y, transform.position.z);
It seems a lot easier than using euler. This way you look at target's x & y but transform your z.
Also I'm no expert with euler but it seems like it is limited to a 90 degree turn and I think this may be the reason why:
Quaternion.Euler(0f, 0f, -(rot_z - 90f));
Unless you have absolute necessity to rotate via angles maybe you'd rather go with manipulating the forward vector of the object, it's easier to understand the logic this way:
void rotateBotConnector()
{
Vector3 targetForwad = botConnector.transform.position - player.transform.position;
targetForward.y= 0f;
targetForward.Normalize(); // <-- this is very expensive function try avoid using it whenever possible
botConnector.forward = Vector3.Lerp(botConnector.forward, targetForward, Time.deltaTime));
}
once again, if you are short on cpu cycles you may want to go for trying to calculate angles instead of vectors. But in most cases this is ok. (the code needs a spell checking)
I'm new in Unity. I want to do that wheels (cylinders) in this car will turn around if I press "up" button on my keyboard.
This is the code I've written:
var forwardSpeed: float = 3;
function Start () {
}
function Update () {
var forwardMoveAmount = Input.GetAxis("Vertical")*forwardSpeed;
transform.Rotate(0, forwardMoveAmount, 0);
}
OK. Wheels are turning around, but my car is still at the same place. What should I do to move this car?
PS: can you explain me, why this cylinder rotates correctly, when I use Y axis? It should be z.
For physics based wheels, you probably want wheel colliders. Their use is pretty well documented here:
http://docs.unity3d.com/Documentation/Components/class-WheelCollider.html
You're applying the distance scalar to a rotate function, and not translating (Moving) the object.
transform.Rotate will rotate the object.
transform.Translate will move the object.
Pick an object on your desk. If you rotate it 90 degrees twice, it will be rotated 180 degrees but still be in the same place. Now imagine every time you rotate that object, you move it in the direction it's facing by a couple inches. After 4 cycles, the object will have completed a full circuit of 360 degrees.
To represent this in code:
var forwardSpeed: float = 3; // Tweak me
var turnAngle: float = 1; // Tweak me
function Start () {
}
function Update () {
// Rotate first
transform.Rotate(0, Vector3.right * turnAngle, 0);
// Move forward along the rotated axis
transform.Translate(0, Vector3.forward * forwardSpeed, 0);
}
You'll need to also explicitly move the car at the same time, using presumably transform.position, transform.Translate(), or something similar on the parent car object.