Rotate any GameObject between Two Angles [duplicate] - c#

I would like to rotate an object back and forth between 90,-90 on the Y axis. The problem is I can set the object in the editor at -90, but when I run the project -90 suddenly becomes 270. Anyway here is the code that I'm using:
void Update()
{
if (transform.eulerAngles.y >= 270)
{
transform.Rotate(Vector3.up * speed * Time.deltaTime);
}
else if (transform.eulerAngles.y <= 90)
{
transform.Rotate(Vector3.up * -speed * Time.deltaTime);
}
}
It always gets stuck in the middle around 360 degrees. Help?

Just like moving GameObject back and forth, you can rotate GameObject back and forth with Mathf.PingPong. That's what it is used for. It will return value between 0 and 1. You can pass that value to Vector3.Lerp and generate the eulerAngle required to perform the rotation.
This can also be done with a coroutine but Mathf.PingPong should be used if you don't need to know when you have reached the destination. Coroutine should be used if you want to know when you reach the rotation destination.
public float speed = 0.36f;
Vector3 pointA;
Vector3 pointB;
void Start()
{
//Get current position then add 90 to its Y axis
pointA = transform.eulerAngles + new Vector3(0f, 90f, 0f);
//Get current position then substract -90 to its Y axis
pointB = transform.eulerAngles + new Vector3(0f, -90f, 0f);
}
void Update()
{
//PingPong between 0 and 1
float time = Mathf.PingPong(Time.time * speed, 1);
transform.eulerAngles = Vector3.Lerp(pointA, pointB, time);
}
EDIT:
With the coroutine method that you can use to determine the end of each rotation.
public GameObject objectToRotate;
public float speed = 0.36f;
Vector3 pointA;
Vector3 pointB;
void Start()
{
//Get current position then add 90 to its Y axis
pointA = transform.eulerAngles + new Vector3(0f, 90f, 0f);
//Get current position then substract -90 to its Y axis
pointB = transform.eulerAngles + new Vector3(0f, -90f, 0f);
objectToRotate = this.gameObject;
StartCoroutine(rotate());
}
IEnumerator rotate()
{
while (true)
{
//Rotate 90
yield return rotateObject(objectToRotate, pointA, 3f);
//Rotate -90
yield return rotateObject(objectToRotate, pointB, 3f);
//Wait?
//yield return new WaitForSeconds(3);
}
}
bool rotating = false;
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;
}

First of all you should always ensure that the angle stays between 0 and 359, meaning that 0 == 360 :
float angle = transform.eulerAngles.y % 360.00f;
After that you can check if it's between your minimum and maximum value :
if ( angle >= 270.00f && angle <= 90.00f )
{
// do your logic here ...
}
Another issue with your code is that it will stuck somewhere between 350 and 10 degrees. To explain this in more details let's assume your speed is 10, Time.deltaTime will be 1 as well and starting angle is 300, now frame steps :
Frame | Angle | Condition
----- | ----- | :-----------:
1 | 300 | angle >= 270
2 | 310 | angle >= 270
3 | 320 | angle >= 270
6 | 350 | angle >= 270
7 | 360 | angle >= 270
8 | 10 | angle <= 90
9 | 0 | angle <= 90
10 | 350 | angle >= 270
... and this will go forever.
To deal with this you have to make some condition based on user input or if you want your camera to "bounce" between these two angle then you can try something like this :
// make private field in that object :
float currentlyRotated = 0;
bool shouldAdd = true;
void Update()
{
var d = Vector3.up * (shouldAdd ? speed : -speed) * Time.deltaTime;
var angle = transform.eulerAngles.y + (shouldAdd ? speed : -speed);
angle %= 360.00f;
transform.Rotate(d);
if ( angle > 90 && angle < 270 )
{
shouldAdd = !shouldAdd;
}
}

Related

Flipping sprite by y axis when rotated past a certain point

I'm new to unity, and an amateur C# user. I have a submarine sprite that I would like to flip by the y-axis when rotated more than 90 degrees, and less than -90 degrees so that it won't be upside down. It's rotated by mouse movement which I'll give the code if necessary. I'm not sure why but, this doesn't seem to work. Any help would be appreciated!
Code:(rot90 is a bool)
if (transform.rotation.z > 90 & transform.rotation.z >-90)
{
rot90 = false;
}
if (transform.rotation.z < 90 & transform.rotation.z < -90)
{
rot90 = true;
}
if (rot90 == true)
{
Vector3 scale = transform.localScale;
scale.y = -22;
transform.localScale = scale;
}
if (rot90 == false)
{
Vector3 scale = transform.localScale;
scale.y = 22;
transform.localScale = scale;
}
transform.rotation is a Quaternion!
A Quaternion has not only 3 but 4 component x, y, z and w, and they all move in ranges between -1 and 1.
Your conditions can never become true!
You could use the eulerAngles and do e.g.
var zAngle = transform.eulerAngles.z;
// clean out the angle to a value between -180 and +180
while(zAngle > 180) zAngle -= 360;
while(zAngle < -180) zAngle += 360;
Vector3 scale = transform.localScale;
scale.y = Mathf.Abs(zAngle) > 90 ? -22 : 22;
transform.localScale = scale;
in case you are using a SpriteRenderer component you should rather go for SpriteRenderer.flipY
var zAngle = transform.eulerAngles.z;
// clean out the angle to a value between -180 and +180
while(zAngle > 180) zAngle -= 360;
while(zAngle < -180) zAngle += 360;
// You should of course rather cache this reference e.g. in Awake only once
// and then reuse it here
GetComponent<SpriteRenderer>().flipY = Mathf.Abs(zAngle) > 90;

How can I limit the rotation on the Y axis so that the player can't continuously spin the camera in Unity

I have an upcoming project that I have to present on Monday and this is the last bug I have to resolve. It would be nice if someone could help me out, and could teach me how to apply an axis limiter, thanks in advance everyone.
The issue is that the camera can spin 360 degrees
Below is my current code that controls the camera
public float sensitivity = 30.0f;
private GameObject cam;
float rotX, rotY;
private void Start()
{
sensitivity = sensitivity * 1.5f;
Cursor.visible = false; // For convenience
}
private void Update()
{
rotX = Input.GetAxis("Mouse X") * sensitivity;
rotY = Input.GetAxis("Mouse Y") * sensitivity;
//Apply rotations
CameraRotation(cam, rotX, rotY);
}
private void CameraRotation(GameObject cam, float rotX, float rotY)
{
//Rotate the player horizontally
transform.Rotate(0, rotX * Time.deltaTime, 0);
//Rotate the players view vertically
cam.transform.Rotate(-rotY * Time.deltaTime, 0, 0);
}
Adding logical clamping to this makes it quite a bit clearer to read through, this method clamps the vertical rotation between 60 and -60 degrees, the comments should help in modifying it if you need
I ended up jumping into Unity to test it this time, instead of going off the top of my head
void CameraRotation(GameObject cam, float rotX, float rotY)
{
//Rotate the player horizontally
transform.Rotate(0, rotX * Time.deltaTime, 0);
//Rotate the players view vertically
cam.transform.Rotate(-rotY * Time.deltaTime, 0, 0);
//Grab current rotation in degrees
Vector3 currentRot = cam.transform.localRotation.eulerAngles;
//If (x-axis > (degreesUp*2) && x-axis < (360-degreesUp))
if (currentRot.x > 120 && currentRot.x < 300) currentRot.x = 300;
//else if (x-axis < (degreesDown*2) && x-axis > (degreesDown))
else if (currentRot.x < 120 && currentRot.x > 60) currentRot.x = 60;
//Set clamped rotation
cam.transform.localRotation = Quaternion.Euler(currentRot);
}
Goodluck with the project

Clapming rotation of child objects in unity

I have a game object that contains another game object that should be able to rotate towards target (imagine a tank turret). And so I've created the below script:
public class Rotator : MonoBehaviour {
public GameObject _enemy;
void Update () {
var actualTarget = _enemy.transform.position;
var targetDir = actualTarget - transform.position;
var step = 2 * Time.deltaTime;
var target = Quaternion.LookRotation(targetDir.normalized, Vector3.up);
var actual = target * Quaternion.Inverse(transform.parent.rotation);
var targetRotation = Quaternion.Slerp(transform.localRotation, actual, step);
targetRotation.eulerAngles = ClampRotation(targetRotation.eulerAngles);
transform.localRotation = targetRotation;
}
private static Vector3 ClampRotation(Vector3 eulerAngles) {
var x = Mathf.Clamp(eulerAngles.x > 180 ? eulerAngles.x - 360 : eulerAngles.x, -180, 180);
var y = Mathf.Clamp(eulerAngles.y > 180 ? eulerAngles.y - 360 : eulerAngles.y, -45, 45);
return new Vector3(x, y, 0);
}
}
Objects setup:
The rotation of the object named "parent" is 90deg on the Y axis, everything else is not rotated.
Clamping on the y axis works well - the rotation stays between -45 and 45 degrees. The rotation however doesn't work on the x axis (with, or without clamping).
So the goal here is that when I move the cube left or right, the red one rotates between [-45,45] degrees around the Y axis and when I move it top or down, the red one rotates between [-180,180] degrees around the X axis.
I had some success using the LookAt method of the Transform clas, but for some reason if I try to manually modify eulerAngles of a localRotation it suddenly looses the possibility to rotate backwards on the X axis even though I'm only doing something to the Y values...
After long hours of trial and error and browsing the internet frenzily, I managed to find an answer that I could tailor to my needs. Word of comment to the first if statement of the Clamp method - this is useful if I want the object to also be clamped in it's inverted position (if the target is behind it):
void Update() {
transform.LookAt(_target.transform);
var rotation = transform.localRotation;
var eulers = ClampRotation(rotation.eulerAngles);
transform.localEulerAngles = eulers;
}
private static Vector3 ClampRotation(Vector3 eulerAngles) {
var x = Clamp(eulerAngles.x, -60, 60);
var y = Clamp(eulerAngles.y, -45, 45);
return new Vector3(x, y, 0);
}
private static float Clamp(float angle, float min, float max) {
if ((angle <= 180 && angle >= 180 - Mathf.Abs(min)) || (angle >= 180 && angle <= 180 + max)) {
return Mathf.Clamp(angle, 180 - Mathf.Abs(min), 180 + max);
}
if (angle > 180f) {
angle -= 360f;
}
angle = Mathf.Clamp(angle, min, max);
if (angle < 0f) {
angle += 360f;
}
return angle;
}
EDIT:
As it turns out, sometimes it's better to create your own fine-grained solution, you can modify more easily, so to anyone who is interested, you can do what I wanted to do with the below code as well:
void Update() {
var actualTarget = _enemy.transform.position;
var targetDir = actualTarget - transform.position;
var target = Quaternion.LookRotation(targetDir.normalized, transform.up);
var actual = Quaternion.Inverse(transform.parent.rotation) * target;
actual.eulerAngles = ClampRotation(actual.eulerAngles);
var targetRotation = Quaternion.Slerp(transform.localRotation, actual, 8 * Time.deltaTime);
transform.localRotation = targetRotation;
}
private static Vector3 ClampRotation(Vector3 newRotation) {
var x = Clamp(newRotation.x, -179, 179);
var y = Clamp(newRotation.y, -45, 45);
return new Vector3(x, y, 0);
}
private static float Clamp(float angle, float min, float max) {
if ((angle <= 180 && angle >= 180 - Mathf.Abs(min)) || (angle >= 180 && angle <= 180 + max)) {
return Mathf.Clamp(angle, 180 - Mathf.Abs(min), 180 + max);
}
if (angle > 180f) {
angle -= 360f;
}
angle = Mathf.Clamp(angle, min, max);
if (angle < 0f) {
angle += 360f;
}
if (Mathf.Abs(angle) == 360) {
angle = 0;
}
return angle;
}

Unity C# rotate object back and forth between vals [duplicate]

I would like to rotate an object back and forth between 90,-90 on the Y axis. The problem is I can set the object in the editor at -90, but when I run the project -90 suddenly becomes 270. Anyway here is the code that I'm using:
void Update()
{
if (transform.eulerAngles.y >= 270)
{
transform.Rotate(Vector3.up * speed * Time.deltaTime);
}
else if (transform.eulerAngles.y <= 90)
{
transform.Rotate(Vector3.up * -speed * Time.deltaTime);
}
}
It always gets stuck in the middle around 360 degrees. Help?
Just like moving GameObject back and forth, you can rotate GameObject back and forth with Mathf.PingPong. That's what it is used for. It will return value between 0 and 1. You can pass that value to Vector3.Lerp and generate the eulerAngle required to perform the rotation.
This can also be done with a coroutine but Mathf.PingPong should be used if you don't need to know when you have reached the destination. Coroutine should be used if you want to know when you reach the rotation destination.
public float speed = 0.36f;
Vector3 pointA;
Vector3 pointB;
void Start()
{
//Get current position then add 90 to its Y axis
pointA = transform.eulerAngles + new Vector3(0f, 90f, 0f);
//Get current position then substract -90 to its Y axis
pointB = transform.eulerAngles + new Vector3(0f, -90f, 0f);
}
void Update()
{
//PingPong between 0 and 1
float time = Mathf.PingPong(Time.time * speed, 1);
transform.eulerAngles = Vector3.Lerp(pointA, pointB, time);
}
EDIT:
With the coroutine method that you can use to determine the end of each rotation.
public GameObject objectToRotate;
public float speed = 0.36f;
Vector3 pointA;
Vector3 pointB;
void Start()
{
//Get current position then add 90 to its Y axis
pointA = transform.eulerAngles + new Vector3(0f, 90f, 0f);
//Get current position then substract -90 to its Y axis
pointB = transform.eulerAngles + new Vector3(0f, -90f, 0f);
objectToRotate = this.gameObject;
StartCoroutine(rotate());
}
IEnumerator rotate()
{
while (true)
{
//Rotate 90
yield return rotateObject(objectToRotate, pointA, 3f);
//Rotate -90
yield return rotateObject(objectToRotate, pointB, 3f);
//Wait?
//yield return new WaitForSeconds(3);
}
}
bool rotating = false;
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;
}
First of all you should always ensure that the angle stays between 0 and 359, meaning that 0 == 360 :
float angle = transform.eulerAngles.y % 360.00f;
After that you can check if it's between your minimum and maximum value :
if ( angle >= 270.00f && angle <= 90.00f )
{
// do your logic here ...
}
Another issue with your code is that it will stuck somewhere between 350 and 10 degrees. To explain this in more details let's assume your speed is 10, Time.deltaTime will be 1 as well and starting angle is 300, now frame steps :
Frame | Angle | Condition
----- | ----- | :-----------:
1 | 300 | angle >= 270
2 | 310 | angle >= 270
3 | 320 | angle >= 270
6 | 350 | angle >= 270
7 | 360 | angle >= 270
8 | 10 | angle <= 90
9 | 0 | angle <= 90
10 | 350 | angle >= 270
... and this will go forever.
To deal with this you have to make some condition based on user input or if you want your camera to "bounce" between these two angle then you can try something like this :
// make private field in that object :
float currentlyRotated = 0;
bool shouldAdd = true;
void Update()
{
var d = Vector3.up * (shouldAdd ? speed : -speed) * Time.deltaTime;
var angle = transform.eulerAngles.y + (shouldAdd ? speed : -speed);
angle %= 360.00f;
transform.Rotate(d);
if ( angle > 90 && angle < 270 )
{
shouldAdd = !shouldAdd;
}
}

Clamping a quaternion rotation causes shaking

I am using the unity3D game engine. I have a player that is a cube with a rigidbody attached to it. Gravity is turned off, and angular drag is set to 0. I am rotating it use AddTorque, but I want to make sure that the x angle is never more than the rotationClampLow (currently at 45 degrees) and never less than rotationClampHigh (currently 315 degrees). So the x angle can be anything except for greater than 45 or less than 315.
This is the code I currently have:
// rotationSpeed is a float currently set at 3.
void RotatePlayer () {
horizontalRotation = rotationSpeed * Input.GetAxisRaw("HorizontalRotate");
verticalRotation = rotationSpeed * Input.GetAxisRaw("VerticalRotate");
if ( horizontalRotation != 0 )
Debug.Log("Horizontal rotation input: " + horizontalRotation);
ClampPlayerRotation();
rb.angularVelocity = Vector3.zero;
rb.AddTorque(horizontalRotation, verticalRotation, 0f, ForceMode.VelocityChange);
}
void ClampPlayerRotation () {
// I use 180 because it is halfway between 45 and 315
if ( rb.rotation.eulerAngles.x >= rotationClampLow && rb.rotation.eulerAngles.x <= 180) {
// Make sure that the x rotation isn't more than the low clamp
transform.eulerAngles = new Vector3(rotationClampLow, rb.rotation.eulerAngles.y, rb.rotation.eulerAngles.z);
//Make sure no positive torque is added to the x axis
if ( horizontalRotation > 0 )
horizontalRotation = 0;
}
else if ( rb.rotation.eulerAngles.x <= rotationClampHigh && rb.rotation.eulerAngles.x > 180 ) {
// Make sure that the x rotation isn't less than the high clamp
transform.eulerAngles = new Vector3(rotationClampHigh, rb.rotation.eulerAngles.y, rb.rotation.eulerAngles.z);
//Make sure no negative torque is added to the x axis
if ( horizontalRotation < 0 )
horizontalRotation = 0;
}
Currently when the x rotation reaches 45, the cube stops rotation, but when it reaches 315 it starts to jitter between 310 and 315 degrees. How can this problem be resolved? Any help would be appreciated.
Edit: Here is what is happening
Edit 2: RotatePlayer() is being called from FixedUpdate().
Edit 3: Changed code from:
rb.rotation = Quaternion.Euler(new Vector3(rotationClampLow, rb.rotation.eulerAngles.y, rb.rotation.eulerAngles.z));
To:
transform.eulerAngles = new Vector3(rotationClampLow, rb.rotation.eulerAngles.y, rb.rotation.eulerAngles.z);

Categories