Quaternion.Slerp on X and Z axis without Y axis - c#

I am trying to rotate the Player about X, Y, and Z axis. The Y axis should not move from last angle. Example, if I rotate 45 degree's to the left, the player should not rotate back to 0. The players X and Z axis rotate a maximum of 30 degrees, then when Input is no longer in use, settle to 0.
Through trial and error, I have finally gotten my Y angle to not Slerp back to 0. However, X and Z, still consider Y to be 0 degree's. The player is rotated (assume 45 degree's to the left), but movement along X and Z is as if Y is 0 degree's.
I've been reading articles and threads, and watching video's across multiple domains, including but not limited StackOverflow, Unity forums, Unity API, and YouTube video's.
Video of Current Game - notice the engine exhaust - X and Z never change to the new normal of the Camera view / Player Y direction.
void Update()
{
if(!controller.isGrounded)
{
//Three degree's
moveDirection = new Vector3(Input.GetAxis("Horizontal"), Input.GetAxis("Thrust"), Input.GetAxis("Vertical"));
moveDirection *= speed;
//rotate around Y-Axis
transform.Rotate(0, Input.GetAxis("Yaw") * rotationSpeed, 0);
float currentY = transform.eulerAngles.y; //save Y for later
//rotation around X and Z
float tiltAroundX = Input.GetAxis("Vertical") * tiltAngle;
float tiltAroundZ = -1 * (Input.GetAxis("Horizontal") * tiltAngle);
Quaternion targetRotation = Quaternion.Euler(tiltAroundX, currentY, tiltAroundZ);
Vector3 finalRotation = Quaternion.Slerp(transform.rotation, targetRotation, smooth).eulerAngles;
finalRotation.y = currentY; //reintroduce Y
transform.rotation = Quaternion.Euler(finalRotation);
controller.Move(moveDirection * Time.deltaTime);
}

After further research that lead me along different avenues, I discovered that there were two issues. Both issue's revolved around the fact that the Z-axis was never being normalized to the new Y-axis degree after rotation. #Ruzihm, solved the issue of Rotation. I solved the then visible issue of movement. Which became readily visible once rotation was working properly.
In essence, the Z-axis (transform.forward) must be recalculated after any change in the Y-axis rotation (Vector3.up). Once you have the new normal (transform.forward), the movement vector needed to flattened to the plane to keep the player from diving into the surface of the world. Thank you #Ruzihm for all your assistance.
Here is the new code:
//Three degree's
moveDirection = new Vector3(Input.GetAxis("Horizontal"),
Input.GetAxis("Thrust"),
Input.GetAxis("Vertical"));
//Normalize the movement direction and flatten the Plane
moveDirection = transform.TransformDirection(moveDirection);
moveDirection = Vector3.ProjectOnPlane(moveDirection, Vector3.up);
moveDirection *= speed;
// collect inputs
float yaw = Input.GetAxis("Yaw") * rotationSpeed;
float pitch = Input.GetAxis("Vertical") * tiltAngle;
float roll = -1 * (Input.GetAxis("Horizontal") * tiltAngle);
// Get current forward direction projected to plane normal to up (horizontal plane)
Vector3 forwardCurrent = transform.forward
- Vector3.Dot(transform.forward, Vector3.up) * Vector3.up;
// Debug to view forwardCurrent
Debug.DrawRay(transform.position, forwardCurrent * 2, Color.white);
// create rotation based on forward
Quaternion targetRotation = Quaternion.LookRotation(forwardCurrent);
// rotate based on yaw, then pitch, then roll.
// This order prevents changes to the projected forward direction
targetRotation = targetRotation * Quaternion.AngleAxis(yaw, Vector3.up);
// Debug to see forward after applying yaw
Debug.DrawRay(transform.position, targetRotation * Vector3.forward, Color.red);
targetRotation = targetRotation * Quaternion.AngleAxis(pitch, Vector3.right);
targetRotation = targetRotation * Quaternion.AngleAxis(roll, Vector3.forward);
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, smooth);
controller.Move(moveDirection * Time.deltaTime);

There seem to be some incorrect assumptions about the order of rotations that apply when working with Euler angles. Roll is applied, then pitch, then finally yaw. This means that keeping the same yaw then setting the roll and pitch to zero (or even just changing roll) can completely change the flattened direction you're facing.
It may help to rotate by yaw, flatten the forward direction (aka project it to a completely horizontal plane) Then create a rotation based off that (using Quaternion.LookRotation) which you can then rotate by each axis manually.
if(!controller.isGrounded)
{
//Three degree's
moveDirection = new Vector3(Input.GetAxis("Horizontal"),
Input.GetAxis("Thrust"),
Input.GetAxis("Vertical"));
moveDirection *= speed;
// collect inputs
float yaw = Input.GetAxis("Yaw") * rotationSpeed;
float pitch = Input.GetAxis("Vertical") * tiltAngle;
float roll = -1 * (Input.GetAxis("Horizontal") * tiltAngle);
// Get current forward direction projected to plane normal to up (horizontal plane)
Vector3 forwardCurrent = transform.forward
- Vector3.Dot(transform.forward,Vector3.up) * Vector3.up;
// Debug to view forwardCurrent
Debug.DrawRay(transform.location, forwardCurrent, Color.white, 0f, false);
// create rotation based on forward
Quaternion targetRotation = Quaternion.LookRotation(forwardCurrent);
// rotate based on yaw, then pitch, then roll.
// This order prevents changes to the projected forward direction
targetRotation = targetRotation * Quaternion.AngleAxis(yaw, Vector3.up);
// Debug to see forward after applying yaw
Debug.DrawRay(transform.location, targetRotation * Vector3.forward, Color.red, 0f, false);
targetRotation = targetRotation * Quaternion.AngleAxis(pitch, Vector3.right);
targetRotation = targetRotation * Quaternion.AngleAxis(roll, Vector3.forward);
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, smooth);
//debug new forward/up
Debug.DrawRay(transform.location, Transform.forward, Color.blue, 0f, false);
Debug.DrawRay(transform.location, Transform.up, Color.green, 0f, false);
controller.Move(moveDirection * Time.deltaTime);
}
This may be considered a partial answer because being able to determine a "flattened forward" direction and reorder the process of applying component rotations is useful to answering your question but may not be enough to get the full effect you want depending on the details.
As a sidenote, you may want to consider using Quaternion.RotateTowards instead of Quaternion.Slerp if you want to ensure that it will actually reach the target rotation instead of infinitely approach it.

Related

I have problem with rotation; rotatetoToTarget;

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

Unity3D - Rotating object towards point and rolling

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:

Unity movement near point

I have spaceships that chase each other. They currently move to their target exactly, but I want to replicate physics you might see on water or in zero gravity, where the object would overshoot its target, turn and move back toward it. Possibly hovering around the target back and forth. I've tried addforce and addrelativeforce, and velocity, but those don't seem to give me the desired effect. Any ideas?
This is my code...
Vector3 dir = (new Vector3(x, y, 0) - transform.parent.position).normalized;
float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg;
Quaternion q = Quaternion.AngleAxis(angle, Vector3.forward);
float heading = Mathf.Atan2(dir.x, dir.y);
transform.parent.rotation = Quaternion.Slerp(transform.parent.rotation, Quaternion.Inverse(Quaternion.Euler(0f, 0f, heading * Mathf.Rad2Deg)), Time.deltaTime * 12f);
//transform.parent.position += dir * speed * Time.deltaTime;
//rb.AddForce(dir);
rb.velocity = new Vector3(dir.x * speed, dir.y * speed, 0);
Here's how I achieve that AI in a 2D space shooter:
void FixedUpdate()
{
var target = Player != null ? PlayerObject : Target; // If player is dead, go for secondary target
Vector2 dir = -(Vector2)(transform.position - target.transform.position + targetOffset); // Direction
// dir.magnitude is the distance to target
if (dir.magnitude > reaction * rangeModifier) // If I'm far away from the target, slowly rotate towards it
{
// calculate angle toward target and slowly rotate child ship object toward it in the speed of turnRate
float attackAngle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg - 90;
ship.transform.rotation = Quaternion.Slerp(ship.transform.rotation, Quaternion.AngleAxis(attackAngle, Vector3.forward), turnRate * Time.deltaTime);
}
else // If I'm close to the target just keep going straight ahead and fire until I'm too far away again
{
My.weapon.Shoot(dir);
}
_rb.velocity = ship.transform.up.normalized * My.Movementspeed; // Add velocity in the direction of the ship's rotation
}
This will give you an AI that goes in eight (8) shapes around the target. If you have many objects running this code I recommend adding a random offset to the target, to simulate swarming and more realistic flight aim.
I added comments to explain some details of the code.

Unity 3d, point object towards mouse (3d space)

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

Unity 3D how to rotate an object so that its oriented correctly based on the ground plane?

I'm using an asset called Dreamteck Splines to create a path, what I'm trying to do is make it so when I rotate the spline the game object the pink cube in this case, also rotates so it's oriented correctly on the path whether its upside down or sideways like a roller coaster. For some reason, I can only rotate the path to about 90 degrees before the cube stops rotating to align itself to the path.
GIFF
if (Input.GetKey(KeyCode.W))
{
vehicle.transform.Translate(0, 0, speed * Time.deltaTime);
}
if (Physics.Raycast(vehicle.transform.position, Vector3.down, out hit, 100))
{
Vector3 surfaceNormal = hit.normal; // Assign the normal of the surface to surfaceNormal
Vector3 forwardRelativeToSurfaceNormal = Vector3.Cross(vehicle.transform.InverseTransformDirection(vehicle.transform.right), surfaceNormal);
Quaternion targetRotation = Quaternion.LookRotation(forwardRelativeToSurfaceNormal, surfaceNormal); //check For target Rotation.
vehicle.transform.rotation = Quaternion.Lerp(vehicle.transform.rotation, targetRotation, Time.deltaTime * 20); //Rotate Character accordingly.
}
I found this which does what I was trying to achieve:
void Update()
{
/*Here we get user input to calculate the speed the ship will get*/
if (Input.GetKey(KeyCode.W))
{
/*Increase our current speed only if it is not greater than fwd_max_speed*/
current_speed += (current_speed >= fwd_max_speed) ? 0f : fwd_accel * Time.deltaTime;
}
else
{
if (current_speed > 0)
{
/*The ship will slow down by itself if we dont accelerate*/
current_speed -= brake_speed * Time.deltaTime;
}
else
{
current_speed = 0f;
}
}
/*We get the user input and modifiy the direction the ship will face towards*/
yaw += turn_speed * Time.deltaTime * Input.GetAxis("Horizontal");
/*We want to save our current transform.up vector so we can smoothly change it later*/
prev_up = transform.up;
/*Now we set all angles to zero except for the Y which corresponds to the Yaw*/
transform.rotation = Quaternion.Euler(0, yaw, 0);
RaycastHit hit;
if (Physics.Raycast(transform.position, -prev_up, out hit))
{
Debug.DrawLine(transform.position, hit.point);
/*Here are the meat and potatoes: first we calculate the new up vector for the ship using lerp so that it is smoothed*/
Vector3 desired_up = Vector3.Lerp(prev_up, hit.normal, Time.deltaTime * pitch_smooth);
/*Then we get the angle that we have to rotate in quaternion format*/
Quaternion tilt = Quaternion.FromToRotation(transform.up, desired_up);
/*Now we apply it to the ship with the quaternion product property*/
transform.rotation = tilt * transform.rotation;
/*Smoothly adjust our height*/
smooth_y = Mathf.Lerp(smooth_y, hover_height - hit.distance, Time.deltaTime * height_smooth);
transform.localPosition += prev_up * smooth_y;
}
/*Finally we move the ship forward according to the speed we calculated before*/
transform.position += transform.forward * (current_speed * Time.deltaTime);
}

Categories