I have a camera facing the ground and I want to pan up to look at a target object in the distance.
Currently, I achieve this with the following:
Vector3 dir = targetPoint - transform.position;
Quaternion lookRotation = Quaternion.LookRotation(dir);
Quaternion newRotation = Quaternion.RotateTowards(transform.rotation, lookRotation, rotationDamping * Time.deltaTime);
transform.rotation = newRotation;
The camera performs the rotation and ends up pointing at the target object correctly, but as the camera pans up it tilts to one side making my game world set at an angle to the viewer, which is pretty disorienting:
How can I constrain the camera angle some way so that the horizon is always flat to the camera?
Thanks!
Update
Adding the line suggested by #Isaac below produces the correct rotation in relation to the horizon, but it snaps abruptly to z=0 at the start which is still not what I'm looking for.
transform.localEulerAngles = new Vector3 (transform.localEulerAngles.x, transform.localEulerAngles.y, 0);
There is an excellent Q/A on gamedev.stackexchange on this subject. You should try the pitch/yaw system suggested there.
Another suggestion is to correct for the roll of your camera during the rotation.
public float rollCorrectionSpeed;
public void Update()
{
float roll = Vector3.Dot(transform.right, Vector3.up);
transform.Rotate(0, 0, -roll * rollCorrectionSpeed);
Vector3 dir = targetPoint.position - transform.position;
Quaternion lookRotation = Quaternion.LookRotation(dir);
Quaternion newRotation = Quaternion.RotateTowards(transform.rotation, lookRotation, rotationDamping * Time.deltaTime);
transform.rotation = newRotation;
}
Edit:
There is an easier solution: Just keep the z rotation of the Quaternion you are rotating from to 0.
public void Update()
{
Vector3 angles = transform.rotation.eulerAngles;
Quaternion from = Quaternion.Euler(angles.x, angles.y, 0);
Vector3 dir = targetPoint.position - transform.position;
Quaternion to = Quaternion.LookRotation(dir);
transform.rotation = Quaternion.RotateTowards(from, to, rotationDamping * Time.deltaTime);
}
Upon experimentation, I found 2 possible solutions depending on what you want.
If you are just trying to follow the target I would recommend using LookAt, which automatically aligns to the world up. In your code that would be (in Update) transform.LookAt(dir);.
If you need/want the pan effect set the localEulerAngles after updating the rotation. This is what I did which worked:
//this is your code
Vector3 dir = targetPoint - transform.position;
Quaternion lookRotation = Quaternion.LookRotation(dir);
Quaternion newRotation = Quaternion.RotateTowards(transform.rotation, lookRotation, rotationDamping * Time.deltaTime);
transform.rotation = newRotation;
//this is what I added
transform.localEulerAngles = new Vector3 (transform.localEulerAngles.x, transform.localEulerAngles.y, 0);
The addition simply takes the way the camera is facing after updating the rotation using the quaternion and sets the z rotation to zero.
Let me know if you have any questions :)
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
new information/edits:
I believe I have found a solution, but it is ugly and I would appreciate feedback as to whether it stutters, etc.
This code is essentially the same as before, but now it checks to see the z angle and edits it more manually using a variable I called zDamping which affects the speed at which the camera rotates around the z access only.
I added outside of update:
public float zDamping; //public only for testing, it's convenient for finding an optimal value
private bool rotationCheck = false;
And then inside update():
//This is your code (unchanged)
Vector3 targetPoint = target.transform.position;
Vector3 dir = targetPoint - transform.position;
Quaternion lookRotation = Quaternion.LookRotation (dir);
Quaternion newRotation = Quaternion.RotateTowards (transform.rotation, lookRotation, rotationDamping * Time.deltaTime);
//This is what is new (remove my addition from before edits or it won't work)
if (transform.localEulerAngles.z >= 180f && transform.localEulerAngles.z <= 359f && !rotationCheck) {
transform.localEulerAngles = new Vector3 (transform.localEulerAngles.x, transform.localEulerAngles.y, transform.localEulerAngles.z + (rotationDamping * zDamping));
transform.rotation = newRotation;
}
else if (transform.localEulerAngles.z <= -180f && transform.localEulerAngles.z >= 1f && !rotationCheck) {
transform.localEulerAngles = new Vector3 (transform.localEulerAngles.x, transform.localEulerAngles.y, transform.localEulerAngles.z - (rotationDamping * zDamping));
transform.rotation = newRotation;
}
else {
transform.rotation = newRotation;
transform.localEulerAngles = new Vector3 (transform.localEulerAngles.x, transform.localEulerAngles.y, 0);
rotationCheck = true;
}
As I said, this solution is pretty ugly but it might work. You'll have to see what zDamping values work for your speeds to look natural (I recommend starting with .01). There will also be a small "jump" once you get close to the value, but the closer you make 359f to 360 and 1f to 0 the smaller that jump will be. The danger with making it too small is if you overshoot, but it should work even if it overshoots, but it will take a small amount of time.
Test it out and let me know what you think, sorry I couldn't find something more elegant right now. I also experimented with adding a separate Quaternion to exclusively rotate the z axis, but it did not work; feel free to experiment with that and if you want I can give more details about what I did.
Good luck and again, sorry for the sloppy solution.
Added a code in your line, hopefully it'll solve the problem.
Vector3 dir = targetPoint - transform.position;
Quaternion lookRotation = Quaternion.LookRotation(dir);
Quaternion newRotation = Quaternion.RotateTowards(transform.rotation, lookRotation, rotationDamping * Time.deltaTime);
newRotation.eulerAngles = new Vector3(newRotation.eulerAngles.x,newRotation.eulerAngles.y,transform.rotation.eulerAngles.z);
transform.rotation = newRotation;
Related
I'm making top down shooting game. I wrote the code where enemies spawn randomly on map and they're trying to catch you. I made them do that and also I wrote a code to make them look at you. Basically rotate towards you only on Z axis. But problem is that when they are spawned on players' right, enemy is moving away from player. but if I rotate and start to move they are trying to fix themselves. Here's my script:
void FixedUpdate () {
Vector3 difference = player.position - transform.position;
float rotationZ = Mathf.Atan2(difference.y, difference.x) * Mathf.Rad2Deg;
transform.rotation = Quaternion.Euler(0.0f, 0.0f, rotationZ);
Vector2 toTarget = player.transform.position - transform.position;
float speed = 1.5f;
transform.Translate(toTarget * speed * Time.deltaTime);
}
Consider that Translate is a relative modifier. For this reason, when you specify the direction in the Translate itself, the movement becomes confused. Use Vector3.MoveTowards to solve the problem. If your game is 2D, you can also use Vector2 like below:
Vector2.MoveTowards(currentPosition, targetPosition, step);
Preferably you can fix this code like this and set the return value of MoveTowards equal to transform.Position.
void FixedUpdate () {
Vector3 difference = player.position - transform.position;
float rotationZ = Mathf.Atan2(difference.y, difference.x) * Mathf.Rad2Deg;
transform.rotation = Quaternion.Euler(0.0f, 0.0f, rotationZ);
float speed = 1.5f;
// replace Translate to MoveTowards
transform.position = Vector3.MoveTowards(transform.position, player.position, Time.deltaTime * speed);
}
I want to 2dgameobject turns toward to target 2dgameobject
I have a Method - rotate to target.
Code:
Quaternion rawRoation = Quaternion.Slerp(transform.rotation,
Quaternion.LookRotation(Player.GetComponent<Transform>().position - transform.position),
10*Time.deltaTime);
transform.rotation = new Quaternion (0, 0, rawRoation.z, rawRoation.w);
Problem - WHen target's position.x < this.object.transform.position.x(1) - rotation breaks..
else(2) - all right.
1 -
enter image description here
2 - enter image description here
The problem is this line of code: transform.rotation = new Quaternion (0, 0, rawRoation.z, rawRoation.w);
I don't know what you want to achieve, but if you want to nullify the x and y rotation use:
transform.rotation = Quaternion.Euler(0.0f, 0.0f, rawRoation.eulerAngles.z);
Example of what your code does:
// Start is called before the first frame update
void Start()
{
Quaternion q1 = new Quaternion(0.3f, 0.7f, 0.4f, 0.5f);
Quaternion q2 = new Quaternion(0.0f, 0.0f, 0.4f, 0.5f);
Debug.Log(q1.eulerAngles);
Debug.Log(q2.eulerAngles);
}
And here the output:
q1: (344.8, 100.3, 59.1)
q2: (0.0, 0.0, 77.3)
Please have a look at the math of quaternions
Edit - 2D LookAt:
Your code does not work like you want because you are in 2D. If you want to look at a target with an x-position less than your object's in 3D you have to do a rotation around the y-axis by 180deg which in 2D is not a valid rotation. You cannot. You do this y-rotation to prevent that the object is upside down. but in 2D you cannot do such a rotation.
You have to choices:
If your camera looks from the side and your object can be upside down use your modified code and also set the x-scale to -1 if the targets x-position is less.
If your camera looks from top down use the following
code:
Code for upside down view:
// Update is called once per frame
void Update() {
Vector3 targetDir = target.position - transform.position;
float angle = Mathf.Atan2(targetDir.y, targetDir.x) * Mathf.Rad2Deg;
Quaternion q = Quaternion.AngleAxis(angle, Vector3.forward);
transform.rotation = Quaternion.Slerp(transform.rotation, q, Time.deltaTime * 10);
}
i've got a gameobject, which moves straight ahead and can turn left, right, up and down using this function:
void moveTowardsPoint(Vector3 targetPoint)
{
//forward movement
var movementSpeed = Time.deltaTime * speed;
transform.position += transform.forward * movementSpeed;
//rotation
Vector3 dir = targetPoint - transform.position;
Quaternion targetRotation = Quaternion.LookRotation(dir);
var turnSpeed = Time.deltaTime * 2f;
transform.rotation = Quaternion.Lerp(transform.rotation, targetRotation, turnSpeed);
}
i want to make this object roll, proportionally to how much it turns left or right. for 20° turn, i want to roll by 20° aswell (angles relative to my startangle)
would actually be even nicer, if i can set a roll limit and it would turn lets say by 30° and roll by 15°.
Here is a topdown view of how this behaviour looks like:
I searched around for a while but I couldn't figure out how to solve this nasty bug. I am using a top down view (pointing towards -z), basically 2d with 3d objects and camera in perspective mode.
I need to orient an object towards the mouse , ignoring the z aspect, as everything moves on the same plane.
I am using the following code:
Vector3 mouseToWorld = Camera.main.ScreenToWorldPoint(Input.mousePosition + new Vector3(0, 0, 1f));
mouseToWorld.z = 0f;
Vector3 difference = mouseToWorld - transform.position;
difference.Normalize();
float angle = Mathf.Atan2(difference.y, difference.x) * Mathf.Rad2Deg;
transform.rotation = Quaternion.Euler(0f, 0f, angle - 90);
Unfortunately it only works when the object is still, and breaks as soon as the velocity is > 0;
Any hint would be appreciated :)
p.s. I am adding 1 to the z and then resetting it, because otherwise the mouseToWorld is constantly 0 wherever I move the pointer.
Perhaps it breaks because the velocity vector and the mouse direction aren't the same.
the following script will make an arrow follow the mouse, It's basically the same as yours except it updates the position as well:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FollowMouse : MonoBehaviour {
public float moveSpeed = 0.01f;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
transform.position = Vector2.Lerp(transform.position, Camera.main.ScreenToWorldPoint(Input.mousePosition), moveSpeed);
Vector3 difference = Camera.main.ScreenToWorldPoint(Input.mousePosition) - transform.position;
difference.Normalize();
float rotation_z = Mathf.Atan2(difference.y, difference.x) * Mathf.Rad2Deg;
transform.rotation = Quaternion.Euler(0f, 0f, rotation_z);
}
}
Thanks for the answer! I figured out you need to subtract the distance between the player and the camera to the initial mouse position:
Vector3 mouseToWorld = Camera.main.ScreenToWorldPoint(Input.mousePosition - new Vector3(0, 0, Camera.main.transform.position.z));
Here the working script:
Vector3 mouseToWorld = Camera.main.ScreenToWorldPoint(Input.mousePosition - new Vector3(0, 0, Camera.main.transform.position.z));
//Debug.DrawLine(transform.position, mouseToWorld);
mouseToWorld.z = 0f;
Vector3 difference = mouseToWorld - transform.position;
difference.Normalize();
float angle = Mathf.Atan2(difference.y, difference.x) * Mathf.Rad2Deg;
transform.rotation = Quaternion.Euler(0f, 0f, angle - 90);
I've seen a lot of solutions to this issue and I've tried a lot with no success with any of them. LookAt is not working in Unity 2d and Ive been trying for a while.
public Transform target;
// ...
void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.name.Contains("Anti"))
{
Quaternion rot = transform.rotation;
transform.rotation = rot;
float z = rot.eulerAngles.z;
Vector3 pos = transform.position;
Vector3 velocity = new Vector3(0, -3, 0);
z += Random.Range(-15.0f, 15.0f);
rot = Quaternion.Euler( 0, 0, z );
transform.position = pos;
transform.rotation = rot;
maxSpeed = Random.Range(5.0f, 15.0f);
}
}
I am struggling getting anything to work regarding LookAt toward my Center doesn't seem to work.
All my other code works (although it has a lot of unnecessary parts I can trim down), its just right before I set Z to a random range, I need it to look at the Origin or Center Object. I cant get that to work at all.
I found a Solution to my issue, I just cleaned out my code and add in
Vector3 diff = Vector3.zero - transform.position;
diff.Normalize();
float rot_z = Mathf.Atan2(diff.y, diff.x) * Mathf.Rad2Deg;
transform.rotation = Quaternion.Euler(0f, 0f, rot_z - 90);
I think my issue was the rest of my code,