Unity: Camera is jumpy/jerky when using touch grab to move rotation - c#

Using Unity, I'm trying to move my turrets rotation using first person camera by using touch, I grab from side of screen to center from the direction i want to go to like in google maps, the touch grab will move my camera there smoothly.
Problem is the camera being very jerky, not smooth in position.
In Global
private float smoothTouchFactor = 5;
This is the code I am using in Update()
Quaternion rotation;
if (Input.touchCount > 0)
{
if (Input.GetTouch(0).phase == TouchPhase.Moved)
{
Touch touch = Input.GetTouch(0);
x += touch.deltaPosition.x * xSpeed * 0.02f;
y -= touch.deltaPosition.y * ySpeed * 0.02f;
rotation = Quaternion.Euler(y, x, 0f);
turretHeadToMove.rotation = Quaternion.Slerp(turretHeadToMove.rotation, rotation, Time.deltaTime * smoothTouchFactor);
}
}
rotation = Quaternion.Euler(y, x, 0f);
turretHeadToMove.rotation = Quaternion.Slerp(turretHeadToMove.rotation, rotation, Time.deltaTime * smoothTouchFactor);
If I dont add the last 2 lines after the if(touchCount) then the camera goes back to initial position after i lift my finger off the screen.
Here is the mouse axis Mouse X/Y code that works and moves the turret with no jerky motion, smoothly.
private float xSpeed = 40.0f;
private float ySpeed = 40.0f;
in Update()
x += Input.GetAxis("Mouse X") * xSpeed * 0.02f;
y -= Input.GetAxis("Mouse Y") * ySpeed * 0.02f;
Quaternion rotation = Quaternion.Euler(y, x, 0f);
turretHeadToMove.rotation = rotation;

From your description of needing those "two lines" at the end, it sounds like you have code elsewhere that's constantly trying to reset their rotation. So, we add a variable to keep rotation consistent with the Quaternion set the last time the touch control was done, and just be sure to assign it to turretHeadToMove.rotation before handling the touch controls.
Then, you can use Euler to create a Quaternion of how much you want to rotate this frame. And then multiply the current rotation by that amount in order to get the new rotation
Altogether this might look like:
As a field:
private Quaternion savedRotation = Quaternion.identity;
In Update:
// Add it before the block so that modifications to `localRotation` are consistent with memory
turretHeadToMove.rotation = savedRotation;
if (Input.touchCount > 0)
{
if (Input.GetTouch(0).phase == TouchPhase.Moved)
{
Touch touch = Input.GetTouch(0);
Quaternion previousRotation = turretHeadToMove.rotation;
Quaternion rotationThisFrame = Quaternion.Euler(
touch.deltaPosition.y * ySpeed,
touch.deltaPosition.x * xSpeed, 0f);
turretHeadToMove.rotation = previousRotation * rotationThisFrame;
savedRotation = turretHeadToMove.rotation;
}
}

Related

Rotating Camera around a player object on both X and Y axis with mouse button

I'm trying to rotate the camera angle on my third person player game object on both X and Y axes by using the middle mouse button.
I have this code below.
public class CameraControl : MonoBehaviour
{
public Transform player;
public Vector3 offset;
public float pitch = 2f;
private float turnSpeed = 5f;
private float currentZoom = 10f;
void LateUpdate()
{
if (Input.GetMouseButton(2))
{
offset = Quaternion.AngleAxis(Input.GetAxis("Mouse X") * turnSpeed, Vector3.up) * offset;
offset = Quaternion.AngleAxis(Input.GetAxis("Mouse Y") * turnSpeed, Vector3.right) * offset;
}
transform.position = player.position - offset * currentZoom;
transform.LookAt(player.position + Vector3.up * pitch);
}
}
Rotating in single axes seem to work as expected, I can rotate along X axis or Y axis without any problems, however when they mix up (moving in both directions at the same time) then some things seem to break and the rotation seems to not work as well. It's as if it loses its ability to move along the Y axis along the way.
Another problem is that I'm not quite sure how to limit the Y axis so that the rotation doesn't go below ground or above and around the player's head. I thought about using Mathf.Clamp but setting the min and the max values seems too problematic in my code as it currently is.
For the Quaternion operator * the order matters! It is currentRotation * additionalRotation.
Also usually what you want to do in order to not get a tilted rotation
rotate around the global Y axis
rotate around the local X axis
I think it should rather be something like
if (Input.GetMouseButton(2))
{
offset *= Quaternion.AngleAxis(Input.GetAxis("Mouse X") * turnSpeed, Vector3.up);
offset *= Quaternion.AngleAxis(Input.GetAxis("Mouse Y") * turnSpeed, transform.right);
}
In general I would propose a different approach
I would give the camera a parent object and place that parent object at the position where to look at (your player).
Then youove the camera on the negative local Z axis (zoom).
For the rotation you rather simply rotate the parent object.
Hierarchy
- CameraAnchor
|--Camera (With CameraControl)
and then do something like
public class CameraControl : MonoBehaviour
{
public Transform player;
public Vector3 offset;
public float pitch = 2f;
private float turnSpeed = 5f;
private float currentZoom = 10f;
void LateUpdate()
{
// Center the parent on the player with an optional offset
transform.parent.position = player.position + offset;
if (Input.GetMouseButton(2))
{
// Simply rotate the parent accordingly
// Since the camera is a child of it it will automatically be moved in an orbit
transform.parent.rotation *= Quaternion.AngleAxis(Input.GetAxis("Mouse X") * turnSpeed, Vector3.up);
transform.parent.rotation *= Quaternion.AngleAxis(Input.GetAxis("Mouse Y") * turnSpeed, transform.parent.right);
}
// For the zoom simply move the camera in its local space forward and backward
// A higher value for currentZoom means further away
// so you might want to either change the name or use something like 1/currentZoom
transform.localPosition = Vector3.back * currentZoom;
}
}

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

Camera collision detection

I'm having trouble with my Camera script. The Camera rotates around the player by using a pivot that's assigned as a child of the camera. This works with 2 arrows (I'm developing this game for mobiles, so they're touch arrows) that allow the camera to rotate left and right.
The problem is when the Camera goes behind a wall or a huge object and can't see anything. I looked for a solution and I see that many developers used the RaycastHit or something similar.
Here's my code, the goal is to get the Camera to go closer to the pivot when near a wall in order to avoid blocking the Camera view. Can anyone help me?
public Transform target1;
public Transform pivot;
protected ButtonLeft buttonLeft;
protected ButtonRight buttonRight;
public Vector3 offset;
public bool useOffsetValues;
public float rotateSpeed;
private void Start()
{
buttonLeft = FindObjectOfType<ButtonLeft>();
buttonRight = FindObjectOfType<ButtonRight>();
if (!useOffsetValues)
{
offset = target1.position - transform.position;
}
pivot.transform.position = target1.transform.position;
//pivot.transform.parent = target.transform;
//USE IF U WANT TO DISAPPEAR THE CURSOR
//Cursor.lockState = CursorLockMode.Locked;
//pivot.transform.parent = target.transform;
pivot.transform.parent = null;
// usa questa dopo la costruzione del livello1
//pivot.transform.position = target.transform.position;
}
private void Update()
{
pivot.transform.position = target1.transform.position;
if (buttonLeft.Pressed)
{
pivot.Rotate(0, -90 * Time.deltaTime, 0);
Debug.Log("rotate left");
}
if (buttonRight.Pressed)
{
pivot.Rotate(0, 90 * Time.deltaTime, 0);
Debug.Log("rotate left");
}
Ray ray = new Ray(pivot.transform.position, pivot.transform.position - transform.position);
RaycastHit hit;
/*float horizontal = Input.GetAxis("Mouse X") * rotateSpeed;
float horizontal = Input.GetAxis("Mouse X") * rotateSpeed;
pivot.Rotate(0, horizontal, 0);
pivot.Rotate(0, horizontal, 0);
Use this to make the camera rotate on Mouse Y axes*/
/*float vertical = Input.GetAxis("Mouse Y") * rotateSpeed;
target.Rotate(vertical, 0, 0); */
//move camera based on the current rotation of the target and the original offset
float desiredYAngle = pivot.eulerAngles.y;
//Use this float to set the x angle of player
float desiredXAngle = pivot.eulerAngles.x;
//Use this rotation only if you want to rotate on Y
//Quaternion rotation = Quaternion.Euler(0, desiredYAngle, 0);
//Use this if u want to rotate up&down on x axes
Quaternion rotation = Quaternion.Euler(desiredXAngle, desiredYAngle, 0);
transform.position = target1.position - (rotation * offset);
//transform.position = target.position - offset;
transform.LookAt(target1);
}
Good approach is when you do raycast from pivot to camera, and, if some obstacle found, put camera on a ray, a bit closer to pivot, than hit point. Like this: (pseudocode, not tested):
Vector3 origin = pivot.transform.position;
Vector3 direction = transform.position - pivot.transform.position;
float maxCameraDistance = 10;
Ray ray = new Ray(origin, direction);
RaycastHit hit;
if (Physics.Raycast(ray, maxCameraDistance))
{
Vector3 offsetFromObstacle = -direction.normalized * 0.1f;
transform.position = hit.point + offsetFromObstacle;
}

Why is a camera rotated around z-axis in Unity3D?

I have a main camera in Unity3D that I want to rotate depending on mouse input, so it works as a first person video-game where you move the mouse depending on where do you want to look at.
The starting values of the camera (Transform tab in Inspector tab in Unity) are:
Position: X = -1, Y = 1, Z = -11.
Rotation: X = 0, Y = 0, Z = 0.
Scale: X = 1, Y = 1, Z = 1.
I added a Script component for the Main Camera. And it is the following class:
using UnityEngine;
using System.Collections;
public class CameraMove : MonoBehaviour {
float deltaRotation = 50f;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
if(Input.GetAxis("Mouse X") < 0){
//Code for action on mouse moving left
transform.Rotate (new Vector3 (0f, -deltaRotation, 0f) * Time.deltaTime);
}
else if(Input.GetAxis("Mouse X") > 0){
//Code for action on mouse moving right
transform.Rotate (new Vector3 (0f, deltaRotation, 0f) * Time.deltaTime);
}
if(Input.GetAxis("Mouse Y") < 0){
//Code for action on mouse moving left
transform.Rotate (new Vector3 (deltaRotation, 0f, 0f) * Time.deltaTime);
}
else if(Input.GetAxis("Mouse Y") > 0){
//Code for action on mouse moving right
transform.Rotate (new Vector3 (-deltaRotation, 0f, 0f) * Time.deltaTime);
}
}
}
However, when I play the scene the camera doesn't rotate like it should. The values of the rotation change in x-axis, y-axis and even for z-axis.
What am I doing wrong?
That's a problem with how Quaternion is calculated. This happens when multiple axis are being modified. If you comment all the x rotation or the y rotation, and only rotate in one axis at a time, you will realize that this problem will go away.
To properly rotate your camera with the mouse input, use the eulerAngles or localEulerAngles variables. The option between these two depends on what you are doing.
public float xMoveThreshold = 1000.0f;
public float yMoveThreshold = 1000.0f;
public float yMaxLimit = 45.0f;
public float yMinLimit = -45.0f;
float yRotCounter = 0.0f;
float xRotCounter = 0.0f;
// Update is called once per frame
void Update()
{
xRotCounter += Input.GetAxis("Mouse X") * xMoveThreshold * Time.deltaTime;
yRotCounter += Input.GetAxis("Mouse Y") * yMoveThreshold * Time.deltaTime;
yRotCounter = Mathf.Clamp(yRotCounter, yMinLimit, yMaxLimit);
transform.localEulerAngles = new Vector3(-yRotCounter, xRotCounter, 0);
}

Resetting Position Relative to Rotation

Currently I have the following two issues:
The first issue being that when I reset my camera position I have to also reset my camera rotation. This is due to the fact that my camera
offset is set to a position slightly off of my player on the z and y
axis and obviously those values should change depending on my cameras
rotation although I am unsure how to figure out what those values
should be.
My second issue is that my rotation uses a raycast to find the middle of
the screen and determine its rotation origin although it seems to be
slightly off the middle of the screen as when it rotates the rotation
origin moves as well, if it is indeed in the middle of the screen
shouldn't it be completely still? Also is there a better and less
expensive way of achieving my desired rotation?
Relevant piece of code:
void RotateCamera()
{
//Find midle of screen
Ray ray = Camera.main.ScreenPointToRay(new Vector3(Screen.width / 2, Screen.height / 2, 0));
RaycastHit hitInfo;
//Checks if ray hit something
if (Physics.Raycast(ray, out hitInfo))
{
//Rotate left and right
if (Input.GetKey(KeyCode.RightArrow))
{
transform.RotateAround(hitInfo.point, -Vector3.up, rotationSpeed * Time.deltaTime);
}
if (Input.GetKey(KeyCode.LeftArrow))
{
transform.RotateAround(hitInfo.point, Vector3.up, rotationSpeed * Time.deltaTime);
}
}
//Draws Raycast
Debug.DrawRay(ray.origin, ray.direction * 100, Color.yellow);
}
void ResetCameraPosition()
{
//Reset and lock camera position
transform.rotation = Quaternion.identity;
transform.position = player.transform.position + cameraOffset;
}
Image displaying what I mean
Use Camera.ScreenToWorldPoint to create a 'target' in the middle of the screen around which to pivot. Remove all the raycasting stuff as you don't need it and replace the relevant bits with:
float rotationSpeed = 45; // or whatever speed
float distance = 5f; // or whatever radius for the orbit
Vector3 target = Camera.main.ScreenToWorldPoint(new Vector3(Screen.width / 2, Screen.height / 2, distance));
if (Input.GetKey(KeyCode.RightArrow))
{
transform.RotateAround(target , -Vector3.up, rotationSpeed * Time.deltaTime);
}
if (Input.GetKey(KeyCode.LeftArrow))
{
transform.RotateAround(target , Vector3.up, rotationSpeed * Time.deltaTime);
}

Categories

Resources