I don't know if this is an easy question or not.
I'm trying to write a single function that slows down the player and returns them to normal speed after a certain amount of time. I founds some answers online but they all use a second function to set a timer, but I'm trying to contain the whole thing in one function.
I've already tried this:
public void slowDown(float slowAmount, float durationSlowed)
{
var playerInfo = player.GetComponent<FPSMovement>();
playerInfo.speed *= slowAmount;
float lengthSlowed = Time.time + durationSlowed;
if(Time.time > lengthSlowed)
{
playerInfo.speed /= slowAmount;
}
}
and calling it with:
slowDown (0.5f, 2f)
It manages to slow down the player, but doesn't return their speed to normal.
What am I missing here?
I assume this is unity.
You probably want to do it with IEnumerators and Coroutines. They distribute code to be executed over multiple frames. Something like this:
IEnumerator SpeedUp() {
speed *= 2;
yield return new WaitForSeconds(2.0f);
speed /= 2;
}
void Update (){
if(Input.GetKeyDown(***YOUR KEY HERE***)) {
StartCoroutine(SpeedUp());
}
}
This will double the speed of your character, wait 2 seconds, and halve it. If you want to add a cooldown to the amount of times you can add speed, just drop a boolean check.
Related
Using yield return new WaitForSeconds(waitTime);
within an IEnumerator, what is the shortest wait time other than 0? I have tried using a float number and have tried as low as 0.00001f for the waitTime, however i'm not sure if there is a limit or not?
The purpose is that I am having a player's coins added one unit at a time, so the meter is 'filling up' rather than instant. I have tried searching but I cannot seem to find the answer to what the shortest limit is for WaitForSeconds, so if anyone knows I'd greatly appreciate the answer.
Additionally, my code is as follows if anyone has any input on how I can speed up the process without making it instant, as it's just not quite quick enough and the player is having to sit for a while waiting for the coins to be added to the meter at the end of the game.
IEnumerator AddCoins()
{
yield return new WaitForSeconds(1);
while (userGainedCoins > 0)
{
if (addingSoundPlaying == false)
{
addingSound.Play();
addingSoundPlaying = true;
}
if (userGainedCoins == 1)
{
addingSound.Stop();
}
userCoins += 1;
userGainedCoins -= 1;
PlayerPrefs.SetInt("User Coins", userCoins);
yield return new WaitForSeconds(waitTime);
}
addingSoundPlaying = false;
}
I think it doesn't have a minimum time, it just waits until it reaches a certain amount before waiting. If you want, you can just add multiple coins at a time. I usually try not to mess with these kinds of things, and instead of messing with the coins, I would just mess with the value it displays. You could have a script that holds the number of coins and increases/decreases it based on the timestep. Something like this:
public int coins = 100;
public int displayedCoins = 0;
private void Update() {
int change = Mathf.CeilToInt(300 * Time.deltaTime);
if (displayedCoins < coins)
displayedCoins = Math.Min(displayedCoins + change, coins);
else
displayedCoins = Math.Max(displayedCoins - change, coins);
}
and with this you can just set the amount and forget about it, and it works with gaining and losing coins. You can also change the change value to something like int change = Mathf.CeilToInt(Mathf.Abs(coins - displayCoins) * Time.deltaTime) to make it have an ease-out effect, or whatever you want. (I didn't test this code.)
I want to move a rigidBody2D for a fixed amount of time with a fixed amount of velocity, when the player presses a button. I am calling the method in FixedUpdate() and the results are not consistent. Sometimes the rigidBody will travel more and sometimes less (Here is a relevant part of my code how i went about do this)
void Update()
{
GetPlayerInput();
}
private void FixedUpdate()
{
GroundCheck();
ApplayNormalPlayerMovement();
ApplyMove(); // This is the method of interest - I tried calling this in Update() with Time.DeltaTime - still inconsistent results.
MoveCooldownCounter(); // I tried calling this in Update() - inconsistent results also
}
IEnumerator MoveRB()
{
savedVelocityX = rb.velocity.x;
rb.gravityScale = 0;
savedXinput = xInput;
while (moveTimer >= 0)
{
if (xInput != 0)
xInput = 0;
cc.sharedMaterial = noFriction;
if (facingRight)
{
rb.velocity = new Vector2(moveSpeed, .0f); // I tried multiplying the moveSpeed by Time.DeltaTime and FixedDeltaTime - still inconsistent results.
} else
{
rb.velocity = new Vector2(-moveSpeed, .0f);
}
moveTimer -= Time.fixedDeltaTime;
yield return null;
}
moveTimer= moveDuration;
rb.gravityScale = gravity;
rb.velocity = new Vector2 (savedVelocityX, .0f);
xInput = savedXinput;
moveCooldownInternal -= Time.deltaTime; // I tried changing this to a specific float - still inconsistent results in physics..
}
private void MoveCooldownCounter()
{
if (moveCooldownInternal != moveCooldown)
{
moveCooldownInternal -= Time.deltaTime;
if (moveCooldownInternal <= 0)
{
moveCooldownInternal = moveCooldown;
}
if (moveCooldownInternal == moveCooldown && isGrounded)
canMove = true;
}
}
private void ApplyMove()
{
if (b_Fire3 && canMove)
{
StartCoroutine("MoveRB");
canMove= false;
}
}
Side note: right now i experience player input loss on occasions because i call this ApplyMove() in FixedUpdate() (will have to find a workaround for that as well - naturally if I can call the ApplyMove in Update() and get consistent results than this issue would be fixed).
Pleas excise my newbiness, i am trying to learn :)
Thank you in advance!
Add comment
Unity is a game engine. All game engines are based on the game loop - however Unity somewhat hides if from you, behind the 3 Update functions.
Not all parts are equally important. Drawing is generally skipped when the GPU is not ready or the FPS are capped. And Drawing and physics are generally calculated independantly of one another (unless you really messed up the development).
With Physics and Animations, you generally want to stuff to be consistent to the Game Tick Counter, not real time. All movement of game pieces and all animation should count in game ticks. Real Time is a nice figure and all, but ticks can have wildly different processing times. Especially since the CPU side drawing work also has to run in the same thread.
Just figure out your maximum ticks/second. And if you want to run something to run for "roughly" 10 ingame seconds, you multiply that value. It does not mater if the game on this machine needs 2 seconds for 200 ticks or 2 minutes for 200 ticks - with the number of ticks, the results will be consistent (unless the math itself is inprecise, like floating point math).
If it's not multiplayer game, it does not have to be hackproof and thus easiest fix is to capture the first frame of the button pressed and apply the force only on that frame, because of that we have to capture input on the Update function and also there immadietly apply the force even though it's suggested to do physics stuff in fixed update, but if you use delta time in update function for calculation it should be fine!
Now I think you know how to do that but I'll write it anyway we can either use apply force or set the velocity,
void Update()
{
if(Input.GetKeyDown("w"))
rb.velocity = new Vector2(rb.velocity.x, rb.velocity.y + 5f*Time.deltaTime);
}
Note: I didn't test it but it should be allright since GetKeyDown returns true only on the first frame when the button was pressed,
If it was just GetKey and in case of jump we would check the grounding of player, we could be during jump and still be grounded on the second frame even if we jumped because of not really precise ground checking.
PS. Use fixedDeltaTime in fixedUpdate method!
I have this piece of code right here that should make the block object move between the startPos and endPos objects, but something is wrong about it and I don't know what.
void FixedUpdate()
{
if (block.transform.position == startPos.transform.position)
{
check = false;
}
if(block.transform.position == endPos.transform.position)
{
check = true;
}
if (check == false)
{
block.transform.position = Vector3.Lerp(block.transform.position, endPos.transform.position, .03f);
}
if (check == true)
{
block.transform.position = Vector3.Lerp(block.transform.position, startPos.transform.position, .03f);
}
}
At some point the block will reach endPos, and then on its way back to startPos it will stop, because the functions will be executed simultaneously. But how is this possible because my if's right there should not allow this to happen?
In general you should always be using
Update → called every frame
instead of
FixedUpdate → called in certain realtime intervals
except you are dealing with Physics somehow (which doesn't seem to be the case here). Also see the Update and FixedUpdate Tutorial
The issue with Vector3.Lerp is that it doesn't behave as you expect.
I guess you like that it starts fast and then becomes "smooth" ... but actually this might be your problem.
It never reaches the target position really. It just gets closer and closer and slower and slower ...
... until at some moment the == with a precision of 0.00001f eventually becomes true.
So it might seem that it stopped but actually it might still be moving just really really slow.
For the following two alternatives you have to decide a bit what you want to control:
Option: The speed
If you want to have a linear velocity for the object you should rather use
// adjust via the Inspector
[SerializeField] private float moveSpeedInUnityUnitPerSecond = 1f;
// you should use Update here in general
void Update()
{
if (block.transform.position == startPos.transform.position)
{
check = false;
}
// always use else in cases where only on condition can be
// true at the same time anyway
else if(block.transform.position == endPos.transform.position)
{
check = true;
}
block.transform.position = Vector3.MoveTowards(block.transform.position, check ? startPos.transform.position : endPos.transform.position, Time.deltaTime * moveSpeed);
}
Option: The duration
If you rather want a smooth movement but control the duration it takes to reach the target you should use a Lerp but with a factor depending on the time like
// adjust via the Inspector
[SerializeField] private float moveDurationInSeconds = 1f;
private float passedTime;
// you should use Update here in general
void Update()
{
// prevent overshooting
passedTime += Mathf.Min(moveDurationInSeconds - passedTime, Time.deltaTime);
if(passedTime >= moveDurationInSeconds)
{
check = !check;
passedTime = 0;
}
var lerpFactor = passedTime / moveDurationInSeconds;
// and now add ease-in and ease-out
var smoothedLerpFactor = Mathf.SmoothStep(0, 1, lerpFactor);
var fromPosition = check ? endPos.transform.position : startPos.transform.position;
var toPosition = check ? startPos.transform.position : endPos.transform.position;
block.transform.position = Vector3.Lerp(fromPosition, toPosition, smoothedLerpFactor);
}
For this you could also use a Coroutine which usually is a bit easier to interpret and maintain:
// adjust via the Inspector
[SerializeField] private float moveDurationInSeconds = 1f;
// yes you see correctly one can directly use the Start
// as a Coroutine
private IEnumerator Start()
{
var fromPosition = startPos.transform.position;
var toPosition = endPos.transform.position;
// looks strange but as long as you yield somewhere inside
// the loop it simply means repeat the sequence forever
// just like the Update method
while(true)
{
var passedTime = 0f;
while(passedTime < moveDurationInSeconds)
{
var lerpFactor = passedTime / moveDurationInSeconds;
// and now add ease-in and ease-out
var smoothedLerpFactor = Mathf.SmoothStep(0, 1, lerpFactor);
block.transform.position = Vector3.Lerp(fromPosition, toPosition, smoothedLerpFactor);
passedTime += Mathf.Min(moveDurationInSeconds - passedTime, Time.deltaTime);
// reads like: "pause" here, render this frame and continue
// from here in the next frame
yield return null;
}
// once reached flip the positions
var temp = fromPosition;
fromPosition = toPosition;
toPosition = temp;
}
}
in both cases you could still add more flexibility and instead of simply using the moveDurationInSeconds use
var fixedDuration = moveDurationInSeconds * Vector3.Distance(fromPosition, toPosition);
this way the movement takes shorter if the positions are closer together and longer if they are further apart. This comes pretty close to the Lerp you used before regarding to the smoothness of motion but you can control very good how long the movement will take.
The third argument of Vector3.Lerp is a percentage of the distance between the first two arguments. When you pass in 0.03f, you're getting 3% closer to your end position but never actually getting exactly there (You can prove this by logging the block's position and the target's position and you'll see they're never perfectly equal).
Instead of checking if your block's position with the == operator (which works to an accuracy of 0.00001f) to the target position, you could simply check if it's close enough with Vector3.Distance:
if(Vector3.Distance(block.transform.position, endPos.transform.position) < 0.1f) {
You can make your "close enough threshold" (0.1f in the example) a named variable for easy adjustment until it feels right.
For starters, Update() loops every frame. FixedUpdate() depends on the number of frames per second set in the time setting. So, put your code into Void Update() instead.
If you refer to Vector3.MoveTowards documentation, you may find a much better approach to your problem.
The script below should do the trick. The target variable should be set to the position of your end point. Speed is how fast your object should move.
Inside Update(), the step variable is used to determine how far the object should move based off its speed and the time elapsed since it last moved. Finally, the last line changes the position of the object and records the new position.
public Transform target;
public float speed;
void Update() {
float step = speed * Time.deltaTime;
transform.position = Vector3.MoveTowards(transform.position, target.position, step);
}
So when my character gets hit by the enemies fire breath, I want to create the feel of the character being set on fire. So while the character is on fire I want him to lose a specific amount of health for a specific amount of time.
For example; lets say he is on fire for 3 seconds and I want to make him lose 30 health for being on fire, how would I evenly distribute losing 30 health for 3 seconds? I dont want the 30 damage to be applied instantly to the health, I want it to slowly tick away at the players health so that at the 3 second mark 30 damage has been dealt.
The game is being made with c#.
Thanks.
This is just like moving Gameobject over time or doing something over time. The only difference is that you have to use Mathf.Lerp instead of Vector3.Lerp. You also need to calculate the end value by subtracting the value you want to lose over time from the current value of the player's life. You pass this into the b or second parameter of the Mathf.Lerp function.
bool isRunning = false;
IEnumerator loseLifeOvertime(float currentLife, float lifeToLose, float duration)
{
//Make sure there is only one instance of this function running
if (isRunning)
{
yield break; ///exit if this is still running
}
isRunning = true;
float counter = 0;
//Get the current life of the player
float startLife = currentLife;
//Calculate how much to lose
float endLife = currentLife - lifeToLose;
//Stores the new player life
float newPlayerLife = currentLife;
while (counter < duration)
{
counter += Time.deltaTime;
newPlayerLife = Mathf.Lerp(startLife, endLife, counter / duration);
Debug.Log("Current Life: " + newPlayerLife);
yield return null;
}
//The latest life is stored in newPlayerLife variable
//yourLife = newPlayerLife; //????
isRunning = false;
}
Usage:
Let's say that player's life is 50 and we want to remove 2 from it within 3 seconds. The new player's life should be 48 after 3 seconds.
StartCoroutine(loseLifeOvertime(50, 2, 3));
Note that the player's life is stored in the newPlayerLife variable. At the end of the coroutine function, you will have to manually assign your player's life with the value from the newPlayerLife variable.
I suppose, what you are looking for is a Coroutine. Check out here and here for the documentation. It will allow you to do your custom health reducing actions separately from update function. Using coroutines you can make something happening by ticks, and you can determine how much time the tick is.
You could use couroutines. Something like this:
void OnHitByFire()
{
StartCoroutine(DoFireDamage(5f, 4, 10f));
}
IEnumerator DoFireDamage(float damageDuration, int damageCount, float damageAmount)
{
int currentCount = 0;
while (currentCount < damageCount)
{
HP -= damageAmount;
yield return new WaitForSeconds(damageDuration);
currentCount++;
}
}
So this is what I ended up doing. It causes the character on fire to lose 30 health and you can see the health ticking down instead of it happening over intervals.
IEnumerator OnFire()
{
bool burning = true;
float timer = 0;
while (burning)
{
yield return new WaitForSeconds(0.1f);
hp -= 1;
timer += 0.1f;
if (timer >= 3)
{
burning = false;
}
}
}
I am trying to make a game which "kind of" simulates the shooting of the "worms" game. The player can choose the position (circular) of an object and then, the force that is applied to the object should move in the direction its pointing towards. I tried using the AddForce(transform.right) code, but it would just go to the right. (2D BoxCollider and RigidBody2D)
Then comes the hard part, making the player choose the force by charging the power. When the player holds down the "f" key, I want the power to go up to a certain point. Once it reaches that point, I want it to go down again and then up again, so the player can choose the power he wants. I have no idea how to go about this.
It's been awhile since I've did Unity coding, so there may be some minor errors with my syntax but it should give you an idea of how to accomplish this. Your best bet for the loop is to use a coroutine to not block the main thread.
in Update() check for 'on key down' for F and start this coroutine:
IEnumerator Cycle()
{
float max = 10.0f;
float min = 1.0f;
float interval = 0.5f;
do
{
for(int i=min;i<max;i++)
{
PowerValue = i;
yield return new waitforseconds(interval);
if(!input.getkey(f))
break;
}
for(int i=max;i>min;i--)
{
PowerValue = i;
yield return new waitforseconds(interval);
if(!input.getkey(f))
break;
}
} while(input.getkey(f));
}
And back in update() use that powerValue with getKeyUp(f)
And here is PowerValue setup as a parameter that prevents code from setting the max and min outside of a 1 to 10 range (configurable)
private float powerValue = 1.0f;
public float PowerValue
{
get { return powerValue; }
set {
if(value>10f)
powerValue=10f;
else if (value<1f)
powerValue=1f;
else
powerValue=value;
}
}