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.
Related
I've been having trouble with Quaternion lerps. I'm simply looking to rotate my character 90 degrees based on their current rotation. The script below executes that almost perfectly, except for the fact that my character rotates a full 90 degrees long before rotationTime reaches the max value of 1. For some reason, the value of rotationTime is not properly synced with the progress of the lerp, and I can't seem to figure out why. What am I missing?
public class Movement : MonoBehaviour
{
bool Rotating = false;
Quaternion targetRotation;
public float rotationTime = 0f;
public float speed = 0.1F;
private Rigidbody rb;
private void Awake(){
rb = GetComponent<Rigidbody>();
}
public void Turn(InputAction.CallbackContext context){ //executes when 'E' is pressed
if (context.started && Rotating == false) {
targetRotation = Quaternion.Euler(0,transform.eulerAngles.y + 90f,0);
rotationTime = 0;
Rotating = true;
}
}
void Update() {
if (Rotating == true) {
rotationTime = rotationTime + Time.deltaTime * speed;
transform.rotation = Quaternion.Lerp(transform.rotation, targetRotation,rotationTime);
};
if (rotationTime > 1) {
Rotating = false;
}
}
}
I suspect that the main issue here is that you are using transform.rotation in your Quaternion.Lerp. You change it every frame, so every frame the start rotation will be closer to the target rotation. You should add some kind of _initRotation variable, and set it to transform.rotation in your Turn method. I mean something like this:
public void Turn(InputAction.CallbackContext context)
{
if (context.started && Rotating == false)
{
targetRotation = Quaternion.Euler(0,transform.eulerAngles.y + 90f,0);
_initRotation = transform.rotation;
rotationTime = 0;
Rotating = true;
}
}
...
void Update()
{
...
transform.rotation = Quaternion.Lerp(_initRotation, targetRotation,rotationTime);
...
}
Also, you have a logical issue with the lerp function. It does not affect the result in your particular case, but it can cause problems later.
You increment your rotation time by Time.deltaTime * speed every frame, it is not correct as it is not time passed from the start of the rotation.
According to the Quaternion.Lerp documentation, t value is always clamped to [0, 1]. So it is more convenient to use normalized time value instead of abstract speed value (right now it has no physical sense, it is just a multiplier).
It would be much clearer to use something like this:
void Update()
{
...
rotationTime += Time.deltaTime;
transform.rotation = Quaternion.Lerp(transform.rotation, targetRotation, rotationTime / fullRotationTime);
...
}
Generally, I almost always work with carriers, and I recommend that you do the same.
public class Movement : MonoBehaviour
{
bool Rotating = false;
Vector3 targetRotation;
public float rotationTime = 0f;
public float speed = 0.1F;
private Rigidbody rb;
private void Awake()
{
rb = GetComponent<Rigidbody>();
}
public void Turn(InputAction.CallbackContext context)
{ //executes when 'E' is pressed
if (context.started && Rotating == false)
{
targetRotation = new Vector3(0, transform.eulerAngles.y + 90f, 0);
rotationTime = 0;
Rotating = true;
}
}
void Update()
{
if (Rotating == true)
{
rotationTime = rotationTime + Time.deltaTime * speed;
transform.eulerAngles = Vector3.Lerp(transform.eulerAngles, targetRotation, rotationTime);
};
if (rotationTime > 1)
{
Rotating = false;
}
}
}
Also consider using coroutines for more readable code and to improve performance.
Good work!
As it was mentioned already by this answer one of your main issues is that every time you use a new transform.rotation as interpolation start point
=> Your rotation starts fast and then gets slower and slower the closer you reach the target value.
There other issues here though:
You are using the transform.rotation.eulerAngles! 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.
And then in general, since I see there is a Rigibody involved you shouldn't set or read anything directly via the Transform component at all but rather only go through that rigidbody! Otherwise you might break/fight against the physics resulting in strange behavior and breaking collision detection.
In your case in my eyes it is way easier to control the entire thing in a Corouine which avoids the need for all the class fields and imho is way easier to understand, control and maintain:
public class Movement : MonoBehaviour
{
public float speed = 0.1F;
[SerializeField] private Rigidbody rb;
private bool isRotating = false;
private void Awake()
{
if(!rb) rb = GetComponent<Rigidbody>();
}
public void Turn(InputAction.CallbackContext context)
{
//executes when 'E' is pressed
if (context.started && !isRotating)
{
StartCoroutine(RotateRoutine());
}
}
private IEnumerator RotateRoutine()
{
// little safety check first
if(Mathf.Approximately(speed, 0f)
{
yield break;
}
// Just to be really sure you avoid any concurrent routines
if (isRotating)
{
yield break;
}
// Lock so no other routine ca be started
isRotating = true;
// wait until we are in the next physics frame
yield return new WaitForFixedUpdate();
// store the initial rotation -> go throug the rigibody not the transform
var start = rb.rotation;
var end = start * Quaternion.Euler(0, 90f, 0);
var duration = 1 / speed;
for (var rotationTime = 0f; rotationTime < duration; rotationTime += Time.deltaTime)
{
// this would be a linear growing factor from 0 to 1
var factor = rotationTime / duration;
// optionally you could add ease in and out at the ends
// basically you can add whatever curve function you like to grow from 0 to 1 within the given rotationTime
factor = Mathf.SmoothStep(0, 1, rotationTime);
// interpolate from start to end while the factor grows from 0 to 1
var rotation = Quaternion.Slerp(start,end, factor);
// again for rigibdoy rather do this instead of going through transform
rb.MoveRotation();
// again wait for the next physics update
yield return new WaitForFixedUpdate();
}
// Just to be sure to end on clean values
rb.MoveRotation(end);
// release the lock for the next routine to start
isRotating = false;
}
}
I have a problem with moving from one place to another in Unity over time. I would like my character to move from current position to current + 1 on Y. Unfortunately, looks like it does not get the current position properly or something, since if I debug what I wrote, it says that the magnitude is always 1, so the point is moving with me. Shouldn't it just check the current position and add 1 to Y, move to that position and then check again? I have no idea what's wrong with this code, or if it's strictly connected with how Unity checks positions and things in real time?
public bool moving = false;
private Vector3 dir;
void FrontMovement()
{
Vector3 movement = new Vector3(-Mathf.Sin(transform.eulerAngles.z * Mathf.PI / 180), Mathf.Cos(transform.eulerAngles.z * Mathf.PI / 180), 0f); // always moving front, even after rotation
if (moving == false)
{
dir = movement - transform.position;
moving = true;
return;
}
transform.Translate(dir.normalized * Time.deltaTime);
if(dir.magnitude <= Time.deltaTime)
{
Debug.Log("Finished movement");
moving = false;
}
}
void FixedUpdate()
{
Debug.Log(dir.magnitude);
FrontMovement();
}
I would also like to know how to do rotations over time.
https://docs.unity3d.com/ScriptReference/Vector3.Lerp.html
lerp also works for rotations
// Movement speed in units per second.
public float speed = 1.0F;
// Time when the movement started.
private float startTime;
// Total distance between the markers.
private float journeyLength;
void StartMoving() {
// Keep a note of the time the movement started.
startTime = Time.time;
Vector3 modifiedPosition = transform.position;
transform.position.y += 1.0f;
// Calculate the journey length.
journeyLength = Vector3.Distance(transform.position, modifiedPosition.position);
moving = true;
}
// Move to the target end position.
void Update()
{
if (moving) {
// Distance moved equals elapsed time times speed..
float distCovered = (Time.time - startTime) * speed;
// Fraction of journey completed equals current distance divided by total distance.
float fractionOfJourney = distCovered / journeyLength;
// Set our position as a fraction of the distance between the markers.
transform.position = Vector3.Lerp(startMarker.position, endMarker.position, fractionOfJourney);
if (fractionOfJourney >= 1.0f) {
moving = false;
}
}
}
You could use Coroutine + Vector3.Lerp to move with a specified amount of time:
public IEnumerator MoveToPosition(Transform transform, Vector3 position, float timeToMove)
{
var currentPos = transform.position;
var t = 0f;
while(t <= 1f)
{
t += Time.deltaTime / timeToMove;
transform.position = Vector3.Lerp(currentPos, position, t);
yield return null;
}
transform.position = position;
}
you call the coroutine in Start Method
StartCoroutine(MoveToPosition(transform, newposition, timeToMove))
You could use the same logic for Rotating, with Quaternion.Lerp or Slerp and Quaternion.LookRotation, of course you have lot of sample with rotation over time on WEB!! google is your friend...
public IEnumerator RotateToDirection(Transform transform, Vector3 position, float timeToRotate)
{
var startRotation = transform.rotation;
var direction = position - transform.position;
var finalRotation = Quaternion.LookRotation(direction);
var t = 0f;
while (t <= 1f)
{
t += Time.deltaTime / timeToRotate;
transform.rotation = Quaternion.Lerp(startRotation, finalRotation, t);
yield return null;
}
transform.rotation = finalRotation;
}
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);
}
This script is called when the user release the mouse button:
float rot_duration = 3f;
float rot_speed = 1.8f;
Quaternion final_rot;
void Start()
{
cubeMesh = GameObject.FindWithTag("CubeMesh");
Vector3 initial_rot = transform.rotation.eulerAngles;
final_rot = Quaternion.Euler(new Vector3(initial_rot.x, initial_rot.y, 180));
}
public void Update()
{
if (Input.GetMouseButtonUp(0))
{
StartCoroutine (DelayRotate (0.1F));
}
}
IEnumerator DelayRotate(float waitTime)
{
yield return new WaitForSeconds (waitTime);
float rot_elapsedTime = 0.0F;
while (rot_elapsedTime < rot_duration) {
cubeMesh.transform.rotation = Quaternion.Slerp (transform.rotation, final_rot, rot_elapsedTime);
rot_elapsedTime += Time.deltaTime * rot_speed;
yield return null;
}
}
This script makes a GameObject rotate, 0.1 seconds after mouse button release. The problem is that it "flips" the GameObject quickly then starts rotating.
I believe it is flipping due to final_rot2 = Quaternion.Euler(new Vector3(initial_rot.x, initial_rot.y, 180)); (because of 180 value) What should I do instead?
I looked at code carefully and I was able to spot two mistakes.
1. The one mistake that is causing the problem is:
cubeMesh.transform.rotation = Quaternion.Slerp (transform.rotation, final_rot, rot_elapsedTime);
The guy above me said you should replace transform.rotation with cubeMesh.transform.rotation. That is close but wont work. What you are suppose to is to get the current position of the GameObject outside the while loop and store it somewhere, then you can use it later on inside the while loop. For example,
Quaternion currentLocation = cubeMesh.transform.rotation;
while(...){
cubeMesh.transform.rotation = Quaternion.Slerp (currentLocation, final_rot, rot_elapsedTime);
...Other codes
}
2. Another mistake I found is that it looks like you are trying to rotate the object within time because you have a variable called rot_duration.
If this is true then you failed when you did Quaternion.Slerp (transform.rotation, final_rot, rot_elapsedTime);.
If you want the object to rotate within rot_duration amount of time, change rot_elapsedTime to rot_elapsedTime / rot_duration. Also remove rot_speed as that will NOT work if you want to rotate over time.
If this is NOT what you are trying to do then the first mistake I found should fix your problem.
Your final Code should look like something below:
float rot_duration = 10f;
float rot_speed = 3f;
Quaternion final_rot;
GameObject cubeMesh;
void Start()
{
cubeMesh = GameObject.FindWithTag("CubeMesh");
Vector3 initial_rot = transform.rotation.eulerAngles;
final_rot = Quaternion.Euler(new Vector3(initial_rot.x, initial_rot.y, 180));
}
public void Update()
{
if (Input.GetMouseButtonUp(0))
{
StartCoroutine(Delay(1));
}
}
IEnumerator Delay(float waitTime)
{
yield return new WaitForSeconds(waitTime);
float rot_elapsedTime = 0.0F;
//Get the current rotation
Quaternion currentLocation = cubeMesh.transform.rotation;
while (rot_elapsedTime < rot_duration)
{
rot_elapsedTime += Time.deltaTime;
cubeMesh.transform.rotation = Quaternion.Slerp(currentLocation, final_rot, rot_elapsedTime / rot_duration);
yield return null;
}
}
The problem here is that this doesn't update for each frame. I see that you add Time.deltaTime, but it doesn't update per frame, it only uses the value for the current frame several times, since you do everything in one update.
This code might work:
float rot_duration = 10f;
float rot_speed = 3f;
float rot_elapsedTime = 3f;
Quaternion final_rot;
public void Update()
{
if (Input.GetMouseButtonUp(0))
{
StartCoroutine (Delay (1));
}
if (rot_elapsedTime < rot_duration) {
cubeMesh.transform.rotation = Quaternion.Slerp (transform.rotation, final_rot, rot_elapsedTime);
rot_elapsedTime += Time.deltaTime * rot_speed;
}
IEnumerator Delay(float waitTime)
{
yield return new WaitForSeconds (waitTime);
rot_elapsedTime = 0.0F;
}
But as being said, transform.Rotate is probably better.
Edit: Updated after OP's edit with new code, adding a 1 sec delay.
I am building an infinite vertical platformer for mobile platforms and I am using the accelerometer to move the player left and right. The further the device is tilted the faster the player moves across the screen. Currently my player is a little too shakey and I would like to create a more fluid movement across the screen. Here is my code to move the character:
/********************************* Variables **************************************/
// Variables
float jumpForce = 700f;
float maxSpeed;
float acceleration;
/********************************* Start Method ************************************/
void Start ()
{
acceleration = Mathf.Abs (Input.acceleration.y);
}
/************************************ Fixed Update *************************************/
void FixedUpdate () {
if (acceleration < 0.2f) {
maxSpeed = 17;
} else if (acceleration < 0.9f) {
maxSpeed = 25;
} else {
maxSpeed = 40;
}
float move = Input.acceleration.x;
rigidbody2D.velocity = new Vector2 (move * maxSpeed, rigidbody2D.velocity.y);
/******************************* Collision Function ******************************/
void OnCollisionEnter2D(Collision2D coll)
{
foreach(ContactPoint2D contact in coll.contacts)
{
rigidbody2D.AddForce (new Vector2(0, jumpForce));
}
}
Can't you just use your Input.acceleration.y as the speed? That way you wont need the if conditions. If that's not enough, then multiply it by a multiplier.
void Update () {
maxSpeed = Mathf.Abs(Input.acceleration.y);
speedMultiplier = 30;
float move = Input.acceleration.x;
rigidbody2D.velocity = new Vector2 (move * maxSpeed * speedMultiplier, rigidbody2D.velocity.y);
You can just adjust the speedMultiplier to match your needs afterwards.
Also if you have some physics works (in this case rigidbody2D.velocity), you should use Update() instead of FixedUpdate().
You can use Mathf.Lerp method for smoothing the velocity values.
Try to use
maxSpeed = Mathf.Lerp(maxSpeed, 17, Time.deltaTime);
instead of
maxSpeed = 17;