Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

C# function to reset a bool after a specified time

Discussion in 'Scripting' started by Emolk, Oct 10, 2019.

  1. Emolk

    Emolk

    Joined:
    Feb 11, 2014
    Posts:
    241
    So i have an 'Attacks' script that uses a spell or attack when the mouse is pressed. Each attack has a float attack speed and a bool. When a spell is used, i launch the spell then invoke a function by the attack speed like this:

    Code (CSharp):
    1. karmaOffCD  = false;
    2. Invoke("karmaCDReset", karmaAS);
    Code (CSharp):
    1. void karmaCDReset(){
    2.         karmaOffCD = true;
    3.     }
    There are going to be a lot of spells and attacks that work this way, so i was wondering instead of creating a new function for resetting the cooldown for each spell/attack, is it possible to write a function that takes in any bool and attack speed, and turns that bool to true after a certain amount of time (attack speed)?

    Cheers.
     
  2. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,194
    If your code is running in a MonoBehaviour, you can solve this with a coroutine:

    Code (csharp):
    1. private Coroutine karmaDelay;
    2. private bool karmaOffCD;
    3. private float karmaCooldown;
    4.  
    5. private void KarmaCDReset() {
    6.     karmaOffCD = true;
    7.  
    8.     if (karmaDelay != null)
    9.         StopCoroutine(karmaDelay);
    10.  
    11.     IEnumerator KarmaDelayRoutine() {
    12.         yield return new WaitForSeconds(karmaCooldown);
    13.         karmaOffCD = false;
    14.     }
    15. }
    Nesting the coroutine is only available in C# 7, which means Unity 2018.3 or later.

    I would recommend building some bool-on-cooldown wrapper, though. That'll make making changes to how cooldowns work easier, and the code easier to read.

    Code (csharp):
    1. // Cooldown:
    2. public class Cooldown {
    3.     public float cooldownDuration;
    4.     private float cooldownFinishedAtTime;
    5.  
    6.     public bool OffCooldown {
    7.         get => Time.time >= cooldownFinishedAtTime;
    8.         set => cooldownFinishedAtTime = Time.time + cooldownDuration;
    9.     }
    10. }
    11.  
    12. // use
    13.  
    14. public CooldownBool abilityCooldown = new CooldownBool { cooldownDuration = 5f };
    15.  
    16. public void TryToUseSomeAbility() {
    17.     if (abilityCooldown.OffCooldown) {
    18.         abilityCooldown.OffCooldown = false;
    19.         UseSomeAbility();
    20.     }
    21. }
    You could alternatively just use a float directly instead:

    Code (csharp):
    1. public float abilityCooldownDuration;
    2. public float abilityOffCooldownAtTime;
    3.  
    4. public void TryToUseSomeAbility() {
    5.     if (abilityOffCooldownAtTime > Time.time) {
    6.         abilityOffCooldownAtTime = Time.time + abilityCooldownDuration;
    7.         UseSomeAbility();
    8.     }
    9. }
    Though having it as a thing could make it easier to reason about. Depends on your style.
     
  3. Magiichan

    Magiichan

    Joined:
    Jan 5, 2014
    Posts:
    403
    This is a way to use inline functions instead of seperate functions.
    Code (CSharp):
    1.  
    2. public class YieldFunc : WaitUtils {
    3.  
    4.     public bool exampleBoolean;
    5.  
    6.     void Start()
    7.     {
    8.         exampleBoolean = true;
    9.         WaitAndDo(1.0f, () => exampleBoolean = false);
    10.     }
    11. }
    For this to work you'll have to extend the class you're using to WaitUtils, or move the functions from WaitUtils into the class you want to use.

    Code (CSharp):
    1.  
    2. public abstract class WaitUtils : MonoBehaviour {
    3.  
    4.     public Coroutine WaitAndDo(float timeInSeconds, Action action)
    5.     {
    6.         return StartCoroutine(Execute(timeInSeconds, action));
    7.     }
    8.  
    9.     private IEnumerator Execute(float timeInSeconds, Action action)
    10.     {
    11.         yield return new WaitForSeconds(timeInSeconds);
    12.         if (Application.isPlaying) action();
    13.     }
    14.  
    15. }
     
    Emolk likes this.
  4. palex-nx

    palex-nx

    Joined:
    Jul 23, 2018
    Posts:
    1,745
    You better not to relay on invoke/coroutines and create separate game object for this because object launched attack might be destroyed before attack reaches its target and all coroutines/invokes in it's script will be terminated. It's better to create separate game object for this sake like a delayed actions manager or rely on the scripts on the attack effect itself be it arrow, fireball or whatever.
     
    Magiichan likes this.
  5. Emolk

    Emolk

    Joined:
    Feb 11, 2014
    Posts:
    241
    This is perfect, thankyou!
     
  6. Emolk

    Emolk

    Joined:
    Feb 11, 2014
    Posts:
    241
    The script is on my Player object so no problems there!
     
  7. palex-nx

    palex-nx

    Joined:
    Jul 23, 2018
    Posts:
    1,745
    So your enemies won't have range attacks at all or you plan to do it with another script? Also, player may launch an attack and die in next frame, will it's object be destroyed or not?
     
  8. Emolk

    Emolk

    Joined:
    Feb 11, 2014
    Posts:
    241
    They already do have ranged attacks. I'm confused as to what you mean? When the Player launches an attack it spawns a completely different GameObject which handles the attack. Why would this script affect it?
     
  9. palex-nx

    palex-nx

    Joined:
    Jul 23, 2018
    Posts:
    1,745
    I was thinking your attack script belongs to attacker object, but if it's on the attack object, then everything is okay
     
    Magiichan likes this.