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. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Resolved How to delay an action without coroutines?

Discussion in 'Scripting' started by Richard_Ingalls, Jan 19, 2023.

  1. Richard_Ingalls

    Richard_Ingalls

    Joined:
    Dec 16, 2021
    Posts:
    88
    I need to change a variable in an instance of another script after some time has passed, however, the reference to the script is created inside of a function, so invoking a function won't work. Creating a coroutine won't work either, for the same reason as why invoking won't work, unless I am missing something about coroutines.
    Code (CSharp):
    1. void CreateBullet(Vector3 offsetRotation)
    2.     {
    3.         GameObject b = Instantiate(electricGrenade, firepoint.position, firepoint.rotation);
    4.         b.transform.localScale = new Vector3(projectileSize / 5f, projectileSize / 5f, 0f);
    5.         b.transform.Rotate(offsetRotation);
    6.         //here is the issue.
    7.     }
     
  2. Brathnann

    Brathnann

    Joined:
    Aug 12, 2014
    Posts:
    7,146
    A simple way is to call a coroutine from CreateBullet, then yield in the coroutine and have your code after the yield do whatever you want. But, I don't understand what you are saying above as to why you don't think this would work. Can you show an example of you using a coroutine?

    There are also ways of doing this with Update and using your own timer.
     
    Yoreki likes this.
  3. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,938
    Perhaps this functionality should be on the instantiated bullet itself? You should reference the prefab by said component too, to make it easy to start up this behaviour.

    Say the component is called 'ElectricGrenade':
    Code (CSharp):
    1. [SerializeField]
    2. private ElectricGrenade electricGrenade;
    3.  
    4. private void CreateBullet(Vector3 offsetRotation)
    5. {
    6.     ElectricGrenade grenade = Instantiate(electricGrenade, firepoint.position, firepoint.rotation);
    7.     grenade.StartGrenade();
    8. }
    9.  
    10. public class ElectricGrenade : Monobehaviour
    11. {
    12.     public void StartGrenade()
    13.     {
    14.         StartCoroutine(GrenadeBehaviour());
    15.     }
    16.    
    17.     private IEnumerator GrenadeBehaviour()
    18.     {
    19.         yield return new WaitForSeconds(1f);
    20.        
    21.         // etc etc
    22.     }
    23. }
     
    Brathnann likes this.
  4. Richard_Ingalls

    Richard_Ingalls

    Joined:
    Dec 16, 2021
    Posts:
    88
    I was saying that a coroutine wouldn't work because it references a variable inside the CreateBullet() function, so unless I can put a coroutine inside the CreateBullet() function, it won't work.
     
  5. Brathnann

    Brathnann

    Joined:
    Aug 12, 2014
    Posts:
    7,146
    @spiney199 Example should cover what I was thinking.
     
  6. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,797
    You can just pass a reference into the coroutine, or if it's a more dynamic quantity (or the variable is a value type), pass a delegate or delegate container object in.

    For instance when I make an enemy, I don't give him a health variable.

    Instead I give him a conduit to the health variable that I have already created deep inside my savegame structure somewhere else, and the enemy uses the conduit to access his health.

    Looks something like:

    Code (csharp):
    1.     public class IntegerConduit
    2.     {
    3.         public readonly System.Action<int> Set;
    4.         public readonly System.Func<int> Get;
    5.  
    6.         public IntegerConduit(System.Func<int> Get, System.Action<int> Set)
    7.         {
    8.             this.Set = Set;
    9.             this.Get = Get;
    10.         }
    11.     }
    If I have a variable:

    Code (csharp):
    1. private int health;
    And I want to make a conduit to give to an enemy,

    Code (csharp):
    1. var conduit = new IntegerConduit(
    2.  Get: () => { return health; },
    3.  Set: (xxx) => { health = xxx; }
    4. );
    then the enemy doesn't care where that data is, he just calls conduit.Get() and conduit.Set()

    At any point in time 100% of my savegame data is up-to-date and can be saved.
     
    Bunny83 and spiney199 like this.
  7. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,938
    I like this pattern.

    Totally going to have to remember this one.
     
    Bunny83 and Kurt-Dekker like this.
  8. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,541
    Right, I created such variable proxies a couple of times in the past. One generic version is here.

    Though it's not really clear what "access to a local variable" would actually mean. Local variables only exist while the method is executing on the stack. So if something should happen at a later time, that variable would not exist anymore (unless it's captured in a closure). If just the value of that variable is needed, you can pass the variable in. If that variable does change and the coroutine needs an updated value after its wait time, just pass a
    Func<VariableType>
    in and create a read-only closure that way.

    An actual concrete example of what "variable" or value is needed and for what purpose would help to suggest a proper solution.
     
    spiney199 likes this.
  9. Juice-Tin

    Juice-Tin

    Joined:
    Jul 22, 2012
    Posts:
    233
    The entire create bullet should be a coroutine.
    Or you can make the function call a coroutine


    Code (CSharp):
    1.    
    2. void CreateBullet(Vector3 offsetRotation){
    3.           StartCoroutine(DoCreateBullet(offsetRotation));
    4. }
    5.  
    6. IEnumerator DoCreateBullet(Vector3 offsetRotation)
    7.         {
    8.             GameObject b = Instantiate(electricGrenade, firepoint.position, firepoint.rotation);
    9.             b.transform.localScale = new Vector3(projectileSize / 5f, projectileSize / 5f, 0f);
    10.             b.transform.Rotate(offsetRotation);
    11.          
    12.            yield return new WaitForSeconds(99);
    13.            b.whatever;
    14.         }
    15.  
     
    Last edited: Jan 20, 2023
  10. Richard_Ingalls

    Richard_Ingalls

    Joined:
    Dec 16, 2021
    Posts:
    88
    Oh, lol. It turns out I could just declare a public variable, thus allowing me to use Invoke. I don't know why I thought I couldn't.