Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 13 Next capture
2021 2022 2023
1 capture
13 Jun 22 - 13 Jun 22
sparklines
Close Help
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
0
Question by BouncedPhysical · Jul 17, 2021 at 10:26 PM · coroutinefloatdecreasegradually

Gradually reduce a float over time with a Coroutine that can be run multiple times simultaneously.

Hello guys! I could use some help!

I have this coroutine

 float currentHP = 100;
 
 IEnumerator GraduallyReduceHP(float damage, float rate)
 {
     // The value that currentHP should have after it has been gradually decreased
     float finalHP = currentHP - damage;
 
     while (currentHP > finalHP)
     {
         currentHP -= rate * Time.deltaTime;
         yield return null;
     }
     currentHP = finalHP; // to make sure that currentHP reaches the correct value and isn't wrong even by 0.0001 because of rate * Time.deltaTime
 
     yield break;
 }



This code works perfectly for me, the problem is that I want to run multiple "instances" of this coroutine.

Let's suppose that the health of the character is = 100. In my game the character can be poisoned, let's say he gets poisioned - so I start this coroutine GraduallyReduceHP(15, .1f) // "Poision" coroutine.

Which reduces the HP very slowly as I want, and it goes until currentHP reaches 85.

Now let's suppose an enemy attacks me while I am poisioned - I start the coroutine again with those parameters: GraduallyReduceHP(40, 10f) - Which rapidly makes my HP go down.

Everything goes perfectly until the "Poision" coroutine ends. When it does, my health becomes 85 (the value of finalHP inside the coroutine - and I can see why).

But I can't get a way to prevent this from happening. If I remove the line "currentHP = finalHP;" from the coroutine everything goes well, but finalHP isn't accurate (instead of being 85 when the Poision coroutine ends it becomes like 84.96849).

How can I get around this? Thanks!

Comment
Add comment
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

3 Replies

· Add your reply
  • Sort: 
avatar image
2
Best Answer

Answer by Bunny83 · Jul 18, 2021 at 02:17 AM

Your approach does not work if you want to have multiple coroutines affecting the CurrentHP at the same time. The main issue is your finalHP which is calculated once at the start of the coroutine. Just think through your code logically. Imagine you want to apply 10 HP damage over the time of 10 seconds (so 1 HP per second). Imagine the starting hp is 100. So When the coroutine is started, its finalHP is calculated as 100 - 10 == 90. If after just one second you start another coroutine, also 10 hp with a rate of 1HP / s, the second coroutine would calculate a finalHP of 99-10 == 89. Since now two coroutines run at the same time you will effectively subtract 2HP/s as expected. However the first coroutine will stop once currentHP reaches 90HP and the second one will stop when it reaches 89HP. So the second coroutine essentially just subtacted 1HP instead of 10 due to the time overlap.


What you have to do is just "count down" the damage you want to deal and that should be your condition.

 IEnumerator GraduallyReduceHP(float damage, float rate)
 {
     while (damage > 0)
     {
         float delta = rate * Time.deltaTime;
         if (delta > damage)
         {
             currentHP -= damage;
             break;
         }
         currentHP -= delta;
         damage -= delta;
         yield return null;
     }
 }

Note that your final line in your original code currentHP = finalHP; does not prevent floating point inaccuracies because they can already happen here:

 float finalHP = currentHP - damage;

In my version we just calculate a "delta" value that should be subtracted this frame. The if statement makes sure when we reach the end to not overshoot the amount we wanted to subtract. In essence as the damage value is counting down to 0 we count down currentHP in parallel by the same amount. Once delta is larger than the total remaining damage, we just subtract the remaining damage directly and quit. This coroutine can run in parallel with any other code that is changing currentHP.


In an actual system you would probably replace the currentHP -= delta; line with something like DealDamage(delta); That's because when you deal damage you usually want to check bounds so currentHP does not go below 0 or above maxHP. Likewise that is also the point where you want to check for the death of the player / object

Comment
Add comment · Show 1 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image BouncedPhysical · Jul 18, 2021 at 06:23 PM 0
Share

Thanks for the detailed reply! I ended up using a similar solution to what you have proposed! I even tested it and it generated anyhow some floating-point imprecision ( about .0001). But I guess could be because what you said,

"Note that your final line in your original code currentHP = finalHP; does not prevent floating point inaccuracies because they can already happen here:

float finalHP = currentHP - damage;"

Thank you!

avatar image
2

Answer by DrHerringbone · Jul 18, 2021 at 06:49 AM

I threw in a comment on how to round numbers but I think what you might want to do is use the Mathf.lerp Function. Not only does it make linearly interpolating (fancy words for gradually increase or decrease) but it does so at a constant rate that you define and it does so at a rate that can be reactive to your frame rate.

 float health;
 float healthBeforeDamage;
 bool doFixedDamageOverTime;
 bool doConstantDamageOverFixedTime;
 bool doConstantDamageIndefinitely;
 
 void update()
 {
    If(!doFixedDamageOverTime && !doConstantDamageOverFixedTime
        && !doConstantDamageIndefinitely)
     {
         healthBeforeDamage = health;
     }
 
     If(doFixedDamageOverTime)
     {
         DoFixedDamageOverTime(/some amount of damage/, /how fast you want to get there/);
     }
 
     If(doConstantDamageOverFixedTime)
     {
         DoConstantDamageOverFixedTime(/some amount of time/, /how fast you want the player to lose health/);
     }
 
     If(doConstantDamageIndefinitely)
     {
         DoConstantDamageIndefinitely(/rate of damage/);
     }
 
 }
 
 void DoFixedDamageOverTime(float amountOfDamage, float rate)
 {
     health = Mathf.lerp(health, healthBeforeDamage - amountOfDamage, rate * Time.deltaTime);
 }
 
 
 void DoConstantDamageOverFixedTime(float timeInSeconds, float damageRate)
 {
     doConstantDamageOverFixedTime = false;
     float totalDeltaTime = 0
     For(totalDeltaTime + Time.deltaTime < timeInSeconds; totalDeltaTime += Time.deltaTime)
     {
         health = Mathf.lerp(health, 0, rateOfDamage * Time.deltaTime);
     }
 }
 
 Void DoConstantDamageIndefinitely(float rateOfDamage)
 {
     health = Mathf.lerp(health, 0, rateOfDamage * Time.deltaTime);
 }
 
         

Comment
Add comment · Show 1 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image BouncedPhysical · Jul 19, 2021 at 03:01 PM 0
Share

Thank you for your solution! But I didn't managed to get it working as I wanted :(

avatar image
0

Answer by Llama_w_2Ls · Jul 17, 2021 at 10:34 PM

You should do one last check to see if your final hp is close to the desired final hp. If it is very close (they are the same when rounded to nearest whole number), then set it to that hp. Else, do nothing.


Or, just round the final output instead of setting it to the finalHP amount. This would, in theory, produce the same effect. Minor floating point errors won't be visible to the player. The health display would only read the health as an int (i hope)!

Comment
Add comment · Show 2 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image BouncedPhysical · Jul 17, 2021 at 10:50 PM 0
Share

Hello thanks for the reply!
How would I round the final output? Do you mean Rounding by Nearest? If this is the case, if I want to remove 6.55 of health, won't the round make me miss the .55? I'm sorry I couldn't figure it out

And no I actually display the health on a slider, so the decimal places are used.

If I keep that imprecision (that I saw it reaching the second decimal place (instead of 85 it gave like 85.03593)), what the issues can be? Is it that bad to have that imprecision? Can that imprecision grow to a unit or worse?

avatar image DrHerringbone · Jul 18, 2021 at 06:06 AM 0
Share

Off the top of my head and as a relative noob… you can round numbers using the following method:

 private float RoundXDecimals(int numOfDecimals, float numToRound)
 {
     Int deno$$anonymous$$ator = numOfDecimals * 10;
     Int numerator = (int)(numToRound * deno$$anonymous$$ator);
     If((float)numerator + 0.5f <= numToRound){
         numerator += 1;
     }
     Return (float)(numerator / deno$$anonymous$$ator);
 }
 

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

131 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

How to change this using a coroutine 1 Answer

Sliding speed decreases over time? 2 Answers

Using Coroutines to increase Float values C# 3 Answers

How to make UI Label text loop through colors ? (example attached) 1 Answer

Increase/Decrease Value With Time - Problem 4 Answers


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges