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;
}
}
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 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.
Ok, I have looked up everything to do with Unity's Animator, and I know you can jump to specific frames and get percentage of animation with https://answers.unity.com/questions/1418940/how-can-i-know-a-percent-of-animation.html and https://answers.unity.com/questions/181903/jump-to-a-specific-frame-in-an-animation.html
I haven't seen anything on this, however - like in the game Causality (made with Unity) I need to do something like Magic leap does with its website on scroll: https://www.magicleap.com
I have an animation I am creating on my model in Blender, and rather than running it, I need to fast forward/go to certain percentage of the animation based on its y axis position.
Right now I can drag my model up/down on y axis, and I have a max and min transform.position.y that the model can move on (like a dolley)
Vector3 goTo = new Vector3 (transform.position.x, (initObjPos.y+touchDiff), transform.position.z);
if (goTo.y >= maxHeight) { //max = 0.251f in y, and min?
transform.position = new Vector3 (transform.position.x, maxHeight, transform.position.z);
} else if (goTo.y <= minHeight) {
transform.position = new Vector3 (transform.position.x, minHeight, transform.position.z);
} else { //lower
transform.position = goTo;
}
Here's a visualization of how I need the animation to be finished at 100% when y position is at max, and 0% at min height, and be able to move back and forth as user drags:
Is this even possible?
For every state, there is a toggle in the animator to take a normalizedTime through a float parameter. As the name suggests, setting its value to 0 will give an animation at 0% and 1 at 100%. You can set the value of the parameter through
animator.SetFloat ("AnimStateTime", 0.5f);
if you want to smooth the transition, use
animator.SetFloat ("AnimStateTime", 0.5f, 1, 10 * Time.deltaTime);
Lets say I'm working in a 2D plane, with a person in the bottom of my screen, and an object that moves down along the Y-axis.
The moving object has following movement code:
transform.position += new Vector3 (0, -1, 0) * speed * Time.deltaTime;
Now, as I want to be able to modify how long it takes for the object to connect with the player, I created this, where spawnTimeDistance is our key factor:
Instantiate (obstacle, player.transform.position + new Vector3(-0.50F , (spawnTimeDistance + obstacleDimensionY + pScript.playerDimensionY), -1), Quaternion.identity);
As can be seen, spawnTimeDistance is a place-holder for the objects Y-distance to the player. However, using this code I get great inconsistencies with my results, especially when comparing devices. I suspect this is caused by distance being FPS dependant, instead of actual time dependant.
To fix this I believe I should implement Time.deltaTime in an extended manner, but I'm insecure on how.
EDIT - spawnDistanceTime related:
void Start() {
spawnTimeDistance = 4F;
}
and
Vector2 sprite_size = obstacle.GetComponent<SpriteRenderer> ().sprite.rect.size;
Vector2 spriteScale = obstacle.transform.localScale;
float sizeAndScaleY = sprite_size.y * spriteScale.y;
float obstacle_local_sprite_sizeY = (sizeAndScaleY / obstacle.GetComponent<SpriteRenderer> ().sprite.pixelsPerUnit) * 0.5F;
obstacleDimensionY = obstacle_local_sprite_sizeY;
Above code, same thing for my player.
Edited the Instantiate to include the obstacleDimensions, had them removed before as I thought it would give a simpler question.