I am using Vector3.Lerp in unity game to simply move a gameobject from one position to other smoothly. Below is my code:
public class playermovement : MonoBehaviour {
public float timeTakenDuringLerp = 2f;
private bool _isLerping;
private Vector3 _startPosition;
private Vector3 _endPosition;
private float _timeStartedLerping;
void StartLerping(int i)
{
_isLerping = true;
_timeStartedLerping = Time.time ; // adding 1 to time.time here makes it wait for 1 sec before starting
//We set the start position to the current position, and the finish to 10 spaces in the 'forward' direction
_startPosition = transform.position;
_endPosition = new Vector3(transform.position.x + i,transform.position.y,transform.position.z);
}
void Update()
{
//When the user hits the spacebar, we start lerping
if(Input.GetKey(KeyCode.Space))
{
int i = 65;
StartLerping(i);
}
}
//We do the actual interpolation in FixedUpdate(), since we're dealing with a rigidbody
void FixedUpdate()
{
if(_isLerping)
{
//We want percentage = 0.0 when Time.time = _timeStartedLerping
//and percentage = 1.0 when Time.time = _timeStartedLerping + timeTakenDuringLerp
//In other words, we want to know what percentage of "timeTakenDuringLerp" the value
//"Time.time - _timeStartedLerping" is.
float timeSinceStarted = Time.time - _timeStartedLerping;
float percentageComplete = timeSinceStarted / timeTakenDuringLerp;
//Perform the actual lerping. Notice that the first two parameters will always be the same
//throughout a single lerp-processs (ie. they won't change until we hit the space-bar again
//to start another lerp)
transform.position = Vector3.Lerp (_startPosition, _endPosition, percentageComplete);
//When we've completed the lerp, we set _isLerping to false
if(percentageComplete >= 1.0f)
{
_isLerping = false;
}
}
}
}
The code works fine and the gameobject moves smoothly between two points. But it takes about 1 sec to reach destination. I want to make it move faster. I have tried decreasing the value of float timeTakenDuringLerp but the speed isn't affected. I have followed this tutorial and the explanation there also says to change timeTakenDuringLerp variable in order to change speed but its not working here.
Any Suggestions please?
H℮y, thanks for linking to my blog!
Decreasing timeTakenDuringLerp is the correct solution. That reduces the time it takes for the object to move from start to finish, which is another way of saying "it increases the speed".
If there is a specific speed you want the object to move at, you'll need to make timeTakenDuringLerp a variable rather than a constant, and set it to distance/speed. Or better yet, don't use Lerp at all, and instead set the object's velocity and let Unity's physics engine take care of it.
Multiplying percentageComplete by a constant, as suggested by #Thalthanas, is incorrect. That causes the lerping updates to continue occurring after the lerping has completed. It also makes the code hard to understand because timeTakenDuringLerp is no longer the time taken during the lerp.
I've double-checked with my code and it does indeed work, so the problem you are experiencing must be elsewhere. Or maybe you accidentally increased the time, which would decrease the speed?
The solution is to
multiply percentageComplete value with a speed value like,
transform.position = Vector3.Lerp (_startPosition, _endPosition, percentageComplete*speed);
So when you increase speed, it will go faster.
Related
Alright, so I'm making a game in Unity, and I tried to spawn in enemies randomly around a player. To control the rate of the spawning, I created a private bool spawnCooldown variable. Then, there was an if {} statement which controlled the rate of spawning. The original code is below:
private bool spawnCooldown;
private void Start
{
spawnCooldown = Time.time;
}
private void Update
{
if (spawnCooldown < Time.time)
{
Instantiate(original, position, rotation);
spawnCooldown = Time.time + 3f;
// Note that original, position and rotation are just placeholders
// and not the actual code.
}
}
What's wrong with this code? Currently, it's just instantiating every frame update, and it seems that it's not testing the if statement at all.
You need to add the seconds of cooldown you want on the spawner when you declare it too, E.G spawnCooldown = Time.time + coolDownPeriodInSeconds;
You also need to set spawnCooldown to be a float, which stores numbers.
Currently you have stored it as a bool, which only stores true or false values, and therefore cannot be compared to Time.time further in the code.
Lastly you are missing the closing } character in the if block
What is happening currently is you are doing the following:
Setting spawn Cooldown to be the current Time
For every frame after that, checking if the new current time is greater than the old time you set as spawnCooldown. Since it always is, the code will then run through the spawn script.
If you change it to
private float spawnCooldown;
void Start
{
spawnCooldown = Time.time +3f ;
}
void Update
{
if (spawnCooldown < Time.time)
{
Instantiate(original, position, rotation);
spawnCooldown = Time.time + 3f;
}
//Note that original, position and rotation are just placeholders and not the actual code.
}
Then it works fine.
Good luck with Unity!
Ok, so I've managed to fix the problems with StarshipladDev's answer. The problem was that the clones were cloning themselves, as I was instantiating the gameobject itself.
I'm trying to get input from my character using the Input System package when I press the left/right arrows keys, however when I read the value of the input it currently snaps between 0, -1, and 1. This is similar to the Input Manager's GetAxisRaw, however, what I want is to be able to do something more like GetAxis where it's not instantly 1, but goes through a couple float numbers between 0 and 1 up until 1. Same when it goes back down as well.
Preferably, I'd like to know if the current Input System has a way to manage that on its own, rather than needing to adjust the input myself through any mathematical formulas over time.
What I'm trying to achieve is smooth movement left and right, where for a split second or so the character picks up speed before they reach the max, and where letting go they also still slide for a split second before stopping. Since what I'm doing is setting the velocity equal to the multiplied value of the speed (rigidbody.velocity = new Vector2(//input value * movement speed, rigidbody.velocity.y)).
Of course I understand that if there is no way to create a sort of "ramping" effect to the snapping numbers, I could make it an acceleration speed instead (rigidbody.velocity += new Vector2(blah blah)). The issue being I then need to insert extra code to set a cap for the speed, and to slow down the velocity when I let go so my character doesn't slide like they're on ice.
No, there doesn't seem to be an equivalent to GetAxis for the Unity Input System package, however I did attempt to emulate it. The Unity Docs note that Pasted from Unity Docs "the Horizontal and Vertical ranges change from 0 to +1 or -1 with increase/decrease in 0.05f steps."
So I used Mathf.Lerp(a, b, t) (https://docs.unity3d.com/ScriptReference/Mathf.Lerp.html) where my starting value (a) was 0, and my ending value (b) was the input (whether it be 1 or -1). My interpolation value (t) starts at 0 and I would add 0.05 to it each frame. This was a bit too slow, so I multiplied it by an int that I called inputSensitivity.
For the visual learners:
[SerializeField] private float moveSpeed = 5f;
private InputMaster inputActions;
private Rigidbody2D rigidbody2D;
static float movementIncrementOverTime = 0;
[SerializeField] int inputSensitivity = 100;
private void Awake()
{
inputActions = new InputMaster();
}
private void OnEnable()
{
inputActions.Enable();
}
private void OnDisable()
{
inputActions.Disable();
}
private void Start()
{
rigidbody2D = GetComponent<Rigidbody2D>();
}
void Update()
{
//Read the movement value
float moveInput = Running();
inputActions.Player.Movement.canceled += Movement_canceled;
//Move the player
rigidbody2D.velocity = new Vector2(moveInput * moveSpeed, rigidbody2D.velocity.y);
}
private float Running()
{
float moveInput = inputActions.Player.Movement.ReadValue<float>();
switch(moveInput)
{
case 1:
moveInput = Mathf.Lerp(0, moveInput, movementIncrementOverTime);
movementIncrementOverTime += (0.05f * inputSensitivity) * Time.deltaTime;
break;
case -1:
moveInput = Mathf.Lerp(0, moveInput, movementIncrementOverTime);
movementIncrementOverTime += (0.05f * inputSensitivity) * Time.deltaTime;
break;
default:
break;
}
return moveInput;
}
private void Movement_canceled(InputAction.CallbackContext obj)
{
movementIncrementOverTime = 0;
}
The Running() function is the key here, it makes it so that instead of snapping to 1 and -1, it gradually moves up to those values. Then, in the Update() function the line of code inputActions.Player.Movement.canceled += Movement_canceled; checks for when I release the key and resets my static float movementIncrementOverTime to 0 so I can once again ramp up speed when I decided to move in the opposite direction, or if I stand still and start walking again.
The only issue, which I have yet to fix, is that I haven't figured out how to get the character to slide slightly when you let go of the key, instead of instantly stopping when you let go.
I have a character that dashes using AddForce(), after dashing, i want him to auto walk in set speed using MoovePosition().
This project was made using the 3D engine and it work flawlessly. But I've changed it to 2D and after the AddForce, the character abruptly changes to the speed that MovePosition() set, it looks bad.
I thought about checking the velocity after the dash and when it reaches the same velocity as when auto-walk is happening, it calls for MovePosition. But it doesn't seem a good approach.. I'd love to receive some tips on this issue.
if(moveFactor!= 0)
{
Vector2 appliedForce = transform.position + transform.up * moveFactor;
Debug.Log("appliedForce: " + appliedForce.magnitude);
playerRigidbody.AddForce(appliedForce);
autoWalk = true;
moveFactor = 0;
}
if (autoWalk) playerRigidbody.MovePosition(walkForce);
moveFactor is a float value that a swipe detector script returns. The code is in FixedUpdate as well.
It would be helpful to see the problem instead of trying to reconstruct the picture from your description, but I'll try my best.
The Problem
The same frame you start the dash, you set autoWalk to true. As soon as the if statement is exited, the if (autoWalk) is evaluated and you start manually moving the RigidBody using MovePosition() — before the force has even been added.
I'm not sure why that ever worked in 3D, though.
Solution #1
Instead of using MovePosition(), move with AddForce(), with the default ForceMode2D; or with playerRigidbody.velocity, where-in you set it once, not every frame.
Solution #2
Instead of using AddForce(), for the dash, use the same old MovePosition() you use inside if (autoWalk).
Just increase the magnitude of walkForce several times for the duration of the dash.
Solution #3
The problem with using AddForce() for it is that you don't know when it's "finished", so you don't know when to transition to MovePosition().
If the above solutions don't work for you, implement the dash manually. You can do this with Vector2.Lerp() (or Vector3) inside a coroutine.
if (moveFactor != 0)
{
Vector2 endPosition = transform.position + transform.up * moveFactor;
StartCoroutine(Dash(endPosition));
moveFactor = 0;
}
Where Dash() is:
public float dashDuration = 1f;
private IEnumerator Dash(Vector2 endPosition)
{
autoWalk = false;
// Record the original position before you begin moving the object.
float startingPosition = transform.position;
float elapsed = 0f;
while (elapsed < dashDuration )
{
elapsed += Time.deltaTime;
Vector2 nextPosition = Vector2.Lerp(startingPosition, endPosition, elapsed / dashDuration );
playerRigidbody.MovePosition(nextPosition);
// Note: if the RigidBody is kinematic (isKinematic == true), this will simply teleport it to the given position.
// Otherwise, it will comply with its interpolation settings.
// You could directly modify playerRigidbody.position or transform.position (though the former is faster) for the same teleportation.
yield return null;
}
autoWalk = true;
}
appliedForce might not immediately work as endPosition — modify it so it does, in that case. dashDuration is in seconds.
Make sure Dash() is started only once, until it is finished. You can do this by saving the Coroutine returned by StartCoroutine() in a private field member and comparing it to null before you start, but you shouldn't have to. If the code inside the if (moveFactor != 0) statement is executed something like every frame, change your code accordingly.
I've not tested this, so I'm not sure it will check for collisions. If it doesn't but you need it to, you can add that funcionality by raycasting (or box-casting, sphere-casting, or whatever kind of Collider you use):
private IEnumerator Dash(Vector2 endPosition)
{
float startingPosition = transform.position;
float elapsed = 0f;
while (elapsed < dashDuration )
{
elapsed += Time.deltaTime;
Vector2 nextPosition = Vector2.Lerp(startingPosition, endPosition, elapsed / dashDuration );
// Add the raycast here and only execute the MovePosition() if the returned RaycastHit2D's collider property is null.
// (The raycast's should extend to nextPosition.)
playerRigidbody.MovePosition(nextPosition);
yield return null;
}
}
while (transform.position != new Vector3(desX, desY))
{
// 2 - Movement
Vector3 movement = new Vector3(
0.1f * desX,
0.1f * desY,
0);
//movement *= Time.deltaTime;
transform.Translate(movement);
}
This part of my program crashes the Unity engine and I'm pretty sure it's an infinite loop but I can't figure out why or how to fix it.
It's freezing your application because you're are not giving other scripts chance to run when the condition in the while loop is not met.
To fix that put that code in a coroutine function then add yield return null; to the while loop. This makes Unity to wait for a frame after each loop therefore given other scripts the opportunity to run every frame. This should fix the freeze issue whether the while loop exits or not. I would also suggest you use Vector3.Distance to determine when you are close to the destination.
public float reachThreshold = 0.2f;
void Start()
{
StartCoroutine(MoveBject());
}
IEnumerator MoveBject()
{
float distance = Vector3.Distance(transform.position, new Vector3(desX, desY));
while (distance > reachThreshold)
{
// 2 - Movement
Vector3 movement = new Vector3(
0.1f * desX,
0.1f * desY,
0);
//movement *= Time.deltaTime;
transform.Translate(movement);
//Wait a frame
yield return null;
}
}
If you really want to move GameObject to another position over time, see this post.
In general don't do things in while that are meant to happen in a per frame base! (Thanks Ron)
The result of while in the best case would be that your App stucks until eventually the vectors match, making the object "jump" in position. In the worst case they never match and your App freezes forever.
Instead you should use the Update() method, which is called each frame, and just move the object one step per frame.
To compare the Vectors you should better use Vector3.Distance. Using the == operator for Vector3 is basically ok but internally it does something equal to
Vector3.Distance(vectorA, vectorB) <= 0.00001f
which uses a very small thershold that might not match exactly. So Using Vector3.Distance you can set your own threshold e.g. to 0.1 to make it "easier" to match.
bool match = Vector3.Distance(transform.position, new Vector3(desX, desY) < 0.1f
To move an object towards another Unity actually already has a built-in method Vector3.MoveTowards e.g.
transform.position = Vector3.MoveTowards(transform.position, new Vector3 (desX, desY), 0.1f);
This takes care of Vectors3 comparing so you don't even need it anymore.
To make it smooth and framarate-save you were right already to use Time.deltaTime. Without this it would move 0.1 meters / frame but your framerate might not be stabil. Using Time.deltaTime makes it 0.1 meters / second which in almost all cases is what you actually want to achieve.
So to put it together your code should be something like
float desX;
float desY;
float moveSpeed = 0.1f;
void Update()
{
var targetPosition = new Vector3 (desX, desY);
transform.position = Vector3.MoveTowards(transform.position, targetPosition, moveSpeed * Time.deltaTime);
}
Note that in some cases MoveTowards still is not relayable enough e.g. if you want to track collisions or something. In this case refer here
My game is an over-the-shoudler endless runner style game. I have the obstacles coming down the screen using constant force. My question is, how do I make this Constant Force Script make the obstacle go gradually faster as the game progresses and the player goes longer into the game? If you try out this script yourself (just attach it to a cube or a sphere), you'll notice how it starts off slow at first then literally 2 seconds later, it goes into Rocket Speed lol. ( I am working on an Object Pooler Script also, since I do not want to waste CPU with Instantiate and Destroy) Thank you to all who attempt and help me solve this! :)
Here is the Script I made:
using UnityEngine;
using System.Collections;
[RequireComponent(typeof(ConstantForce))]
public class AddConstantForce : MonoBehaviour
{
float speed = 1.0f;
Vector3 force;
void Awake()
{
//Another way to "Require" the component.
if(!GetComponent<ConstantForce>())
gameObject.AddComponent<ConstantForce>();
//
force = GetComponent<ConstantForce>().force;
GetComponent<Rigidbody>().useGravity = false;
}
void Update()
{
GetComponent<ConstantForce>().force = force;
force += -transform.forward * speed * Time.deltaTime;
/* Transform is negative because in my game, I have the
illusion of the objects coming down my mountain.
*/
}
}
Usually, to do stuff like this, you need 2 variables:
1. Time in which you want the speed to increase in. (For example, increase every 5 seconds) secondsToIncreaseIn.
2. Value to increase by.(For example, increment speed by 2 every time in #1 seconds) valueToIncreaseBy.
This way you have flexible ways to speed up or slow down how much your speed increments by changing either variable. With experiment setting secondsToIncreaseIn to 5 and valueToIncreaseBy to 0.0007f made the game last longer. You can increase valueToIncreaseBy to make it even last longer.You can change the startingSpeed variable if the starting speed is too slow or high.
private float speed = 0f;
private Vector3 force;
private ConstantForce cachedConstantForce;
private float counter = 0; //Dont change(Used for counting)
public float startingSpeed = 5f;//Start with 5 speed
public int secondsToIncreaseIn = 5; //Increase in every 5 seconds
public float valueToIncreaseBy = 0.0007f; //Increase by 0.0007 in secondsToIncreaseIn time/seconds
void Awake()
{
//Another way to "Require" the component.
cachedConstantForce = gameObject.GetComponent<ConstantForce>();
if (cachedConstantForce == null)
{
gameObject.AddComponent<ConstantForce>();
}
cachedConstantForce = GetComponent<ConstantForce>();
}
void Start()
{
force = cachedConstantForce.force;
GetComponent<Rigidbody>().useGravity = false;
speed = startingSpeed;
}
void Update()
{
cachedConstantForce.force = force;
force = -transform.forward * speed * Time.deltaTime;
/* Transform is negative because in my game, I have the
illusion of the objects coming down my mountain.
*/
if (counter < secondsToIncreaseIn)
{
counter += Time.deltaTime;
}
else
{
//Time has reached to Increase value
counter = 0; //Reset Timer
//Increase Spped
speed += valueToIncreaseBy;
Debug.Log("Increased Speed!");
}
}
Initially I was going to suggest use Time.time as factor (since Time.time is constantly increasing), but then, if you have to wait for some input for the run to start, Time.time could already be to big the the time that the run actually starts.
Instead I would suggest to keep a third variable that works as Time.time (keeps updating every frame by Time.deltaTime) and use that third variable to constantly keep increasing the applied force.
For example:
float speed = 1.0f;
Vector3 force;
float time = 0;
void Awake()
{
//Another way to "Require" the component.
if(!GetComponent<ConstantForce>())
gameObject.AddComponent<ConstantForce>();
//
force = GetComponent<ConstantForce>().force;
GetComponent<Rigidbody>().useGravity = false;
}
void Update()
{
GetComponent<ConstantForce>().force = force;
time += Time.deltaTime;
force += (-transform.forward * speed * Time.deltaTime) * time;
/* Transform is negative because in my game, I have the
illusion of the objects coming down my mountain.
*/
}
NOTE: You will probably have to apply a bigger value to speed since this operation will return quite a small value. Or you could add another variable that will increase the small value returned by the operation. Also consider that once you start testing the difficulty you might need to use a bigger or smaller value than Time.deltaTime to increase time, to either make the game harder or easier.