How to clamp camera rotation axis correctly? - c#

I am trying to clamp the X and Y axis of my camera, which I have managed to do. However, when the clamped value reaches the MAX threshold it will jump back to the MIN threshold!
Any idea what is causing this in my code?
private void ClimbingLookRotation()
{
if (input.mouseX != 0 || input.mouseY != 0f)
{
orientation.rotation *= Quaternion.AngleAxis(input.mouseX, Vector3.up);
orientation.rotation *= Quaternion.AngleAxis(input.mouseY, Vector3.right);
}
var rotX = orientation.eulerAngles.x;
var rotY = orientation.eulerAngles.y;
rotX = Mathf.Clamp(rotX, 1, 25);
rotY = Mathf.Clamp(rotY, 200, 355);
orientation.eulerAngles = new Vector3(rotX, rotY, 0);
}
Any help would be appreciated!
Thankyou.

I found something the works well so thought I'd answer in case it would help anyone!
public static float ClampAngle(float angle, float min, float max)
{ //Normalises angle value passed in between -180 to 180 to make the angle clampable
angle = NormalizeAngle(angle);
if (angle > 180)
{
angle -= 360;
}
else if (angle < -180)
{
angle += 360;
}
min = NormalizeAngle(min);
if (min > 180)
{
min -= 360;
}
else if (min < -180)
{
min += 360;
}
max = NormalizeAngle(max);
if (max > 180)
{
max -= 360;
}
else if (max < -180)
{
max += 360;
}
return Mathf.Clamp(angle, min, max);
}
public static float NormalizeAngle(float angle)
{ //If the angle is above or below 360 degrees, normalise it
while (angle > 360)
angle -= 360;
while (angle < 0)
angle += 360;
return angle;
}
And then just call the ClampAngle method where ever you need to clamp a value, for example:
private void ClimbingLookRotation()
{
if (input.mouseX != 0 || input.mouseY != 0f)
{
orientation.rotation *= Quaternion.AngleAxis(input.mouseX, Vector3.up);
orientation.rotation *= Quaternion.AngleAxis(input.mouseY, Vector3.right);
}
var rotX = orientation.eulerAngles.x;
var rotY = orientation.eulerAngles.y;
rotX = HelperFunctions.ClampAngle(rotX, -10, 25); //Here
rotY = HelperFunctions.ClampAngle(rotY, 200, 340); //And here
orientation.eulerAngles = new Vector3(rotX, rotY, 0);
}
I've called it in my camera rotation function to clamp both rotX and rotY, which is later applied to the rotation of my orientation game object.
Thankyou again to Ruzihm for your help before :)

You could re-center your euler angles around the center value then clamp the difference:
float centralX = 13f;
float extentX = 12f;
float centralY = 277.5f;
float extentY = 77.5f;
private static float ClampEuler(float val, float center, float extent)
{
return center + Mathf.Clamp((val - center + 360f) % 360f, -extent, extent);
}
private void ClimbingLookRotation()
{
if (input.mouseX != 0 || input.mouseY != 0f)
{
orientation.rotation *= Quaternion.AngleAxis(input.mouseX, Vector3.up);
orientation.rotation *= Quaternion.AngleAxis(input.mouseY, Vector3.right);
}
var rotX = orientation.eulerAngles.x;
var rotY = orientation.eulerAngles.y;
rotX = ClampEuler(rotX, centralX, extentX);
rotY = ClampEuler(rotY, centralY, extentY);
orientation.eulerAngles = new Vector3(rotX, rotY, 0);
}
By the way you may want to do orientation.rotation *= Quaternion.AngleAxis(input.mouseY, orientation.right); to rotate around the local right instead of global right. Rotating around global right may produce a rolling effect over time which you may not want.

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;

Mathf.Clamp is messing with my camera movement script

i am making a game that uses the WASD keys to move the camera between different objects and am using Mathf.Clamp to limit it but it now makes it so the movement doesn't work and i only move diagonal which shouldn't be possible without multiple key presses.
{
public Vector3 startPos;
public float moveDistance = 1;
public float minX;
public float minZ;
public float maxX;
public float maxZ;
void Start()
{
transform.position = startPos;
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.W))
{
transform.position += new Vector3(0, 0, moveDistance);
}
else if (Input.GetKeyDown(KeyCode.S))
{
transform.position += new Vector3(0, 0, -moveDistance);
}
else if (Input.GetKeyDown(KeyCode.D))
{
transform.position += new Vector3(moveDistance, 0, 0);
}
else if (Input.GetKeyDown(KeyCode.A))
{
transform.position += new Vector3(-moveDistance, 0, 0);
}
transform.position = new Vector3(Mathf.Clamp(transform.position.x, minX, maxX), transform.position.y, Mathf.Clamp(transform.position.x, minZ, maxZ));
}
}
looks like Ruzihm already answered your question.
I recently ran into some problems with Mathf.Clamp with negative numbers.
So I wanted to share the solution.
Cannot remember where I found it so sorry I can't credit the person who made it.
public float ClampAngle(float angle, float minAngle, float maxAngle)
{
if (angle < 90 || angle > 270)
{ // if angle in the critic region...
if (angle > 180) angle -= 360; // convert all angles to -180..+180
if (maxAngle > 180) maxAngle -= 360;
if (minAngle > 180) minAngle -= 360;
}
angle = Mathf.Clamp(angle, minAngle, maxAngle);
if (angle < 0) angle += 360; // if angle negative, convert to 0..360
return angle;
}

Clamping my camera to a min and max value

I tried many approaches, watched tutorials but can't wrap my head around to make the clamp work with my code that I have right now.
So I can zoom in and out but infinitely, how to clamp the camera to max value -5 which is slightly above my player, and min value around -15 which is far above my player.
// Control the distance between the object && camera
private void ZoomIntoObject(float maxZoom, float minZoom)
{
float scrollInput = Input.GetAxis("Mouse ScrollWheel");
//zPos = scrollInput;
// zPos = Mathf.Clamp(zPos, minZoom, maxZoom);
// While scrollwheel
if (scrollInput > 0.0f)
{
// Move forward on the z-as && Clamp maxZoom
transform.position += transform.forward;
} else if (scrollInput < 0.0) {
// Move forward on the z-as && Clamp maxZoom
transform.position -= transform.forward;
}
Debug.Log(zPos);
}
Calculate your new position, clamp its z component, then assign to position.
private void ZoomIntoObject(float maxZoom, float minZoom)
{
float scrollInput = Input.GetAxis("Mouse ScrollWheel");
Vector3 newPos = transform.position;
if (scrollInput > 0.0f)
{
newPos += transform.forward;
} else if (scrollInput < 0.0) {
newPos -= transform.forward;
}
newPos.z = Mathf.Clamp(newPos.z, minZoom, maxZoom);
transform.position = newPos;
}

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;
}

How to clamp camera in Unity3D

My code is not working, I am trying to clamp the camera, but its not working. How to clamp the camera?
using UnityEngine;
using System.Collections;
public class MoveCamera : MonoBehaviour
{
public float sensitivity = 4.0f;
private Vector3 mouseOrigin;
private bool isRotating;
private float speed = 2.0f;
private float minX = -45.0f;
private float maxX = 45.0f;
private float minY = -10.0f;
private float maxY = 10.0f;
float rotationY = 0.0f;
float rotationX = 0.0f;
void Update ()
{
if (Input.GetMouseButtonDown (0)) {
mouseOrigin = Input.mousePosition;
isRotating = true;
}
if (!Input.GetMouseButton (0))
isRotating = false;
if (isRotating) {
Vector3 pos = Camera.main.ScreenToViewportPoint (Input.mousePosition - mouseOrigin);
transform.RotateAround (transform.position, transform.right, -pos.y * sensitivity);
transform.RotateAround (transform.position, Vector3.up, pos.x * sensitivity);
rotationY = Mathf.Clamp (rotationY, minY, maxY);
rotationX = Mathf.Clamp (rotationX, minX, maxX);
transform.localEulerAngles = new Vector3 (-rotationY, rotationX, 0);
}
}
}
You forgot to get the value of rotationX and rotationY from your transform after rotating it. Try this :
if (isRotating) {
Vector3 pos = Camera.main.ScreenToViewportPoint (Input.mousePosition - mouseOrigin);
transform.RotateAround (transform.position, transform.right, -pos.y * sensitivity);
transform.RotateAround (transform.position, Vector3.up, pos.x * sensitivity);
rotationY = Mathf.Clamp (transform.localEulerAngles.y, minY, maxY);
rotationX = Mathf.Clamp (transform.localEulerAngles.x, minX, maxX);
transform.localEulerAngles = new Vector3 (-rotationY, rotationX, 0);
}
Here's the code of Mathf.Clamppublic static float Clamp(float value, float min, float max) {
if (value < min) {
value = min;
} else if (value > max) {
value = max;
}
return value;
}
Use an IL reverse tool such as (EXTERNAL LINK)ILSPY if you are unsure how a call works in .NET (Unity / Mono / etc).
Based on the code you posted and understanding Mathf.Clamp should work as intended, the issue is most probably laying in your code, at least at one point, e.g. here:
rotationY = Mathf.Clamp (rotationX, minY, maxY); //note it's rotation "X" instead of "Y"
rotationX = Mathf.Clamp (rotationX, minX, maxX);
If this still did not sort out the issue, use Debug.Log to see variable values to find where you make the mistake(s).
If you can't sort out like this, you'll have a clear picture what exactly you can't do and can post a much cleaner question and expect a clean answer.
Hope this helps!
OK, So I fixed it. Here is complete code.
using UnityEngine;
using System.Collections;
public class MoveCamera : MonoBehaviour
{
public float sensitivity = 4.0f;
private Vector3 mouseOrigin;
private bool isRotating;
public GameObject cam;
void Start()
{
}
protected float ClampAngle(float angle, float min, float max) {
angle = NormalizeAngle(angle);
if (angle > 180) {
angle -= 360;
} else if (angle < -180) {
angle += 360;
}
min = NormalizeAngle(min);
if (min > 180) {
min -= 360;
} else if (min < -180) {
min += 360;
}
max = NormalizeAngle(max);
if (max > 180) {
max -= 360;
} else if (max < -180) {
max += 360;
}
return Mathf.Clamp(angle, min, max);
}
protected float NormalizeAngle(float angle) {
while (angle > 360)
angle -= 360;
while (angle < 0)
angle += 360;
return angle;
}
void Update ()
{
if (Input.GetMouseButtonDown (0)) {
mouseOrigin = Input.mousePosition;
isRotating = true;
}
if (!Input.GetMouseButton (0))
isRotating = false;
if (isRotating) {
cam.transform.localEulerAngles = new Vector3(0, ClampAngle(cam.transform.localEulerAngles.y, -45, 45), 0);
Vector3 pos = Camera.main.ScreenToViewportPoint (Input.mousePosition - mouseOrigin);
transform.RotateAround (transform.position, transform.right, -pos.y * sensitivity);
transform.RotateAround (transform.position, Vector3.up, pos.x * sensitivity);
}
}
}

Categories