'Smoother' looking teleportation with Vector3.Lerp - c#

Short version: Can I use Vector3.Lerp in transform.Translate(new Vector3(-6,0)); (without calculating a speed variable into the movement)?
I have a very simple game where the Player can move from 1 lane to another (max. 3 lanes (like Temple Run)). Right now my character is just teleporting to the other lanes and I want to make it look more smooth with Vector3.Lerp. My problem now is, I cant figure out how to implement it into my code. I don't calculate my movement with a speed variable, because my character starts moving uneven distances, which I don't want. I have 3 set lanes to move to.
if (Input.GetKeyDown("a"))
{
// Do I need a speed variable here?
transform.Translate(new Vector3(-6, 0));
}

If the code is not in Update(), you can use StartCoroutine
if (Input.GetKeyDown("a")) {
changing = true;
StartCoroutine(ChangeLane(int n));
}
IEnumerator ChangeLane(int n) {
float changeProgress = 0f;
float changeDuration = 1f;
originPosition = tranform.position;
//the speed should be run speed
targetPosition = originPosition + new Vector3(n * lanWidth, speed * changeDuration);
while(changeProgress < changeDuration) {
tranform.position = Vector3.Lerp(originPosition, targetPosition, changeProgress / changeDuration);
changeProgress += Time.DeltaTime;
yield return 0;
}
tranform.position = targetPosition;
changing = false;
}
If the code is in Update(), you can also use this, but while changing lane, you should not start a new change

Related

How to measure Z rotation accurately with rapid changes

I am programming a game in unity and for one of the games enemies I wanted their sword to rotate in a 180 degree arc in front of them, I was able to make the sword rotate around the enemy but when I try to check if it has done the full arc it does not measure it properly since it is rotating at such high speeds.
public GameObject pivotPoint;
public SpriteRenderer renderer;
// Start is called before the first frame update
void Start()
{
StartCoroutine("rotate");
}
IEnumerator rotate()
{
renderer.enabled = true;
yield return new WaitForSeconds(2);
while(true)
{
transform.RotateAround(pivotPoint.transform.position, new Vector3(0, 0, 1), -1 * Time.deltaTime);
if(transform.eulerAngles.z >= -47)
{
if(transform.eulerAngles.z > 132)
{
renderer.enabled = false;
StopCoroutine("rotate");
}
}
yield return null;
}
yield return null;
}
I have tried using fixed delta time which made it a lot worse and tried to play around with the checks just to hopefully find a sweet spot where it could consitently get the right number but none have worked so far.
First, I'm not sure how this is a problem, because it looks like you're rotating 1 degree per second, so it should take 3 minutes (180 seconds) to rotate 180 degrees.
Regardless, assuming you're changing the speed to something faster, you're going to have trouble getting Euler angles out of the rotation because they may be limited to +/- 90 degrees or similar.
I think you'd be better off accumulating the fractional angle and checking that. I don't understand your nested if statements, because the angle must be >= -47 if it's > 132, I I'll drop it and rewrite your code as follows:
float totalAngle = 0f;
while(true)
{
float speed = -1f;
float fractionalAngle = speed * Time.deltaTime;
transform.RotateAround(pivotPoint.transform.position, Vector3.up, fractionalAngle);
totalAngle += fractionalAngle;
if(fractionalAngle < -180)
{
renderer.enabled = false;
StopCoroutine("rotate");
}
yield return null;
}
You were asking about 180 so I made it 180. Your code had -1 so I left the speed negative and went to -180. Change speed to get it faster.
I wouldn't use eulerAngles here at all but rather track how much you have rotated and make sure you rotate exactly 180°.
// Adjust this via the Inspector
public float anglePerSecond = 90f;
IEnumerator Start()
{
renderer.enabled = true;
yield return new WaitForSeconds(2);
var rotated = 0f;
while(rotated < 180f)
{
var step = Mathf.Min(anglePerSecond * Time.deltaTime, 180 - rotated);
transform.RotateAround(pivotPoint.transform.position, Vector3.forward, -step);
rotated += step;
yield return null;
}
renderer.enabled = false;
}

Ensure rotation takes the same amount of time regardless of angle to be rotated in Unity

I hope somebody can help. I have a script that rotates a globe, over which a plane (which is stationary) flies from 1 country to another. This all works fine and is achieved with the following script
while (!DestinationReached)
{
Vector3 planetToCountry = (Destination.localPosition - Vector3.zero);//.normalized; //vector that points from planet to country
Vector3 planetToCamera = (camTr.position - tr.position).normalized; //vector that points from planet to camera/plane
Quaternion a = Quaternion.LookRotation(planetToCamera);
Quaternion b = Quaternion.LookRotation(planetToCountry);
b = Quaternion.Inverse(b);
newRotation = a * b;
tr.rotation = Quaternion.Slerp(tr.rotation, newRotation, 0.01f);
if (Approximately(tr.rotation, newRotation, 0.0001f)) //if here the plane has arrived
{
Debug.Log("Destination reached");
DestinationReached = true;
}
yield return new WaitForEndOfFrame();
}
It essentially calculates the angle between the plane (the camera is attached to the plane GO and views it from above) and the destination the globe needs to rotate to so that the plane looks as though it flies to the destination.
The issue I have is I need to make the flight time uniform regardless of the angle the globe must rotate, so lets say it must be 5 seconds, regardless if the plane flies from Paris to Ireland or Paris to Australia. Anybody have any ideas on how to do this.
I have to admit, I nicked this script for the web, as my Vector and Quaternion mathematics is hopeless :)
If you want to be flexible and e.g. add some easing at beginning and end but still finish within a fixed duration I would do it like this (I'll just assume here that your calculating the final rotation is working as intended)
// Adjust the duration via the Inspector
[SerializeField] private float duration = 5f;
private IEnumerator RotateRoutine()
{
// calculate these values only once!
// store the initial rotation
var startRotation = tr.rotation;
// Calculate and store your target ratoation
var planetToCountry = (Destination.localPosition - Vector3.zero);
var planetToCamera = (camTr.position - tr.position);
var a = Quaternion.LookRotation(planetToCamera);
var b = Quaternion.LookRotation(planetToCountry);
b = Quaternion.Inverse(b);
var targetRotation = a * b;
if(duration <= 0)
{
Debug.LogWarning("Rotating without duration!", this);
}
else
{
// track the time passed in this routine
var timePassed = 0f;
while (timePassed < duration)
{
// This will be a factor from 0 to 1
var factor = timePassed / duration;
// Optionally you can alter the curve of this factor
// and e.g. add some easing-in and - out
factor = Mathf.SmoothStep(0, 1, factor);
// rotate from startRotation to targetRotation via given factor
tr.rotation = Quaternion.Slerp(startRotation, targetRotation, factor);
// increase the timer by the time passed since last frame
timePassed += Time.deltaTime;
// Return null simply waits one frame
yield return null;
}
}
// Assign the targetRotation fix in order to eliminate
// an offset in the end due to time imprecision
tr.rotation = targetRotation;
Debug.Log("Destination reached");
}
So the problem here is the t used on your Quaternion.Slerp method, it's constant. This t is the "step" the slerp will do, so if it's contstant, it won't depend on time, it will depend on distance.
Try instead to do something like this, being timeToTransition the time you want that every rotation will match:
public IEnumerator RotatintCoroutine(float timeToTransition)
{
float step = 0f;
while (step < 1)
{
step += Time.deltaTime / timeToTransition;
//...other stuff
tr.rotation = Quaternion.Slerp(tr.rotation, newRotation, step);
//...more stuff if you want
yield return null;
}
}
Edit: addapted to your code should look like this
float timeToFly = 5f;
while (!DestinationReached)
{
step += Time.deltaTime / timeToTransition;
Vector3 planetToCountry = (Destination.localPosition - Vector3.zero);//.normalized; //vector that points from planet to country
Vector3 planetToCamera = (camTr.position - tr.position).normalized; //vector that points from planet to camera/plane
Quaternion a = Quaternion.LookRotation(planetToCamera);
Quaternion b = Quaternion.LookRotation(planetToCountry);
b = Quaternion.Inverse(b);
newRotation = a * b;
tr.rotation = Quaternion.Slerp(tr.rotation, newRotation, step);
if (step >= 1) //if here the plane has arrived
{
Debug.Log("Destination reached");
DestinationReached = true;
}
yield return null();
}

moving a 2d object from point a to b without changing its rotation

I'm trying to move a 2d object from point a to b without changing its rotation in unity
I've tried to use Vector2.Lerp() but its not working
Vector2 pointB = new Vector2(20, 10);
Vector2.Lerp(transform.position, pointB, 3F);
The code should move the object from point a to b in 3F seconds
First, Vector2.Lerp doesn't change the value of the first parameter. You'll want to assign the new value to transform.position if you want to change the transform's position that way.
Secondly, you need to update the transform's position once every frame to keep the transform moving smoothly.
Thirdly, Vector2.Lerp will only produce positions between start and end with a t between 0 and 1. This t should relate to the ratio of how much time has passed since this movement started over how much time will complete the movement.
This is a good use for a coroutine:
private IEnumerator GoToInSeconds(Vector2 pointB, float movementDuration)
{
Vector2 pointA = transform.position;
float timeElapsed = 0f;
while (timeElapsed < movementDuration)
{
yield return null;
timeElapsed += Time.deltaTime;
transform.position = Vector2.Lerp(pointA, pointB, timeElapsed/movementDuration);
}
}
Here's an example of how to use it in Start:
void Start()
{
Vector2 pointB = new Vector2(20, 10);
StartCoroutine(GoToInSeconds(pointB, 3f));
}

How to implement lerp for smooth movement

I want achieve moving object on x axis only with lerp to get smoothly movement.
this is picture what i need
I don't know how i can implement lerp to this code to get smooth movement between these values, it now works but it teleport the player and that is not smooth movement what i want to achieve
This is my working code that teleports player:
void Start()
{
}
void Update()
{
if (Input.GetMouseButtonDown(0))
{
Vector3 desiredPos = new Vector3(transform.position.x + 1.5f, transform.position.y, transform.position.z);
transform.position = desiredPos;
}
if (Input.GetMouseButtonDown(1))
{
Vector3 desiredPos = new Vector3(transform.position.x -1.5f, transform.position.y, transform.position.z);
transform.position = desiredPos;
}
}
I want to implement this but i don't understand how to do it .. When i put all code into update the player don't even move.. It only works for me when i copy paste all the code from docs, but how i can move the time from start method to update and always do the same to achieve to get smooth movement for player when going left and right i don't know really please help me guys..
This is the code that works but i don't know how to change it for my example..
https://docs.unity3d.com/ScriptReference/Vector3.Lerp.html
There are multiple ways. I would not use Translate as this gives you little control here but rather e.g. MoveTowards which makes sure you have no over shooting at the end. Use this for a linear movement with a given moveSpeed:
// set move speed in Units/seconds in the Inspector
public float moveSpeed = 1f;
private Vector3 desiredPos;
private bool isMoving;
private void Update()
{
if (!isMoving && Input.GetMouseButtonDown(0))
{
desiredPos = transform.position + Vector3.right * 1.5f;
isMoving = true;
}
if (!isMoving && Input.GetMouseButtonDown(1))
{
desiredPos = transform.position - Vector3.right * 1.5f;
isMoving = true;
}
if(isMoving)
{
transform.position = Vector3.MoveTowards(transform.position, desiredPos, moveSpeed * Time.deltaTime);
// this == is true if the difference between both
// vectors is smaller than 0.00001
if(transform.position == desiredPos)
{
isMoving = false;
// So in order to eliminate any remaining difference
// make sure to set it to the correct target position
transform.position = desiredPos;
}
}
}
Or as you asked use Vector3.Lerp like e.g.
// a factor between 0 and 1
[Range(0, 1)] public float lerpFactor;
...
transform.position = Vector3.Lerp(transform.position, desiredPos, lerpFactor);
lerpFactor has to be a value between 0 and 1 where in our case 0 would meen the object never moves and 1 it directly jumps to the target position. In other words the closer you set it to 0 the slower it will reach the target, the closer you set it to 1 the faster it will reach the target.
a lot of people do this to get "smooth" movements but what actually happens is e.g. if you set 0.5 for lerpFactor then every frame the object is placed in the middle between current position and target position.
That looks somehow smooth, moves very fast at the beginning and very very slow at the end ... but: It actually never really reaches the target position but just gets very slow.
For your case that is fine since anyway we compare the current and target position using == with a precision of 0.00001. One just has to have in mind how Lerp works.
But with this you won't have any control neither over the move speed nor the duration.
If you want overall more controll (as I do) I would recommend to use a Coroutine (it is not absolutely neccessary and you could do the same in Update as well but in my eyes Coroutines are better to maintain and keep track of).
Than you could also make a smooth eased-in and eased-out movement with an always fixed duration regardless how far the distance is
// set desired move duration in seconds
public float moveDuration = 1;
private bool isMoving;
privtae void Update()
{
if (!isMoving && Input.GetMouseButtonDown(0))
{
StartCoroutine(transform.position + Vector3.right * 1.5f, moveDuration);
}
if (!isMoving && Input.GetMouseButtonDown(1))
{
StartCoroutine(transform.position - Vector3.right * 1.5f, moveDuration);
}
}
private IEnumerator Move(Vector3 targetPosition, float duration)
{
if(isMoving) yield break;
isMoving = true;
var startPosition = transform.position;
var passedTime = 0f;
do
{
// This would move the object with a linear speed
var lerpfactor = passedTime / duration;
// This is a cool trick to simply ease-in and ease-out
// the move speed
var smoothLerpfactor = Mathf.SmoothStep(0, 1, lerpfactor);
transform.position = Vector3.Lerp(startPosition, targetPosition, smoothLerpfactor);
// increase the passedTime by the time
// that passed since the last frame
passedTime += Time.deltaTime;
// Return to the main thread, render this frame and go on
// from here in the next frame
yield return null;
} while (passedTime < duration);
// just like before set the target position just to avoid any
// under shooting
transform.position = targetPosition;
isMoving = false;
}
and you could still extend this example to also take the dtsnace to move into account like e.g.
var actualDuration = duration * Vector3.Distance(startPosition, targetPosition);
and then later everywhere use actualDuration.
Use transform.Translate instead:
public float moveSpeed = 3f;
void Update ()
{
//Moves Left and right along x Axis
transform.Translate(Vector3.right * Time.deltaTime * Input.GetAxis("Horizontal")* moveSpeed);
}

Slow down Rigidbody

I am adding forward force based on GetAxis ("Vertical") and a speed modifier up to normal max speed.
I am also allowing "speed boost" that adds more force on top of the normal added force up to boosted max speed.
what i am having hard time with is slowing the object down once boost period is over
So basically if you were booster to a faster speed then normal max speed and you cannot speed boost no more, slow the object back to normal max speed.
this is what i got so far:
float velocity;
float magnitude = myRigidBody.velocity.magnitude;
float vertical = Input.GetAxis ("Vertical");
if (Input.GetKeyUp(KeyCode.LeftShift)) {
boost = false;
}
if (Input.GetKeyDown (KeyCode.LeftShift)) {
boost = true;
}
if (!boost) {
velocity = vertical * speed;
Vector3 force = myRigidBody.transform.forward * velocity;
if (magnitude + force.sqrMagnitude < normalMaxSpeed) {
myRigidBody.drag = 0f;
myRigidBody.AddForce (force, ForceMode.Impulse);
} else if (magnitude + force.sqrMagnitude >= maxTrust + 50) {
myRigidBody.drag = 1.5f;
}
} else if ( magnitude < boostedMaxSpeed) {
velocity = vertical * (speed + speedBoost);
myRigidBody.AddForce (myRigidBody.transform.forward * velocity, ForceMode.Impulse);
This some what working but there must be a better solution other then changing drag.
Other than changing the drag, there are still other ways to do this. You just have to experiment to see which one works best.
1.Change the drag
myRigidBody.drag = 20f;
2.Add force to the opposite velocity.
public Rigidbody myRigidBody;
void FixedUpdate()
{
Vector3 oppositeVelocity = -myRigidBody.velocity;
myRigidBody.AddRelativeForce(oppositeVelocity);
}
3.Decrement or Lerp the current velocity back to the normal force.
public Rigidbody myRigidBody;
Vector3 normalForce;
void Start()
{
normalForce = new Vector3(50, 0, 0);
//Add force
myRigidBody.velocity = new Vector3(500f, 0f, 0f);
//OR
//myRigidBody.AddForce(new Vector3(500f, 0f, 0f));
}
void FixedUpdate()
{
//Go back to normal force within 2 seconds
slowDown(myRigidBody, normalForce, 2);
Debug.Log(myRigidBody.velocity);
}
bool isMoving = false;
void slowDown(Rigidbody rgBody, Vector3 normalForce, float duration)
{
if (!isMoving)
{
isMoving = true;
StartCoroutine(_slowDown(rgBody, normalForce, duration));
}
}
IEnumerator _slowDown(Rigidbody rgBody, Vector3 normalForce, float duration)
{
float counter = 0;
//Get the current position of the object to be moved
Vector3 currentForce = rgBody.velocity;
while (counter < duration)
{
counter += Time.deltaTime;
rgBody.velocity = Vector3.Lerp(currentForce, normalForce, counter / duration);
yield return null;
}
isMoving = false;
}
4.Change the dynamic friction of the Physics Material. You can't use this method because it requires collider which you don't have.
//Get the PhysicMaterial then change its dynamicFriction
PhysicMaterial pMat = myRigidBody.GetComponent<Collider>().material;
pMat.dynamicFriction = 10;
It's really up to you to experiment and decide which one works best for you. They are useful depending on what type of game you are making. For example, #4 is mostly used when rolling a ball because it uses physics material which requires frequent collision to actually work.
The method used in #3 gives you control over how long the transition should happen. If you need to control that then that's the choice.

Categories

Resources