How to subtract hundreds/thousands of times a second in unity? - c#

I am currently working on an idle game but am having trouble with time. The code below runs when a business is activated in order to prevent the player activating the business again before it finishes. The variable "subtractTime" is the time it takes for the business to finish multiplied by 100.
There are different kinds and levels of businesses that require a different amount of time each. When you upgrade the business to certain levels, the time it takes to make money cuts in half.
When the time is lower than a second, the code below works as it is fast enough to not be able to tell it can't keep up. However, when dealing with larger amounts of time, such as 6 seconds, it does not subtract fast enough.
while (subtractTime > 0)
{
completed = false;
yield return new WaitForSeconds(.01f);
subtractTime = subtractTime - 1;
}

There are multiple ways to achieve that.
One simple way is to just wait until the timer has finished:
IEnumerator StartCooldown(float subtractTime) {
subtractTime *= 0.1f;
completed = false;
yield return new WaitForSeconds(subtractTime);
completed = true;
}
The only problem with this is that you don't have the remaining time updated as time passes, which may be useful for other things such as a cooldown bar.
A workaround to that would be something like this:
IEnumerator StartCooldown(float subtractTime) {
subtractTime *= 0.1f;
completed = false;
while (subtractTime > 0) {
subtractTime -= Time.deltaTime;
yield return null;
}
completed = true;
}
Time.deltaTime is just the time elapsed between the last frame and the current one, and yield return null just stops the execution of the Coroutine and resumes it on the next frame.
Note: you can remove the subtractTime *= 0.1f; line in both examples if the value you pass to the function is in seconds, since both examples work with time measured in seconds :)

The problem is the fixed time WaitForSeconds(.01f) because you don't know if the players' CPU can keep to that schedule, it may be too slow or be busy on other tasks. Instead we use the time it took to complete the game loop each frame, which is called Time.deltaTime:
IEnumerator ITimeSomething(float timeLimit)
{
float t = 0;
while (t < timeLimit)
{
t+= Time.deltaTime;
yield return null;
}
// do something when the time runs out
}

Related

What Is The Shortest WaitForSeconds Time Possible, Except 0?

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.)

Use WaitForSeconds for timing recorded commands

What I'm trying to do is a system to replay a list of commands I previously recorded. The list I use contains a class with the command and the time that was recorded (with Time.time), in this way I can decide when to play each command in sequence (for example I execute the first command at 1 second after I press record, and the second command at 5 seconds, in this way when I want to replay the commands they will be replayed waiting the exact seconds).
When I replay the command I used a coroutine with WaitForSeconds, but I discover because is frame rate dependent If the command ends at the middle of the frame the time starts from a cumulative delay (the wait time of the command plus the offset extra to finish the frame) and because this I tried to build something that predicts this problem, but when I replay the commands, the result is a slowdown replay.
private IEnumerator ExecuteBodyAnimation(List<UserBodyRecord> bodyAnimations)
{
// Prevent multiple calls
if (IsAnimationPlaying)
yield break;
IsAnimationPlaying = true;
float extraTime = 0;
for (int i = 0; i < bodyAnimations.Count; i++)
{
// Exit if we want to stop the animation sequence
if (!IsAnimationPlaying)
break;
int currIndex = i,
nextIndex = i;
// Prevent OutOfRangeException
if (currIndex < bodyAnimations.Count - 1)
nextIndex++;
UserBodyRecord currBodyAnimation = bodyAnimations[currIndex],
nextBodyAnimation = bodyAnimations[nextIndex];
// Calculate the time difference between the current time recorded animation and the next one recorded
float currentAnimTime = currBodyAnimation.timeRecorded;
float nextAnimTime = nextBodyAnimation.timeRecorded;
float differenceTime = nextAnimTime - currentAnimTime;
// Send the body pose list to the provider
bodyProvider.SetBodyPose(currBodyAnimation.humanPoses);
float lastRealTime = Time.realtimeSinceStartup;
// Wait the time between animations
yield return new WaitUntil(() =>
{
// Exit if we want to
if (!IsAnimationPlaying)
return true;
// Calculate how much time has passed since we are waiting
float counterTime = Time.realtimeSinceStartup - lastRealTime;
// Add the extra time exceeded from last animation frame
counterTime += extraTime;
if (counterTime > differenceTime)
{
// Get the extra time
extraTime = counterTime - differenceTime;
return true;
}
else
{
return false;
}
});
}
// Wait a bit before stop the animation
yield return new WaitForSeconds(delayBeforeStopAnimation);
}
What could be the problem?
I'm not sure I know the answer but you can use :
float time = TIME_TO_WAIT;
while(time > 0)
{
time -= Time.deltaTime;
yield return null;
}

Execute if-statement after fixed amount of time

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.

while conditional inside coroutine does not loop, hangs

I have a coroutine that I want to contain a 2 second pause. I use a while loop that checks the diff between initialTime and now to check how long it's been:
IEnumerator MyCoroutine(){
Debug.Log("Before the counter");
//`Time.time` returns the number of seconds since app launch (i.e., `72.33448`)
float initialTime = Time.time;
Debug.Log("initialTime = " + initialTime);
float now = initialTime;
while(now - initialTime < 2.0f){
yield return null;
now = Time.time;
Debug.Log("now = " + now);
}
Debug.Log("After the counter");
//...Stuff that happens after delay
}
For some reason, about 1/5 times I run it, it will not exactly hang in the loop, but will fail to execute the entire coroutine: in the console I see that Debug.Log("now = " + now); only executes once and Debug.Log("After the counter"); never happens— I'd expect a proper while loop hang to print Debug.Log("now = " + now); infinitely.
What could be wrong with the logic around this timer that could cause this behavior?
Edit: I'd prefer to stick to Unity's norms of using StartCoroutine() and StopCoroutine() rather than System.Threading if possible.
The solution is fairly simple.
First off, you NEED an IEnumerator like you had, not an IEnumerable as was suggested in the comments. Very different things.
Second, Unity has a built in function that pauses your coroutine for how long you specify. Use either WaitForSeconds(2f) (2 being the amount of seconds) or WaitForSecondsRealtime(2f)
WaitForSeconds "waits" for 2 seconds while taking framerate into account.
WaitForSecondsRealTIme "waits" for 2 seconds without taking framerate into account.
IEnumerator MyCoroutine(){
Debug.Log("Before the counter");
yield return new WaitForSeconds(2f);
Debug.Log("After the counter");
//...Stuff that happens after delay
}

Change a value of 5 to 1 over exactly 600ms [duplicate]

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;
}
}
}

Categories

Resources