How to limit camera's vertical rotation Unity 3D - c#

I would like to limit my vertical rotation of the camera so that it can't do a 360 spin. I have tried a lot of tutorials but nothing worked for me so i .
Please also check my code.
[RequireComponent(typeof(PlayerMoto))]
public class PlayerController: MonoBehaviour {
public float speed = 5, sensetivity;
private PlayerMoto motor;
public GameObject hands;
public camera cam;
float lookUpMax = .6 f;
void Start() {
motor = GetComponent < PlayerMoto > ();
cam = GetComponent < camera > ();
}
void Update() {
//cam.transform.localEulerAngles = new Vector3(cam.transform.localEulerAngles.x, 0, 0);
float _xmove = Input.GetAxis("Horizontal");
float _zmove = Input.GetAxis("Vertical");
Vector3 _moveHorizontal = transform.right * _xmove;
Vector3 _movVertical = transform.forward * _zmove;
Vector3 _velocity = (_moveHorizontal + _movVertical) * speed;
motor.Move(_velocity);
float _yRot = Input.GetAxis("Mouse X");
Vector3 _rotation = new Vector3(0 f, _yRot, 0 f) * sensetivity;
motor.Rotate(_rotation);
float _xRot = Input.GetAxis("Mouse Y");
Vector3 _cameraRotation = new Vector3(_xRot, 0 f, 0 f) * sensetivity;
motor.RotateCamera(_cameraRotation);
if (Input.GetKey(KeyCode.LeftShift) && Input.GetKey(KeyCode.W)) {
for (; speed <= 15; speed++) {
speed = 15;
}
} else {
speed = 10;
}
}
}
Thank you very much for your kind help. I really appreciate every single comment to try and help me in this amazing journey.

Try this out
private void Rotation()
{
x_axis += speed * Input.GetAxis("Mouse X"); // speed = 2f;
y_axis -= speed * Input.GetAxis("Mouse Y");
y_axis = Mathf.Clamp (y_axis, -45, 45); // limits vertical rotation
transform.eulerAngles = new Vector3(y_axis, x_axis, 0.0f);
}

If I understood your code, the part that moves the camera is:
float _xRot = Input.GetAxis("Mouse Y");
Vector3 _cameraRotation = new Vector3(_xRot, 0f, 0f) * sensetivity;
Then, your code calls this method on another class
motor.RotateCamera(_cameraRotation);
which probably (since the previous suggestion did not work) applies the rotation through
GameObject.Rotate(_cameraRotation);
In order to limit your camera’s rotation, we need to directly apply the rotation, and it cannot be clamped if the rotation is applied by adding a rotation to the existing rotation.
So, let’s assume you can skip that call and directly apply the rotation (I don’t know if there will be side effects), and let’s assume your cam variable is your camera.
If all these assumptions are correct, you can try:
float _xRot += Input.GetAxis("Mouse Y") * sensetivity;
xRot = Mathf.Clamp(_xRot, xMin, xMax);
cam.transform.rotation = Quaternion.Euler(_xRot, 0.0f, 0.0f);
Remember to comment out
//motor.RotateCamera(_cameraRotation);
You can declare
public float xMin;
public float xMax;
And experiment with values until you find your optimal ones.
I strongly suspect that the code I suggested will not solve all your problems, or it could add issues, because the actual transformations of both your player and the camera are applied by another script, that is not provided. In that case, you can provide us also that code, but I suggest you try writing your own code, that you can customize to your needs.
As for why clamping a rotation is not as easy as it seems, this post is interesting:
Rotations, Quaternions and Euler Angles

Related

How to stop body from rotating around wrong axis in unity

I recently started on unity and I wanted to make simple movement which can be seen below.
using System.Collections.Generic;
using UnityEngine;
public class movement : MonoBehaviour
{
//Variables
float speed = 5.0f;
public float turnSpeed = 4.0f;
public float moveSpeed = 2.0f;
public float minTurnAngle = -90.0f;
public float maxTurnAngle = 90.0f;
private float rotX;
//Other
Vector3 Char_velocity;
Rigidbody Physics;
void Start()
{
Physics = GetComponent<Rigidbody>();
}
void FixedUpdate()
{
//Get axis on which we want to move
if (Input.GetButton("Vertical"))
{
//Creating vector which velocity is calculated based on vect3, speed and gets FPS compensation via fixed
//delta time
Char_velocity = new Vector3(Input.GetAxis("Horizontal"), 0.0f, Input.GetAxis("Vertical"));
Physics.MovePosition(transform.position + Char_velocity * speed * Time.fixedDeltaTime);
}
if (Input.GetButton("Horizontal"))
{
Char_velocity = new Vector3(Input.GetAxis("Horizontal"), 0.0f, Input.GetAxis("Vertical"));
Physics.MovePosition(transform.position + Char_velocity * speed * Time.deltaTime);
}
float y = Input.GetAxis("Mouse X") * turnSpeed;
rotX += Input.GetAxis("Mouse Y") * turnSpeed;
rotX = Mathf.Clamp(rotX, minTurnAngle, maxTurnAngle);
transform.eulerAngles = new Vector2(-rotX, transform.eulerAngles.y + y);
}
}
Mouse movement has been the problem for me. I got it to somewhat work but I have 2 issues. The camera should move the whole body while rotating from side to side but instead rotates the body when moving along the Y axis.

How can I rotate a steering wheel to its original rotation in Unity3D?

I have been searching for an answer for hours, and I cant find the solution. I have a script that rotates a steering wheel when the car is turning. It has maximum rotation of 120 and a minimum rotation of -120. I have been trying to rotate with RotateTowards and other Quaternion methods but I can't figure them out. How can I make it when the turning angle is not zero and turning keys (a and d) are not pressed, it goes to its original rotation of 0, 0, 0, at a certain speed?
Here's my script for the steering wheel:
if (horizontalInput > 0)
{
wheel.transform.Rotate(Vector3.forward*Time.deltaTime*wheelspeed);
rotations -= wheelspeed;
}
else if (horizontalInput < 0)
{
wheel.transform.Rotate(-Vector3.forward * Time.deltaTime * wheelspeed);
rotations += wheelspeed;
}
And here is my (very bad) script for the minimum and maximum rotation of the steering wheel:
angle += Input.GetAxis("Horizontal") * Time.deltaTime*10;
angle = Mathf.Clamp(-120, angle, 120);
wheel.transform.localRotation = Quaternion.AngleAxis(angle, Vector3.forward);
angle += Input.GetAxis("Horizontal") * Time.deltaTime * 400;
angle = Mathf.Clamp(120, 0, angle);
wheel.transform.localRotation = Quaternion.AngleAxis(angle, Vector3.forward);
You should do something like this for the steering wheel script:
float rotateBack = 0f;
float angle = 0f;
void Update(){
angle += Input.GetAxis(“Horizontal”) * Time.deltaTime * 10;
angle = Mathf.Clamp(angle, -120, 120);
if (Input.GetAxis(“Horizontal”) == 0 && angle != 0f){
angle += rotateBack * Time.deltaTime;
if (angle > 0){
rotateBack = -10f;
}
else{
rotateBack = 10f;
}
}
transform.rotation = Quaternion.Euler(0f, 0f, angle);
}
What this does is setting a variable of angle to the input times a value to increase turning speed. Then it clamps if so it doesn’t go over 120, or under -120. Then, if there is no input and the angle isn’t zero: it will change the variable by how much to rotate back. The rotate back variable is set to either positive or negative based on which direction the steering wheel needs to be turned. The only problem with this script that I could see is the angle not being exactly 0, continuing the if statement. If this error affects your game, use the Mathf.Round(); function.
(If there are any errors, comment on this post.)
I would do what #ken is doing, but refactor the speed factors and other constants into fields so they are more easily changed and also use Mathf.MoveTowardsAngle to move the angle back to its neutral position.
[SerializeField] float rotateBackSpeed = 3f; // degrees per second
[SerializeField] float rotateSpeed = 10f; // degrees per second
[SerializeField] float angle = 0f; // degrees
[SerializeField] float minAngle = -120f; // degrees
[SerializeField] float maxAngle = 120f; // degrees
[SerializeField] float neutralAngle = 0f; // degrees
void Update()
{
angle = Mathf.Clamp(angle + Input.GetAxis(“Horizontal”) * rotateSpeed
* Time.deltaTime, minAngle, maxAngle);
if (Mathf.Approximately(0f, Input.GetAxis(“Horizontal”)))
{
angle = Mathf.MoveTowardsAngle(angle, neutralAngle,
rotateBackSpeed * Time.deltaTime);
}
transform.eulerAngles = angle * Vector3.forward;
}

rotation to original position in unity 3d

I was making a game where there is a plane which I control using the wasd keys, it rotates and translates. Up-to that its fine, but I would like the plane to re-align to its original rotation when I lift the key. The code I made up is this but it doesn't work. The plane realigns for only one frame and then "misaligned" again . This is the code -**
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class planemovement : MonoBehaviour
{
public int fspeed = 10;
float horizontal; float zrot;
float vertical; float yrot;
public float sense; public int lim = 0;
void Start()
{
}
// Update is called once per frame
void Update()
{
float rotz = Input.GetAxis("Vertical"); float roty = Input.GetAxis("Horizontal");
horizontal = Input.GetAxis("Horizontal");
vertical = Input.GetAxis("Vertical");
transform.Translate(Vector3.forward * fspeed * Time.deltaTime);
transform.Translate(Vector3.right * sense * Time.deltaTime * horizontal*20f);
transform.Translate(Vector3.up * sense * Time.deltaTime * vertical);
zrot -= rotz;
yrot -= roty;
zrot = Mathf.Clamp(zrot, -lim, lim);
yrot = Mathf.Clamp(yrot, -lim, lim);
transform.localRotation = Quaternion.Euler(zrot, 0f, yrot);
}
}
Rotation in Unity C# is usually quite wonky, the only time it is exact is when you use Quaternions properly.
public Quaternion startQuaternion;
void Start() {
startQuaternion = transform.rotation;
}
//when you want to reset to original
transform.rotation = startQuaternion;
https://docs.unity3d.com/ScriptReference/Quaternion.html
I don't quite understand, but if you using a rigidbody, you can try using "Quaternion.Lerp" and MoveRotation.
Quaternion.Lerp has three parameters and creates a rotation from point A to B, with a speed ugual to T (T goes from 0 to 1).
var currentRot = transform.rotation
var desired Rot = rotation on which the plane must be aligned
Quaternion RotPlane = Quaternion.Lerp (currentRot, desiredRot, 0.5)
MoveRotation(RotPlane)
You can use an if (Input.GetKeyUp) and put the script underneath it, so every time you release the buttons the plane returns to the desired rotation.

How can i prevent from moving the camera view under the terrain?

I'm using this mouseorbit script attached to a camera.
The problem is when i move the camera with the mouse and rotating it so the camera is under the terrain.
I want that when it get to the terrain height then stop don't move down i mean don't get to this view under the character maximum to be in the terrain height..
To stop on terrain height i mean something like that when it's getting to this:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MouseOrbit : MonoBehaviour
{
/* These variables are what tell the camera how its going to function by
* setting the viewing target, collision layers, and other properties
* such as distance and viewing angles */
public Transform viewTarget;
public LayerMask collisionLayers;
public float distance = 6.0f;
public float distanceSpeed = 150.0f;
public float collisionOffset = 0.3f;
public float minDistance = 4.0f;
public float maxDistance = 12.0f;
public float height = 1.5f;
public float horizontalRotationSpeed = 250.0f;
public float verticalRotationSpeed = 150.0f;
public float rotationDampening = 0.75f;
public float minVerticalAngle = -60.0f;
public float maxVerticalAngle = 60.0f;
public bool useRMBToAim = false;
/* These variables are meant to store values given by the script and
* not the user */
private float h, v, smoothDistance;
private Vector3 newPosition;
private Quaternion newRotation, smoothRotation;
private Transform cameraTransform;
/* This is where we initialize our script */
void Start()
{
Initialize();
}
/* This is where we set our private variables, check for null errors,
* and anything else that needs to be called once during startup */
void Initialize()
{
h = this.transform.eulerAngles.x;
v = this.transform.eulerAngles.y;
cameraTransform = this.transform;
smoothDistance = distance;
NullErrorCheck();
}
/* We check for null errors or warnings and notify the user to fix them */
void NullErrorCheck()
{
if (!viewTarget)
{
Debug.LogError("Please make sure to assign a view target!");
Debug.Break();
}
if (collisionLayers == 0)
{
Debug.LogWarning("Make sure to set the collision layers to the layers the camera should collide with!");
}
}
/* This is where we do all our camera updates. This is where the camera
* gets all of its functionality. From setting the position and rotation,
* to adjusting the camera to avoid geometry clipping */
void LateUpdate()
{
if (!viewTarget)
return;
/* We check for right mouse button functionality, set the rotation
* angles, and lock the mouse cursor */
if (!useRMBToAim)
{
/* Check to make sure the game isn't paused and lock the mouse cursor*/
if (Time.timeScale > 0.0f)
Cursor.lockState = CursorLockMode.Locked;
h += Input.GetAxis("Mouse X") * horizontalRotationSpeed * Time.deltaTime;
v -= Input.GetAxis("Mouse Y") * verticalRotationSpeed * Time.deltaTime;
h = ClampAngle(h, -360.0f, 360.0f);
v = ClampAngle(v, minVerticalAngle, maxVerticalAngle);
newRotation = Quaternion.Euler(v, h, 0.0f);
}
else
{
if (Input.GetMouseButton(1))
{
/* Check to make sure the game isn't paused and lock the mouse cursor */
if (Time.timeScale > 0.0f)
Cursor.lockState = CursorLockMode.Locked;
h += Input.GetAxis("Mouse X") * horizontalRotationSpeed * Time.deltaTime;
v -= Input.GetAxis("Mouse Y") * verticalRotationSpeed * Time.deltaTime;
h = ClampAngle(h, -360.0f, 360.0f);
v = ClampAngle(v, minVerticalAngle, maxVerticalAngle);
newRotation = Quaternion.Euler(v, h, 0.0f);
}
else
{
Cursor.lockState = CursorLockMode.Confined;
}
}
/* We set the distance by moving the mouse wheel and use a custom
* growth function as the time value for linear interpolation */
distance = Mathf.Clamp(distance - Input.GetAxis("Mouse ScrollWheel") * 10, minDistance, maxDistance);
smoothDistance = Mathf.Lerp(smoothDistance, distance, TimeSignature(distanceSpeed));
/*We give the rotation some smoothing for a nicer effect */
smoothRotation = Quaternion.Slerp(smoothRotation, newRotation, TimeSignature((1 / rotationDampening) * 100.0f));
newPosition = viewTarget.position;
newPosition += smoothRotation * new Vector3(0.0f, height, -smoothDistance);
/* Calls the function to adjust the camera position to avoid clipping */
CheckSphere();
smoothRotation.eulerAngles = new Vector3(smoothRotation.eulerAngles.x, smoothRotation.eulerAngles.y, 0.0f);
cameraTransform.position = newPosition;
cameraTransform.rotation = smoothRotation;
}
/* This is where the camera checks for a collsion hit within a specified radius,
* and then moves the camera above the location it hit with an offset value */
void CheckSphere()
{
/* Add height to our spherecast origin */
Vector3 tmpVect = viewTarget.position;
tmpVect.y += height;
RaycastHit hit;
/* Get the direction from the camera position to the origin */
Vector3 dir = (newPosition - tmpVect).normalized;
/* Check a radius for collision hits and then set the new position for
* the camera */
if (Physics.SphereCast(tmpVect, 0.3f, dir, out hit, distance, collisionLayers))
{
newPosition = hit.point + (hit.normal * collisionOffset);
}
}
/* Keeps the angles values within their specificed minimum and maximum
* inputs while at the same time putting the values back to 0 if they
* go outside of the 360 degree range */
private float ClampAngle(float angle, float min, float max)
{
if (angle < -360)
angle += 360;
if (angle > 360)
angle -= 360;
return Mathf.Clamp(angle, min, max);
}
/* This is our custom logistic growth time signature with speed as input */
private float TimeSignature(float speed)
{
return 1.0f / (1.0f + 80.0f * Mathf.Exp(-speed * 0.02f));
}
}
It looks like the script i grabbed already handles terrain collision... i just need to make sure to set the collision layers on it to include the terrain. But not sure how to do it.
What i tried:
I added now a new layer in the inspector called it Terrain.
Then in the hierarchy on the Terrain i change it's layer to terrain.
Also in the script i selected Terrain. But it's still not working.
In the screenshot the top is the inspector of the camera with the script selected in Collision Layers Terrain.
In the bottom the terrain inspector selected Terrain as :Layer
Many answers to this question.
But here's my take on it since I am fairly certain this solution is interesting compared to all solutions you will find online related to unity 3rd person controllers.
Abstract
In Unity, you could take the usual approach of having an empty object and having that be the parent of the camera, etc... But what people in the industry would typically do is use a mathematical formula called Spherical coordinates.
This, by far, is the best approach for 3rd person controllers from my experience and its the most elegant and beautiful approach. All you need to do after implementing this approach, is shoot a ray from the camera down a meter, and if it detects anything, then change a single value in your spherical coordinates formula which would make the sphere around the player smaller which gives the effect of collision
Implementation
Here's a snippet of code which shows a single function needed to create the spherical coordinates formula.
private Vector3 sphericalCoordinates()
{
float m = distanceFromPlayer;
v = mouseY;
h = mouseX;
x = m * Mathf.Sin(v) * Mathf.Cos(h);
z = m * Mathf.Sin(v) * Mathf.Sin(h);
y = m * Mathf.Cos(v);
Vector3 pos = new Vector3(x, y, z);
return pos;
}
m = magnitude.
You can think of this as the radius from the sphere, this is what you change to give the collision effect.
I later use this snippet to bind the player with the camera in trigger.
[NOTE: very unclean code, I just wanted to show you a basic concept, definitely make it cleaner later]
Vector3 getCamRotWORLD()
{
Vector3 camRotWORLD = new Vector3(0, transform.rotation.eulerAngles.y, 0);
return camRotWORLD;
}
void adjustPlayerRot()
{
if (Input.GetKey(KeyCode.W))
{
player.transform.rotation = Quaternion.Euler(player.transform.rotation.x, getCamRotWORLD().y, player.transform.rotation.z);
player.transform.Translate(0, 0, playerScript.speed * Time.deltaTime);
}
if (Input.GetKey(KeyCode.A))
{
player.transform.rotation = Quaternion.Euler(player.transform.rotation.x, getCamRotWORLD().y - 90, player.transform.rotation.z);
player.transform.Translate(0, 0, playerScript.speed * Time.deltaTime);
}
if (Input.GetKey(KeyCode.S))
{
player.transform.rotation = Quaternion.Euler(player.transform.rotation.x, getCamRotWORLD().y - 180, player.transform.rotation.z);
player.transform.Translate(0, 0, playerScript.speed * Time.deltaTime);
}
if (Input.GetKey(KeyCode.D))
{
player.transform.rotation = Quaternion.Euler(player.transform.rotation.x, getCamRotWORLD().y + 90, player.transform.rotation.z);
player.transform.Translate(0, 0, playerScript.speed * Time.deltaTime);
}
}
Here's how my update looks like in case you really would like to snatch the implementation instead of trying to experiment with it yourself.
private void Update()
{
mouseX -= Input.GetAxis("Mouse X") * (sensitivity * Time.deltaTime);
mouseY -= Input.GetAxis("Mouse Y") * (sensitivity * Time.deltaTime);
playerCam.transform.LookAt(player.transform);
playerCam.transform.position = new Vector3(
sphericalCoordinates().x + player.transform.position.x,
sphericalCoordinates().y,
sphericalCoordinates().z + player.transform.position.z
);
}

Unity 3d Follow camera

Have this camera working perfect on my target object with one caveat. Can't seem to get the character to look up and down. Left and right move perfectly, up and down don't move at all. What am I doing wrong with the "Mouse Y" part?
public GameObject target;
public float rotateSpeed = 7;
Vector3 offset;
void Start() {
offset = target.transform.position - transform.position;
}
void LateUpdate() {
float horizontal = Input.GetAxis("Mouse X") * rotateSpeed * Time.deltaTime;
float verticle = Input.GetAxis("Mouse Y") * rotateSpeed * Time.deltaTime;
target.transform.Rotate(0, horizontal, 0);
float desiredAngle = target.transform.eulerAngles.y;
Quaternion rotation = Quaternion.Euler(0, desiredAngle, verticle);
transform.position = target.transform.position - (rotation * offset);
transform.LookAt(target.transform);
}
You're not using verticle in your Transform.Rotate call (vertical?).
Edit: Sorry, I just stopped looking once I got to the first problem, looking further there's another problem that's similar to the one I addressed in this question. The "order of operations" is wrong (see the new comments):
public GameObject target;
public float rotateSpeed = 7;
Vector3 offset;
void Start() {
offset = target.transform.position - transform.position;
}
void LateUpdate() {
float horizontal = Input.GetAxis("Mouse X") * rotateSpeed * Time.deltaTime;
float verticle = Input.GetAxis("Mouse Y") * rotateSpeed * Time.deltaTime;
//You didn't use verticle before. Depending on your model/setup you might verticle as the 3rd (Z) parameter to get rotation in the right direction
target.transform.Rotate(verticle, horizontal, 0); //This line rotates the transform
float desiredAngle = target.transform.eulerAngles.y;
Quaternion rotation = Quaternion.Euler(0, desiredAngle, verticle);
transform.position = target.transform.position - (rotation * offset);
transform.LookAt(target.transform); //This sets the absolute rotation of the transform. This call will "override" the above call to Rotate
}
To give more information you'll have to give an explanation of what your end goal is with this code, because looking closer I'm seeing "oddities" with what the code sample is trying to do.
The last line of code (transform.Lookat) overrides the previous code ... basically says 'always look at the target no matter what else happens'.

Categories

Resources