I have the following code that rotates an object towards a target point at a smooth rate.
public bool RotatedTowards(Vector3 lookAtPoint, float deltaTime)
{
flatDirPath = Vector3.Scale((lookAtPoint - transform.position), new Vector3(1, 0, 1));
targetRotation = Quaternion.LookRotation(flatDirPath, Vector3.up);
transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRotation, deltaTime * basicProperties.turnRate);
if (Vector3.Dot(flatDirPath.normalized, transform.forward) >= 0.9f)
{
return true;
}
else
{
return false;
}
}
It works well enough, the problem is if I right click around a point to move to really fast (telling this object to rotate toward a very similar but different lookAtPoint at a very high frequency), the object shakes very slightly as its constantly making small rotational changes (I believe that's whats happening).
I believe the best solution may be to only do the transform.rotation if the target point is over a threshold from the front of the object, say at least 1 degree to the left or the right of the direction the object is already facing. How could I test this? If it's under 1 degree, no point rotating toward it, and that should remove the shaking from clicking around the same point really fast. I hope.
Any help is appreciated. Thanks
you can use this: https://docs.unity3d.com/ScriptReference/Quaternion.Angle.html
in your code:
public bool RotatedTowards(Vector3 lookAtPoint, float deltaTime)
{
flatDirPath = Vector3.Scale((lookAtPoint - transform.position), new Vector3(1, 0, 1));
targetRotation = Quaternion.LookRotation(flatDirPath, Vector3.up);
float angle = Quaternion.Angle(transform.rotation, targetRotation);
float threshold = 1;
if(Mathf.Abs(angle) > threshold)
transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRotation, deltaTime * basicProperties.turnRate);
return (Vector3.Dot(flatDirPath.normalized, transform.forward) >= 0.9f);
}
if you don't like how this threshold behaves, you can try another approach:
use Queue<Vector3> to store the last few flatDirPath vectors (last 10, for example), then add a new vector each time and remove the oldest vector each time - then compute the average vector from all vectors in the Queue and use that in Quaternion.LookRotation - you should get a more smooth behavior. the rotation will lag behind a bit with more vectors you store in the Queue, so use 4-6 vectors for a faster response or 10-20 vectors for a more smooth response.
Related
I've been told that Rigidbody.MoveRotation is the best way in Unity 3D to rotate the player between fixed positions while still detecting hits. However, while I can move smoothly from fixed position to position with:
if (Vector3.Distance(player.position, targetPos) > 0.0455f) //FIXES JITTER
{
var direction = targetPos - rb.transform.position;
rb.MovePosition(transform.position + direction.normalized * playerSpeed * Time.fixedDeltaTime);
}
I can't find out how to rotate smoothly between fixed positions. I can rotate to the angle I want instantly using Rigidbody.MoveRotation(Vector3 target);, but I can't seem to find a way to do the above as a rotation.
Note: Vector3.Distance is the only thing stopping jitter. Has anyone got any ideas?
First of all MoveRotation doesn't take a Vector3 but rather a Quaternion.
Then in general your jitter might come from overshooting - you might be moving further than the distance between your player and target actually is.
You can avoid that bit by using Vector3.MoveTowards which prevents any overshooting of the target position like e.g.
Rigidbody rb;
float playerSpeed;
Vector3 targetPos;
// in general ONLY g through the Rigidbody as soon as dealing wit Physics
// do NOT go through transform at all
var currentPosition = rb.position;
// This moves with linear speed towards the target WITHOUT overshooting
// Note: It is recommended to always use "Time.deltaTime". It is correct also during "FixedUpdate"
var newPosition = Vector3.MoveTowards(currentPosition, targetPos, playerSpeed * Time.deltaTime);
rb.MovePosition(newPosition);
// [optionally]
// Note: Vector3 == Vector3 uses approximation with a precision of 1e-5
if(rb.position == targetPos)
{
Debug.Log("Arrived at target!");
}
Then you can simply apply this same concept also to rotation by going through the equivalent Quaternion.RotateTowards basically just the same approach
Rigidbody rb;
float anglePerSecond;
Quaternion targetRotation;
var currentRotation = rb.rotation;
var newRotation = Quaternion.RotateTowards(currentRotation, targetRotation, anglePerSecond * Time.deltaTime);
rb.MoveRotation(newRotation);
// [optionally]
// tests whether dot product is close to 1
if(rb.rotation == targetRotation)
{
Debug.Log("Arrived at rotation!");
}
You can go one step further and use a tweeting library to tween between rotations.
DOTween
With that you can call it like this:
rigidbody.DoRotate(target, 1f) to rotate to target in 1 second.
Or even add callbacks.
rigidbody.DoRotate(target, 1f).OnComplete(//any method or lambda you want)
If at some point you want to cancel the tween yuou can save it on a variable and then call tween.Kill();
So, you want to animate the rotation value over time until it reaches a certain value.
Inside the Update method, you can use the Lerp method to keep rotating the object to a point, but you will never really reach this point if you use Lerp. It will keep rotating forever (always closer to the point).
You can use the following:
private bool rotating = true;
public void Update()
{
if (rotating)
{
Vector3 to = new Vector3(20, 20, 20);
if (Vector3.Distance(transform.eulerAngles, to) > 0.01f)
{
transform.eulerAngles = Vector3.Lerp(transform.rotation.eulerAngles, to, Time.deltaTime);
}
else
{
transform.eulerAngles = to;
rotating = false;
}
}
}
So, if the distance between the current object angle and the desired angle is greater than 0.01f, it jumps right to the desired position and stop executing the Lerp method.
i am trying to rotate cube smoothly to 90 degrees every time i press space key. here in my code every time i decrease speed to less than 1 its rotation is not consistent at 90 decrease and speed at anything more than 1 its rotating instantly not smoothly. Here is my code
Vector3 to = new Vector3(0, 0, 90);
public float speed = 0.5f;
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
RotateOne();
}
}
void RotateOne()
{
transform.eulerAngles = Vector3.Lerp(transform.rotation.eulerAngles, to, speed * Time.deltaTime);
to += new Vector3(0, 0, 90);
}
You almost had it ;)
The main issue is that you only rotate once a tiny little bit when you click the key.
You rather want to rotate continously and only increase the target rotation once when you click.
A second issue is you using eulerAngles for a continuous rotation. From the API:
When using the .eulerAngles property to set a rotation, it is important to understand that although you are providing X, Y, and Z rotation values to describe your rotation, those values are not stored in the rotation. Instead, the X, Y & Z values are converted to the Quaternion's internal format.
When you read the .eulerAngles property, Unity converts the Quaternion's internal representation of the rotation to Euler angles. Because, there is more than one way to represent any given rotation using Euler angles, the values you read back out may be quite different from the values you assigned. This can cause confusion if you are trying to gradually increment the values to produce animation.
To avoid these kinds of problems, the recommended way to work with rotations is to avoid relying on consistent results when reading .eulerAngles particularly when attempting to gradually increment a rotation to produce animation. For better ways to achieve this, see the Quaternion * operator.
// In general instead of eulerAngles always prefer calculating with
// Quaternion directly where possible
private Quaternion to;
void Start()
{
to = transform.rotation;
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
RotateOne();
}
// You want to do this always, not only in the one frame the key goes down
// Rather use RotateTowards for a linear rotation speed (angle in degrees per second!)
transform.rotation = Quaternion.RotateTowards(transform.rotation, to, speed * Time.deltaTime);
// Or if you still rather want to interpolate
//transform.rotation = Quaternion.Lerp(transform.rotation, to, speed * Time.deltaTime);
}
void RotateOne()
{
to *= Quaternion.Euler(0, 0, 90);
}
NOTE though there will be one little issue with this: The moment you hit the key 3 or 4 times it will suddenly rotate back! This is because RotateTowards and Lerp use both the shortest way towards the target rotation.
In order to fully avoid this in your case you could rather use a Corotuine and stack your inputs like e.g.
private int pendingRotations;
private bool isRotating;
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
pendingRotations++;
if(!isRotating) StartCoroutine(RotateRoutine());
}
}
IEnumerator RotateRoutine()
{
// just in case
if(isRotating) yield break;
isRotating = true;
var targetRotation = transform.rotation * Quaternion.Euler(0, 0, 90);
while (transform.rotation != targetRotation)
{
transform.rotation = Quaternion.RotateTowards(startRotation, targetRotation, speed * Time.deltaTime);
// tells Unity to "pause" the routine here, render this frame
// and continue from here in the next fame
yield return null;
}
// in order to end up with a clean value
transform.rotation = targetRotation;
isRotating = false;
pendingRotations--;
// are there more rotations pending?
if (pendingRotations > 0)
{
// start another routine
StartCoroutine(RotateRoutine());
}
}
Quaternion to = Quaternion.Euler(0,0,90);
transform.rotation = Quaternion.Lerp(transform.rotation, to, speed * Time.deltaTime);
Don't change to and add Time.deltaTime
I am trying to move four objects and a SteamVR player by updating the transform.position. This works fine, however it does not look so well because the movement is like instant. That is why I want to use Vector3.MoveTowards().
Somehow, the code below is not doing the job. I was hoping that someone could help me out.
private void ZoomObject(Vector3 currentPlayerPosition, float height, float distance)
{
TPNorthObject.transform.position = Vector3.MoveTowards(TPNorthObject.transform.position, new Vector3(0, height, distance), 10 * Time.deltaTime);
TPEastObject.transform.position = Vector3.MoveTowards(TPEastObject.transform.position, new Vector3(distance, height, 0), 10 * Time.deltaTime);
TPSouthObject.transform.position = Vector3.MoveTowards(TPSouthObject.transform.position, new Vector3(0, height, -distance), 10 * Time.deltaTime);
TPWestObject.transform.position = Vector3.MoveTowards(TPWestObject.transform.position, new Vector3(-distance, height, 0), 10 * Time.deltaTime);
}
What I expected was, that the object would float to the new vector place. However it does not seem to do that.
Can someone give me some insight or advice?
Thanks in advance
From Unity's documentation:
https://docs.unity3d.com/ScriptReference/Vector3.MoveTowards.html
Calculate a position between the points specified by current and target, moving no farther than the distance specified by maxDistanceDelta.
Use the MoveTowards member to move an object at the current position toward the target position. By updating an object’s position each frame using the position calculated by this function, you can move it towards the target smoothly. Control the speed of movement with the maxDistanceDelta parameter.
That is to say that MoveTowards doesn't do the smooth animation for you. If you wan't some kind of animation effect, your ZoomObject function needs to be called in a loop until your object reaches the target position. Check out the example on the documentation page.
You can use a loop or a coroutine to do that. Maybe something that would look like this.
IEnumerator Fade()
{
while (Vector3.Distance(TPNorthObject.transform.position, new Vector3(0, height, distance)) > 0.001f)
{
// Speed = Distance / Time => Distance = speed * Time. => Adapt the speed if move is instant.
TPNorthObject.transform.position = Vector3.MoveTowards(TPNorthObject.transform.position, new Vector3(0, height, distance), 10 * Time.deltaTime);
yield return null;
}
}
I'm currently working on a simple baseball game. What I'm trying to do is have the player be able to swing the bat backwards to "charge up" power, so to speak, and then when a button is released he will swing the bat forward at a speed equal to the amount of power stored up.
This is all well and good, but my problem is I need to stop the motion of the bat when he reach a certain point on the y-axis, and I'm a bit unsure how to go about doing this as I cannot just tell it to stop rotating after a set time as the bat won't reach the front point at the same time every time due to the difference in speed each swing might have.
What I'm trying to do basically is something like if(reached 266 on the y-axis stop rotating), but I'm unsure of how to do this.
Anyway here is the code I've written so far:
public int rotateSpeed = 50;
public float AmountOfPowerChargedUp = 0;
void Update()
{
if (Input.GetMouseButton(0))
{
AmountOfPowerChargedUp += 5f;
transform.Rotate(Vector3.up * rotateSpeed * Time.deltaTime);
Debug.Log(AmountOfPowerChargedUp);
}
if (!Input.GetMouseButton (0))
{
transform.Rotate(Vector3.down * AmountOfPowerChargedUp * Time.deltaTime);
}
}
private void OnGUI()
{
GUI.Label (new Rect (50, 15, 250, 25), "AmountOfPowerChargedUp: " + AmountOfPowerChargedUp);
}
if your rotating a certain amount of degrees, you should use a Slerp or RotateTowards
Vector3 targetDir = (target.position - transform.position).normalized;
// The step size is equal to speed times frame time.
float step = speed * Time.deltaTime;
Vector3 newDir = Vector3.RotateTowards(transform.up, targetDir, step, 0.0);
transform.rotation = Quaternion.LookRotation(newDir)
I have the model representing the player's ship gradually leaning when the player strafes. For instance, here's the code that leans the ship right:
In Update() of the Game class:
if (ship.rightTurnProgress < 1 && (currentKeyState.IsKeyDown(Keys.D)))
{
ship.rightTurnProgress += (float)gameTime.ElapsedGameTime.TotalSeconds * 30;
}
In Update() of the Ship class:
if (currentKeyState.IsKeyDown(Keys.D))
{
Velocity += Vector3.Right * VelocityScale * 10.0f;
RotationMatrix = Matrix.CreateRotationX(MathHelper.PiOver2) *
Matrix.CreateRotationY(0.4f * rightTurnProgress);
}
This is what I'm attempting to do to make it ease back out of the lean when it stops strafing:
In Update() of the Game class:
if (ship.rightTurnProgress > 0 && currentKeyState.IsKeyUp(Keys.D))
{
ship.rightTurnProgress -= (float)gameTime.ElapsedGameTime.TotalSeconds * 30;
}
In Update() of the Ship class:
if (currentKeyState.IsKeyUp(Keys.D) && rightTurnProgress > 0)
{
RotationMatrix = Matrix.CreateRotationX(MathHelper.PiOver2) *
Matrix.CreateRotationY(-0.4f * rightTurnProgress);
}
Since easing into the lean works no problem, I thought easing out of the lean would be a simple matter of reversing the process. However, it tends to not go all the way back to the default position after a long strafe. If you tap the key, it snaps all the way back to the full lean of the -opposite- direction. This isn't what I expected at all. What am I missing here?
I suggest you represent the rotation of you ship as a quaternion. That way you can use an interpolation function such as slerp. Simply have a second quaternion that represents you targeted lean angle and the ship will smoothly rotate until it achieves the targeted angle.
Here's a good tutorial on quaternions. If you want to avoid quaternions use MathHelper.Lerp to smoothly transition from the current value to the target.
if (currentKeyState.IsKeyDown(Keys.D))
{
ship.TurnProgress = MathHelper.Lerp(ship.TurnProgress, 1, somefloat * timeDelta);
}
else if (currentKeyState.IsKeyDown(Keys.a))
{
ship.TurnProgress = MathHelper.Lerp(ship.TurnProgress, -1, somefloat * timeDelta);
}
else (currentKeyState.IsKeyDown(Keys.D))
{
ship.TurnProgress = MathHelper.Lerp(ship.TurnProgress, 0, somefloat * timeDelta);
}
Edit: Also there is a GameDev stack overflow so check it out if you have more questions.
Unless you know how long the turn is or you have some kind of acceleration vector you will have to wait until the turn is stopped before returning the sprite angle to neutral, then what happens when the player turns left before the sprite has reached its neutral position? I assume that when you turn right using RightTurnProgress you also have a LeftTurnProgress I suggest you combine them into one variable to keep it smooth and avoid the snapping effect you are getting.
You are creating an 'absolute' rotation matrix so you don't need to flip the sign to -0.4f. Why not just have a variable called ship.lean and calculate the rotation matrix every update. Then you just need logic to ease ship.lean between -1 (left lean) and 1 (right lean) or 0 for no lean.