So I'm writing a script that's able to rotate an object using mouse movements.
The scene I have set up is a camera with an object in front of it. Moving the mouse left results in the object rotating left, moving the mouse up results in the object rotating up, etc. Now I have a little problem. When I rotate the object 90 degrees left or right and then rotate it up or down it rotates around the Z axis instead of the X axis like I want it to. This happens because the Z and X axis ofcourse rotate with the Y axis I manipulated earlier when rotating it left or right.
I made two gifs showcasing the problem:
What I want to always happen: https://media.giphy.com/media/wsUxoi9LyXXbB24PNg/giphy.gif
What's actually happening: https://media.giphy.com/media/1jl3MNtMuXW9AAUQOZ/giphy.gif
Here's the code I'm currently using:
public float object_rotSens = 100.0f;
float object_rotY = 0.0f;
float object_rotX = 0.0f;
void Update()
{
object_rotX += Input.GetAxis("Mouse X") * object_rotSens * Time.deltaTime;
object_rotY += Input.GetAxis("Mouse Y") * object_rotSens * Time.deltaTime;
objectImRotating.transform.localEulerAngles = new Vector3(object_rotY, -object_rotX, 0);
}
I hope that someone can help me change the code so I have the preferred rotation even when the object is rotated any amount around the Y axis. Thanks in advance!
Update:
Chris H helped me fix the problem. For anyone who has the same problem here's what helped me fix the problem:
object_rotX = Input.GetAxis("Mouse X") * object_rotSens * Time.deltaTime;
object_rotY = Input.GetAxis("Mouse Y") * object_rotSens * Time.deltaTime;
objectImRotating.transform.RotateAround(objectImRotating.transform.position, new Vector3(object_rotY, -object_rotX, 0), 100 * Time.deltaTime);
Try using Transform.RotateAround instead localEulerAngles.
Related
Im building a first person controller in unity and im trying to make it so the camera tracks mouse location so the player could look around but when i try and block the player from getting the camera upside down the camra starts to shake and it messes the rotation of the camera
// Get Axis
float mouseY = Input.GetAxis(mouseYInputName) * mouseSensitivity *
Time.deltaTime;
// Check if past limit
clampX += mouseY;
if (Mathf.Abs(clampX) < 90f) {
transform.Rotate(Vector3.left * mouseY);
}
else {
Vector3 eulerRotation = -transform.eulerAngles;
eulerRotation.x = (clampX > 90f ? 270f : 90f);
transform.eulerAngles = eulerRotation;
}
There's a few problems with your code.
First, you negative the rotation when assigning it to your vector (-transform.eulerAngles) but then just re-assign the modified vector to the transform. This will have the effect of flipping the Y and Z axis rotations every frame.
Additionally, the clamping itself looks a bit off. Right now, when the rotation is moved past 90°, it is snapped to 270° instantly, and when it's moved past -90°, it is snapped to 90° instead.
Another issue:
With your current code, if the player keeps moving the mouse after clamping has already started, the clampX variable is still being increased. Thus when the player starts moving the mouse in the other direction, they have to get the clampX value back into acceptable values again before the camera even starts moving.
Here's an approach to clamping that hopefully doesn't have those issues:
clampX += mouseY;
if (Mathf.Abs(clampX) < 90f) {
transform.Rotate(Vector3.right * mouseY);
}
else {
if (clampX > 90f) clampX = 90f;
else if (clampX < -90f) clampX = -90f;
Vector3 eulerRotation = transform.eulerAngles;
eulerRotation.x = clampX;
transform.eulerAngles = eulerRotation;
}
(I also replaced Vector3.left with Vector3.right, since that's positive X in Unity.)
right now I am trying to make rts camera zooming with panning when close to the ground. The problem I have now is that I use mouse scroll wheel for zooming and it makes the zooming feel like it's laggy. It looks like it jumps some Y value and teleports rather than smoothly move to the desired position. Also, I would like to know how to make the camera stop at the minimum Y value because what is happening now is that it stops at around 22 rather than 20 which is my minimum Y value for the camera movement.
I tried zooming with + and - on my numpad and it worked how I wanted (smoothly zooming in and out without skipping) but not all players have numpad and I feel it is more suitable to zoom with mouse wheel.
{
private const int levelArea = 100;
private const int scrollArea = 25;
private const int scrollSpeed = 25;
private const int dragSpeed = 70;
private const int zoomSpeed = 50;
// Maximum/minimum zoom distance from the ground
public int zoomMin = 20;
public int zoomMax = 120;
private const int panSpeed = 40;
// Minimal/maximal angles for camera
private const int panAngleMin = 30;
private const int panAngleMax = 90;
void Update()
{
// Init camera translation for this frame.
var translation = Vector3.zero;
// Zoom in or out
var zoomDelta = Input.GetAxis("Mouse ScrollWheel") * zoomSpeed * Time.deltaTime;
if (zoomDelta != 0)
{
translation -= Vector3.up * zoomSpeed * zoomDelta;
}
// Start panning camera if zooming in close to the ground or if just zooming out.
var pan = transform.eulerAngles.x - zoomDelta * panSpeed;
pan = Mathf.Clamp(pan, panAngleMin, panAngleMax);
// When to start panning up the camera
if (zoomDelta < 0 || transform.position.y < (zoomMax -20))
{
transform.eulerAngles = new Vector3(pan, 0, 0);
}
// Move camera with arrow keys
translation += new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
// Move camera with mouse
if (Input.GetMouseButton(2)) // MMB
{
// Hold button and drag camera around
translation -= new Vector3(Input.GetAxis("Mouse X") * dragSpeed * Time.deltaTime, 0,
Input.GetAxis("Mouse Y") * dragSpeed * Time.deltaTime);
}
else
{
// Move camera if mouse pointer reaches screen borders
if (Input.mousePosition.x < scrollArea)
{
translation += Vector3.right * -scrollSpeed * Time.deltaTime;
}
if (Input.mousePosition.x >= Screen.width - scrollArea)
{
translation += Vector3.right * scrollSpeed * Time.deltaTime;
}
if (Input.mousePosition.y < scrollArea)
{
translation += Vector3.forward * -scrollSpeed * Time.deltaTime;
}
if (Input.mousePosition.y > Screen.height - scrollArea)
{
translation += Vector3.forward * scrollSpeed * Time.deltaTime;
}
}
// Keep camera within level and zoom area
var desiredPosition = transform.position + translation;
if (desiredPosition.x < -levelArea || levelArea < desiredPosition.x)
{
translation.x = 0;
}
if (desiredPosition.y < zoomMin || zoomMax < desiredPosition.y)
{
translation.y = 0;
}
if (desiredPosition.z < -levelArea || levelArea < desiredPosition.z)
{
translation.z = 0;
}
// Move camera parallel to world axis
transform.position += translation;
}
}
I would like to have a smooth transition from the position where the camera is now and the desired position after scrolling in/out. And also I would like to know how to make the camera to stop at the minimum/maximum zoom distance rather than to stop close to it. Thank you for helping. Video how the camera movement looks like: https://youtu.be/Lt3atJEaOjA
Okay, so I'm going to do three things here. First and foremost, I'm going to recommend that if you're working with advanced camera behavior, you probably want to at least consider using Cinemachine. I'd walk you through it myself, but given my lack of personal experience with it, I'd probably be doing you a disservice by even trying. There are many good tutorials out there. Youtube and Google should provide.
The second thing I'll do is solve your problem in the most direct way I can manage, and after that, we'll see if we can't come up with a better method for solving your problem.
So, the key here is that Unity's scrollwheel input is pretty binary. When you check a scrollwheel axis, the result is directly based on how many "clicks" your wheel has gone through since the last frame update, but what you really want is something with a bit of give. By default, Unity can actually do this with most of its axis inputs: You might notice that if you use WASD in a default Unity project, there's a sort of "lag" to it, where you'll take your fingers off the keys but you'll still keep receiving positive values from Input.GetAxis() for a few frames. This is tied to the Gravity value in your input settings, and Input.GetAxisRaw() is actually used to circumvent this entirely. For whatever reason, scrollwheel axes don't seem to be affected by axis gravity, so we essentially have to implement something similar ourselves.
// Add this property to your class definition (so it persists between updates):
private float wheelAxis = 0;
// Delete this line:
var zoomDelta = Input.GetAxis("Mouse ScrollWheel") * zoomSpeed * Time.deltaTime;
// And put these three new lines in its place:
wheelAxis += Input.GetAxis("Mouse ScrollWheel");
wheelAxis = Mathf.MoveTowards(wheelTotal, 0f, Time.deltaTime);
var zoomDelta = Mathf.Clamp(wheelAxis, -0.05f, 0.05f) * zoomSpeed * Time.deltaTime;
Right, so we do a few things here. Every update, we add the current scrollwheel values to our wheelAxis. Next, we apply the current Time.deltatime as "gravity" via the Mathf.MoveTowards() function. Finally, we call what's mostly just your old zoomDelta code with a simple modification: We constrain the wheelAxis with Mathf.Clamp to try and regulate how fast the zooming can happen.
You can modify this code in a couple of ways. If you multiply the Time.deltaTime parameter you can affect how long your input will "persist" for. If you mess with the Mathf.Clamp() values, you'll get faster or slower zooming. All in all though, if you just want a smooth zoom with minimal changes to your code, that's probably your best bet.
So!
Now that we've done that, let's talk about your code, and how you're approaching the problem, and see if we can't find a somewhat cleaner solution.
Getting a good camera working is surprisingly non-trivial. Your code looks like a lot of code I see that tries to solve a complex problem: It looks like you added some feature, and then tested it, and found some edge cases where it fell apart, and then patched those cases, and then tried to implement a new feature on top of the old code, but it kinda broke in various other ways, etc etc, and what we've got at this point is a bit messy.
The biggest issue with your code is that the camera's position and the camera's rotation are closely tied together. When working with characters, this is usually fine, but when working with a camera, you want to break this up. Think about where the camera is and what the camera is looking at as very separate things to keep track of.
So here's a working camera script that you should be able to plug in and just run with:
using UnityEngine;
public class RTSCamera : MonoBehaviour
{
public float zoomSpeed = 100f;
public float zoomTime = 0.1f;
public float maxHeight = 100f;
public float minHeight = 20f;
public float focusHeight = 10f;
public float focusDistance = 20f;
public int panBorder = 25;
public float dragPanSpeed = 25f;
public float edgePanSpeed = 25f;
public float keyPanSpeed = 25f;
private float zoomVelocity = 0f;
private float targetHeight;
void Start()
{
// Start zoomed out
targetHeight = maxHeight;
}
void Update()
{
var newPosition = transform.position;
// First, calculate the height we want the camera to be at
targetHeight += Input.GetAxis("Mouse ScrollWheel") * zoomSpeed * -1f;
targetHeight = Mathf.Clamp(targetHeight, minHeight, maxHeight);
// Then, interpolate smoothly towards that height
newPosition.y = Mathf.SmoothDamp(transform.position.y, targetHeight, ref zoomVelocity, zoomTime);
// Always pan the camera using the keys
var pan = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical")) * keyPanSpeed * Time.deltaTime;
// Optionally pan the camera by either dragging with middle mouse or when the cursor touches the screen border
if (Input.GetMouseButton(2)) {
pan -= new Vector2(Input.GetAxis("Mouse X"), Input.GetAxis("Mouse Y")) * dragPanSpeed * Time.deltaTime;
} else {
var border = Vector2.zero;
if (Input.mousePosition.x < panBorder) border.x -= 1f;
if (Input.mousePosition.x >= Screen.width - panBorder) border.x += 1f;
if (Input.mousePosition.y < panBorder) border.y -= 1f;
if (Input.mousePosition.y > Screen.height - panBorder) border.y += 1f;
pan += border * edgePanSpeed * Time.deltaTime;
}
newPosition.x += pan.x;
newPosition.z += pan.y;
var focusPosition = new Vector3(newPosition.x, focusHeight, newPosition.z + focusDistance);
transform.position = newPosition;
transform.LookAt(focusPosition);
}
}
While I encourage you to go through it in your own time, I'm not going to drag you through every inch of it. Instead, I'll just go over the main crux.
The key idea here is that rather than controlling the camera's height and orientation directly, we just let the scrollwheel dictate where want the camera's height to be, and then we use Mathf.SmoothDamp() to move the camera smoothly into that position over several frames. (Unity has many useful functions like this. Consider Mathf.MoveTowards() for an alternative interpolation method.) At the very end, rather than trying to fiddle with the camera's rotation values directly, we just pick a point in front of us near the ground and point the camera at that spot directly.
By keeping the camera's position and orientation completely independent of each other, as well as separating out the "animation" of the camera's height, we avoid a lot of headaches and eliminate a lot of potential for messy interwoven bugs.
I hope that helps.
Cinemachine is pretty good, but I suggest people learn on your own quite a bit before using Cinemachine so that you better understand what's going on in the background.
I have a moving object (the ship) with several child objects (its turrets). The turrets are to rotate towards the player object regardless of the direction the ship is facing. The problem is, unless the ship is rotated straight up, the turrets just spin around wildly.
The code to rotate the turrets is as follows:
//Rotate towards player
dir = PlayerScript.GlobalVariables.playerPosition - myPosition;
angleToTarget = Vector2.Angle(dir, transform.up);
angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg - 90;
transform.localRotation = Quaternion.RotateTowards(transform.rotation, Quaternion.AngleAxis(angle, Vector3.forward), turnSpeed);
The ship is instantiated with this code. Changing rotation changes the intitial rotation. At rotation = 180 it is rotated vertically up:
newEnemyShip = Object.Instantiate(enemyShip2, new Vector3(mousePosition.x, mousePosition.y, 0), Quaternion.Euler(210, 0, rotation));
The ship has an initial rotation which never changes. It's movement code is:
//moves in straight line at constant speed
transform.Translate(Vector3.up * currentSpeed * Time.deltaTime, Space.Self);
It also has this to lock it onto the 2d plane:
//Lock rotation on 2d plane
Quaternion q = transform.rotation;
q.eulerAngles = new Vector3(0, 0, q.eulerAngles.z);
transform.rotation = q;
Any help would be great appreciated!
I figured it out! I'm not sure what the problem was with the original code, but the following code for the turret worked:
//Rotate towards player
dir = PlayerScript.GlobalVariables.playerPosition - myPosition;
angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg - 90;
transform.rotation = Quaternion.RotateTowards(transform.rotation, Quaternion.AngleAxis(angle, Vector3.forward), turnSpeed);
Bijan, thanks for taking the time to look and respond
Why you just don't use transform.LookAt(playerPosition) ?
I am trying to make a first person character controller and I am having an issue smoothing my look rotation. My LookRotation script takes my mouses input and uses it to rotate the character. I have used Quaternion.Slerp() in order to smooth my look rotation although there is an issue with using Quaternion.Slerp(). Once the desired rotation becomes larger than 180 degrees away from the current rotation it will just take the shorter route (the opposite way). The issue is in the last two lines of code. Does anyone have a method to prevent this issue? Thanks in advance!
#region Variables
// Sensitivity variables
[Range(0.0f, 10.0f)] public float horizontalSensitivity = 2.0f, verticalSensitivity = 2.0f;
// Smoothing Variables
[Range(0.0f, 30.0f)] public float smoothAmount = 5.0f;
// Character rotation variables
private Quaternion characterTargetRotation;
private Quaternion cameraTargetRotation;
public Transform character;
public Transform characterCamera;
#endregion
private void Update()
{
float horizontalRotation = Input.GetAxis("Mouse X") * horizontalSensitivity;
float verticalRotation = Input.GetAxis("Mouse Y") * verticalSensitivity;
characterTargetRotation *= Quaternion.Euler(0.0f, horizontalRotation, 0.0f);
cameraTargetRotation *= Quaternion.Euler(-verticalRotation, 0.0f, 0.0f);
character.localRotation = Quaternion.Lerp(_character.localRotation, characterTargetRotation, smoothAmount * Time.deltaTime);
characterCamera.localRotation = Quaternion.Lerp(_characterCamera.localRotation, cameraTargetRotation, smoothAmount * Time.deltaTime);
}
Can't be done using Quaternions and Lerp, I solved this using Euler angles.
horizontalAngle += (Input.GetAxis("Mouse X") * horizontalSensitivity);
verticalAngle += (-Input.GetAxis("Mouse Y") * verticalSensitivity);
horizontalSmoothAngle = Mathf.Lerp(horizontalSmoothAngle, horizontalRotation, smoothAmount * Time.deltaTime);
verticalSmoothAngle = Mathf.Lerp(verticalSmoothAngle , verticalRotation, smoothAmount * Time.deltaTime);
horizontalRotation = Quaternion.Euler(0, smoothRotation ? horizontalSmoothAngle: horizontalAngle, 0);
verticalRotation = Quaternion.Euler(smoothRotation ? verticalSmoothAngle : verticalAngle, 0, 0);
Using this method creates alternate issues you must deal with but using Quaternions and Lerp has an issue that cant be solved. As commented above, Lerp will always find the shortest distance. Eulers can go as high as you want (or more precisely as high as the data type allows) so it doesn't have this issue.
Although it is unlikely this will ever be an issue for a normal player if you keep adding to the angle, the data type in this case float, will eventually lose precision. To avoid this simply minus or add (dependent on whether the value is positive or negative) any multiple of 360 from the angle and the smoothed angle at a number high enough as to not effect the Lerp.
Getting the starting angle on the x axis using this method has some issues but is irrelevant to this question so anyone wanting to know how to do that will have to find an alternative source for the solution to that issue.
I'm using Unity 5 (latest version), and I'm trying to make a conveyor belt type of thing. To do so I want to have cylinders rotate on the z-axis, and only the z-axis. How would I do that?
You can use the transform.Rotate method to rotate an object around a fixed axis.
The method has various constructors but a simple way to achieve what you want would be using the following depending on the axis you actually want to rotate the object around.
using UnityEngine;
using System.Collections;
public class RotateCylinder : MonoBehaviour
{
// rotation speed in degrees per second.
private float rotationSpeed = 1f;
void Update()
{
// Use one of the following depending on the axis you want to rotate the object, this will depend on how your object is transformed.
// Rotate around X Axis
transform.Rotate(Vector3.right * rotationSpeed * Time.deltaTime);
// Rotate around Y Axis
transform.Rotate(Vector3.up * rotationSpeed * Time.deltaTime);
// Rotate around Z Axis
transform.Rotate(Vector3.forward * rotationSpeed * Time.deltaTime);
}
}
Well, it was rather hard to rotate GameObject ONLY in one axis, nothing worked correctly, adding values to another axis, even RotateAround or Rotate, but...
Vector3 v = transform.localRotation.eulerAngles;
transform.localRotation = Quaternion.Euler(v.x + dx, v.y + dy, v.z + dz);
dx, dy, dz - how much you want to change value in degrees.