Search Unity

Heartbeat courutine fx

Discussion in 'Scripting' started by hrohibil, Jul 24, 2021.

  1. hrohibil

    hrohibil

    Joined:
    Apr 17, 2021
    Posts:
    280
    I can’t get my head around courutine
    I am sure for what I want do I need exactly that.
    I have a heartbeat sound fx I want to start looping every 1 sec.

    It starts when players health reaches a specific value...

    Please advice..
     
  2. gorbit99

    gorbit99

    Joined:
    Jul 14, 2015
    Posts:
    1,350
    Coroutines are not that complicated once you get the hang of them.

    You start one, then it can loop and wait without blocking unity and if the function ends, the coroutine ends.


    Code (CSharp):
    1. private IEnumerator HeartbeatCoroutine() {
    2.     while (Player is below some health) {
    3.         heartbeatAudioSource.Play();
    4.         yield return new WaitForSeconds(1);
    5.     }
    6. }
    Whenever the player takes damage, just check if he's below the target health value and if he is, start this coroutine
     
  3. hrohibil

    hrohibil

    Joined:
    Apr 17, 2021
    Posts:
    280
    THIS WORKED PERFECTLY........ Thank you so much.........

    So we wrote the condition to stop in the while statement, so when does one use StopCourutine??
     
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,689
    Again I would not reach for coroutines here.

    The reason: you can go below the critical health, then go up, then go back down, and every single point in there is a chance to screw up how many coroutines are actually running.

    Coroutines are great but they are the wrong solution for this problem.

    Instead, make a heartbeat script and have a variable to count if it is time yet:

    Code (csharp):
    1. private float heartBeatYet;
    In your Update() function, calculate the desired rate of heartbeat based on health:

    Code (csharp):
    1. float desiredInterval = 0;  // no heartbeat
    2. if (health < 15)
    3. {
    4.   desiredInterval = 2.0f;  // sorta hurting
    5. }
    6. if (health < 10)
    7. {
    8.   desiredInterval = 1.5f;  // hurting more
    9. }
    10. if (health < 5)
    11. {
    12.   desiredInterval = 1.0f;  // really hurting!
    13. }
    Now that you have that interval:

    Code (csharp):
    1. if (desiredInterval > 0)
    2. {
    3.   // countdown until heartbeat needed
    4.   heartBeatYet -= Time.deltaTime;
    5.  
    6.   // check if it is time for a heartbeat yet?
    7.   if (heartBeatYet <= 0)
    8.   {
    9.     // reset timer for next heartbeat
    10.     heartBeatYet += desiredInterval;
    11.  
    12.     // lub-dup...
    13.     HeartBeatSound.Play();
    14.   }
    15. }
    DONE. Now you can have multiple "urgencies" of heartbeat, no coroutines to start, stop, keep track of, etc. It just runs forever. When nothing is happening the only cost is a couple of compare statements.
     
    Last edited: Jul 24, 2021
    hrohibil likes this.
  5. hrohibil

    hrohibil

    Joined:
    Apr 17, 2021
    Posts:
    280
    I have a screen overlay, like a flashing/blinking where i tried to implement same method here, but that did not help..
    Any ideas?

    Basiclly i wanted the blinking/flashing to go on and off just like the heartbeat..

    Code (CSharp):
    1.  private IEnumerator FlashScreen()
    2.     {
    3.         while (currentHealth <= 40 && currentHealth > 10)
    4.         {
    5.             myBlod.Blood_Hit_Full_3 = 1f;
    6.             yield return new WaitForSeconds(1);
    7.             myBlod.Blood_Hit_Full_3 = 0f;
    8.             yield return new WaitForSeconds(0.5f);
    9.         }
    10.  
    11.      
    12.  
    13.     }
     
  6. hrohibil

    hrohibil

    Joined:
    Apr 17, 2021
    Posts:
    280
    wow Kurt.

    You were spot on.

    The courutine did actually mess something up as I also have a health pickup item which fills the health to full again but this time the courutine did not work.

    So I used your code and it was magic
    Thank you a million time...

    2 questions:

    1:
    when is it a good scenario to use courutine?

    2:
    The code you provided can I actually modify that to use in other time/ interval based setups like enemy shoot with interval etc..


     
  7. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,689
    There's never a need. As @PraetorBlue pointed out in a recent post of his, nobody NEEDS coroutines.

    They do solve a certain class of problem nicely however, primarily problems of sequencing a set of data or events to happen.

    Coroutines CAN be interrupted and restarted but doing so exponentially increases your chance of a bug, negating a lot of their inherent benefits.

    A lot of human behaviors are good coroutines: "Go take a shower."

    So that breaks down to:

    - get undressed
    - start the shower
    - wait until it is warm enough (loop and test water)
    - step into the shower
    - loop: as long as there are dirty parts:
    ---> keep washing and rinsing
    - loop: as long as your hair is dirty:
    ---> keep lathering and rinsing
    - final rinse off
    - shut off shower
    - step out and dry off
    - get dressed again
    - DONE!

    But what you had above was a heartbeat and blood spurt that mostly is NOT happening... when it starts to happen it should only continue until you are healed up, or you are dead.

    That is much more amenable to a finite state machine with these states:

    - healthy (above 15hp)
    - injured (10 to 15 hp)
    - more injured (5 to 10 hp)
    - critical (1 to 5 hp)
    - dead (0hp)

    At any instant you can leap completely from one state to another. You could be healthy at 100hp and fall into lava and go straight to 0hp, or be slightly injured and fall to 0hp in lava.

    With a coroutine you'd have to write code at ALL the stages to fall through all the intervening things, skip heartbeat 1, blood spurt1, heartbeat 2, all that code can have bugs in it.

    With a state machine you just go from healthy to dead when you fall in the lava.

    And lets say your healer casts "resurrect 10hp" and hauls you out before the game declares you game over? Bam, right up to "more injured" and heartbeat going, no code in between, don't have to "go up through" critical.

    The construct I wrote above is basically a state machine with an observer pattern: it constantly observes your health to decide what state to be in. Then there's just a cooldown on the heart beat.
     
    Last edited: Jul 24, 2021
    hrohibil likes this.
  8. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,689
    The observe-and-decide-state pattern forms the basis for TONS of computer game mechanisms... TONS!

    - check where the player is, if he's close, go aggro, else mellow
    - check if I am at my goal, if so, stop, otherwise keep going
    - check if my magazine has less than 5 bullets, if so reload, otherwise keep patrolling.
    - etc.

    And get this mind-blower:

    If the "observe-and-decide-state" was expensive computationally, you can stick the entire process into a single coroutine loop that only runs once every few seconds.

    It's also useful to only observe once in a while, like wait randomly from 1 to 3 seconds and then react if the player is within the room. If each enemy has differently-chosen times, they will all react randomly rather than like a formation of precision marching troops.
     
    hrohibil likes this.
  9. hrohibil

    hrohibil

    Joined:
    Apr 17, 2021
    Posts:
    280
    Awesome explanation Kurt..

    I like the whole observe and decide setup..

    Kurt if may ask, could you put some words on the below code as I would like to know exactly what these line does, I have some ideas and you did comment as well on some of them.


    Code (CSharp):
    1.   // timer countdown
    2.   heartBeatYet -= Time.deltaTime;
    3.   // heartbeat yet?
    4.   if (heartBeatYet <= 0)
    5.   {
    6.     heartBeatYet += desiredInterval;


    Cheers
     
  10. hrohibil

    hrohibil

    Joined:
    Apr 17, 2021
    Posts:
    280
    @Kurt-Dekker
    Sorry to bother you Kurt again again..

    See the code I tried to adapt to a flashing hurt screen:
    The issue here is that it gets it value with this code:

    myBlod.Blood_Hit_Full_3 = 1f;

    but i need to switch between 0 and 1f.
    How do i do that so i switch between
    myBlod.Blood_Hit_Full_3 = 1f; and
    myBlod.Blood_Hit_Full_3 = 0f;

    Code (CSharp):
    1. float flashInterval = 0;  // no flash
    2.         if (target.currentHealth < 20)
    3.         {
    4.             flashInterval = 1.0f;  // sorta flash
    5.         }
    6.         if (target.currentHealth < 10)
    7.         {
    8.             flashInterval = 0.7f;  // flash more
    9.         }
    10.         if (target.currentHealth < 5 && target.currentHealth > 0)
    11.         {
    12.             flashInterval = 0.2f;  // really fast flash!
    13.         }
    14.  
    15.  
    16.         if (flashInterval > 0)
    17.         {
    18.             // timer countdown
    19.             ScreenFlashYet -= Time.deltaTime;
    20.  
    21.             // heartbeat yet?
    22.             if (ScreenFlashYet <= 0)
    23.             {
    24.                 ScreenFlashYet += flashInterval;
    25.  
    26.                 myBlod.Blood_Hit_Full_3 = 1f;
    27.             }
    28.         }
     
  11. To be honest, I wouldn't even do this. It's waste of resources, needless calculation. Just cut your audio clip properly, make one beat, add 1 second tail (or beginning), then just play it in loop when the time comes.
    Additional benefit is that you can speed up or slow down the beat properly (by changing the pitch or in middleware like fmod with more sophisticated methods) if you need it (dying, fear, heart race, whatever).