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.
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.
When I try to use Vector2.Lerp in unity, I run into a problem. The object flies downwards at a very high speed.
I am trying to make a moving platform in a 2D game. It moves from minimum x value to maximum x value. I want to use Vector2.Lerp to make the speed in both directions the same, but when I apply transform.Translate, and pass Vector2.Lerp as argument, the object flies down with very high speed.
That's the problem, because when I pass in Vector 3 with coordinates divided by 100, everything works fine. But different speeds appear in different directions.
The object has a box collider 2D and a script that moves it. It has no rigidbody 2D.
What am I doing wrong?
Here's my function that moves the object in one direction (it's called in FixedUpdate):
Vector2 target = new Vector3(xMin, 0);
Vector2 moving = Vector2.Lerp(transform.position, target, speed * Time.fixedDeltaTime);
transform.Translate(moving);
Lerp(a, b, t) interpolates between a and b.
t must be in the range [0,1], so that Lerp(a, b, 0) = a and Lerp(a, b, 1) = b. Using speed * Time.fixedDeltaTime in the place of t doesn't make sense.
If you want to move your object from a to b with a constant speed of speed (units per second), you can do:
IEnumerator Move(Vector2 a, Vector2 b, float speed){
float movementDuration = Vector2.Distance(a, b) / speed;
for(float t = 0; t < 1; t += Time.deltaTime / movementDuration){
transform.position = Vector2.Lerp(a, b, t);
yield return null;
}
transform.position = b;
}
And you can call this method using StartCoroutine(Move(a, b, speed));
Be careful not to call this every frame. Call only once and see what happens.
As explained here Lerp interpolates linear from the given start value to he target value using a relative factor from 0 to 1.
You can go with a Coroutine but would need to start it and make sure it is running only once at a time.
Or if you simply want to continuously move towards the target you would want to rather use
private void Update()
{
transform.position = Vector2.MoveTowards(transform.position, new Vector2(xMin, 0), speed * Time.deltaTime);
}
Another note for your usecase: If you use Rigibody/Rigidbody2D or any other component form the physics engines you do not want to move anything via Transform as this breaks with the physics, usually looks weird and breaks collision detection etc.
You rather want to go though the according component - in your case e.g.
[SerializeField] private Rigidbody2D _rigidbody;
pivate void Awake()
{
if(!_rigidbody) _rigidbody = GetComponent<Rigidbody2D>();
}
private void FixedUpdate()
{
var position = Vector2.MoveTowards(_rigidbody.position, new Vector2(xMin, 0), speed * Time.deltaTime);
_rigidbody.MovePosition(position);
}
I just started with unity and followed thair 2D UFO example project.
In an effort to extend it, I came up with a quirky way of contolling my Player.
It always moves in circular paths and once I click a button, the circle's direction changes and the imaginary circle center is tanslated as shown in the picture below. That allows you to move in a figure 8 or S shape pattern and is quite fun.
However, once I figured out how to do this motion, the player object did not have any collision detection anymore.
In the original example the whole movemet handling is done within FixedUpdate(). I, however, use Update() since the former does not seem to work at all with my code (i.e. no movement at all).
This is my code for the movement so far:
public class ControlledRotation : MonoBehaviour
{
public float radius = 3f;
public float speed = 3f;
private float timeCounter = 0;
private float direction = 1f;
private Vector3 offset;
private Rigidbody2D rb2d;
void Start()
{
offset = transform.position;
}
void Update()
{
if (Input.GetKeyDown(KeyCode.RightArrow))
{
//change the offset from the circle center and the rotation direction on keypress
offset += new Vector3(Mathf.Cos(timeCounter), Mathf.Sin(timeCounter), 0) * radius * direction * 2;
direction *= -1;
}
timeCounter += Time.deltaTime * direction * speed;
transform.position = new Vector3(Mathf.Cos(timeCounter), Mathf.Sin(timeCounter)) * radius * direction + offset;
}
}
The Plyer object has a Rigidbody 2D and a Circle Collider 2D. The walles it should collide with have Box Collider 2D. Yet, the UFO can simply pass the walls.
I assume a probable cause in the fact that I simply change transform.position or since im using Update/FixedUpdate wrong.
If you happen to have any advice on how I can keep my chosen movement control mechanism and still be able to collide with objects, I'd highly appreciate it :)
Edit:
I feel I need to go with using the rigidbody and applying some force... but I haven't figured out how to reproduce this movement with forces and also forces seem to not be super crisp in response
When you need to move an object that has a Rigidbody, you need to move it using forces, you cannot do it with just transform.position, it ignores physics. that's why you cannot detect the collision.. I suggest you move it like that. I have some examples when I had to move a character that needed to interact with physics.
gameObject.GetComponent<Rigidbody>().velocity = Vector3.zero;
And this one is for moving it in certain directions:
//Keys for controlling the player (move and shoot) only when he's alive
if (Input.GetKey(KeyCode.UpArrow) && alive)
{
GetComponent<Rigidbody>().MovePosition(transform.position + Vector3.forward * Time.deltaTime * 4);
}
if (Input.GetKey(KeyCode.DownArrow) && alive)
{
GetComponent<Rigidbody>().MovePosition(transform.position + Vector3.back * Time.deltaTime * 4);
}
I hope it helps.. greetings :)
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 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.