Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Question invoke within a if statement

Discussion in 'Scripting' started by ChuckieGreen, Dec 6, 2022.

  1. ChuckieGreen

    ChuckieGreen

    Joined:
    Sep 18, 2017
    Posts:
    358
    Hi, I seem to be having some weird behaviour with invoking a function that is in a if statement. The code below is meant to check the amount of enemies within a scene, and if it is 0 spawn new enemies. This works with the if statement if i simply call the spawnEnemies function. But I wanted to put a delay on the spawning, so used Invoke, but now the enemies constantly spawn, even if the if statement should not be running (but it is for some reason). Is there something with the invoke method that I am not aware of that might cause this. The waveSpawningTimer is set to 5.0f

    Code (CSharp):
    1.  if(currentEnemyCount <= 0)                 //testing
    2.         {
    3.             Debug.Log("count less than");
    4.             Invoke(nameof(spawnEnemies), waveSpawningTimer);
    5.             //spawnEnemies();
    6.         }
    7.  
    8.     void spawnEnemies()
    9.     {
    10.  
    11.         for (int i = 0; i < spawnWaveEnemyCount; i++)
    12.         {
    13.             spawnLocation = generateSpawnLocation();
    14.             Instantiate(enemyPrefab, new Vector3(spawnLocation.x, spawnLocation.y, 0), Quaternion.identity);
    15.             currentEnemyCount++;
    16.         }
    17.         exponentialSpawnCount += Mathf.Pow(exponentialSpawnCount, exponential);
    18.         spawnWaveEnemyCount = (int)exponentialSpawnCount;
    19.  
    20.         currentWave++;
    21.     }
    22.  
     
  2. tleylan

    tleylan

    Joined:
    Jun 17, 2020
    Posts:
    616
    I'll submit a guess based upon what I see. Your currentEnemyCount is zero so you invoke a method to run in 5 seconds but the update is still continually running and since currentEnemyCount is still zero (for at least 5 seconds) it is called again. Add another flag indicating that you have called Invoke and add it to the if statement.
     
    Brathnann likes this.
  3. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    Handling your own timer is always a superior approach and it's SUPER easy.

    Code (csharp):
    1. private float TimeWithNoEnemies;
    and in Update():

    Code (csharp):
    1. void Update()
    2. {
    3.   if (WeHaveEnemies)
    4.   {
    5.     TimeWithNoEnemies = 0.0f;
    6.   }
    7.   else
    8.   {
    9.     TimeWithNoEnemies += Time.deltaTime;
    10.     if (TimeWithNoEnemies >= SpawnDelay)
    11.     {
    12.        TimeWithNoEnemies = 0.0f;
    13.        SpawnEnemies();
    14.     }
    15.   }
    16. }
    Simple, simple, simple.

    Cooldown timers, gun bullet intervals, shot spacing, rate of fire:

    https://forum.unity.com/threads/fire-rate-issues.1026154/#post-6646297

    GunHeat (gunheat) spawning shooting rate of fire:

    https://forum.unity.com/threads/spawning-after-amount-of-time-without-spamming.1039618/#post-6729841
     
    Ryiah likes this.
  4. ChuckieGreen

    ChuckieGreen

    Joined:
    Sep 18, 2017
    Posts:
    358
    The currentEnemyCount does increase when the enemies spawn, so it is not at 0 (I can check it in the inspector as using serializedField), which is why I am so confused at why the if statement is running, really weird.




    I was thinking of doing my own timer, but thought the Invoke is more of a proper way to do it, but I will just go with the timer then if you think it is better.

    Thanks for replying everyone :)
     
  5. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,922
    Yes it does increase when the enemies spawn. Though until then the count is still 0. So every frame you start another Invoke call. When the first one actually executes the count is no longer 0 and you would no longer start new Invoke calls. However all that have been piled up until now will run. Essentially one every frame.

    So for example, when your "waveSpawningTimer" is 1 second and you have a frame rate of 60 fps, you would start a new invoke every frame. After 1 second the first Invoke call runs, the count is no longer 0 and no new Invoke would be started. However during that 1 second you have called Invoke 60 times, all with one frame delay between them. So for the next second you would get one of those Invoke calls would run.
     
    Last edited: Dec 6, 2022
    Ryiah, Brathnann and Yoreki like this.
  6. tleylan

    tleylan

    Joined:
    Jun 17, 2020
    Posts:
    616
    It really isn't about Invoke vs your own timer. And it has everything to do with what we said about the Update being called repeatedly prior the counter changing. Set a flag and it will work.
     
  7. tleylan

    tleylan

    Joined:
    Jun 17, 2020
    Posts:
    616
    And if you will pardon one more comment. I don't think this all needs to be in the Update(). Something is getting rid of the enemies so checking in Update whether they are now zero seems a bit odd. Create a class that keeps track of the enemies and spawns them as needed and when the counter in that class reaches zero it knows to spawn more.

    You don't want to put every bit of game logic in your Update() method.
     
    Ryiah and Brathnann like this.
  8. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    I do. :)

    Why?

    Because I like to breakpoint my code and debug it.

    I know. I'm funny that way.

    Event-driven stuff is awesome.

    But what happens when an event simply doesn't fire?

    How come the last enemy didn't send a "there are zero enemies now!" event?

    Or did it perhaps run and something went wrong? How would you know?

    That last enemy is gone now, so I can't ask him.

    How come the next wave isn't spawning?

    Where do you put a breakpoint when the code isn't even being run?

    Also, Unity scripting is effectively single-threaded, so hiding it in a coroutine does nothing. Hiding it in an invoke also does nothing. (see below)

    You absolutely will not sink your performance in anything but the most insane use of ten billion Update() calls to check a boolean in Update(). Trust me. And if you finally do, then move to something event driven.

    Meanwhile I shall have already breakpointed my code, found my bug and moved on.

    Here is some timing diagram help:

    https://docs.unity3d.com/Manual/ExecutionOrder.html
     
    AnimalMan likes this.
  9. tleylan

    tleylan

    Joined:
    Jun 17, 2020
    Posts:
    616
    Not only do I disagree but I'd be willing to be the developers of Unity disagree. One simple reason is if there was no benefit few people would have asked for the feature and it wouldn't likely have been added.

    And if your event doesn't fire you find out why and fix it. You can add logging statements (that compile out for release).

    Probably the biggest benefit is the methods become unit testable which isn't the case with a lot of code in the Update loop. But re-use comes in strong as well. If you are single developer you can pretty much code any way you prefer but if you are part of a team you can't all be typing into the Update method.

    I'm not trying to sell you on modern development techniques simply trying to provide a counter-point lest someone think all the code needs to be in Update which it clearly doesn't.
     
    Munchy2007, Bunny83 and Kurt-Dekker like this.
  10. AnimalMan

    AnimalMan

    Joined:
    Apr 1, 2018
    Posts:
    1,164
    Ctrl-C everything from update and move when it’s debugged
     
  11. tleylan

    tleylan

    Joined:
    Jun 17, 2020
    Posts:
    616
    No disrespect but I think you must work alone. Once the code is given to QA that build is frozen. And it would mean moving everything back into Update if you plan on making modifications wouldn't it?

    Systems exist to limit unknown and unhandled behaviors and yet bugs get into application code anyway. Moving it all around on a regular basis only adds to the problems. :)
     
  12. AnimalMan

    AnimalMan

    Joined:
    Apr 1, 2018
    Posts:
    1,164
    hey I am not disagreeing I am just saying best of both. Say you write a system you are unsure of when you begin. We can plan but we can still oversight. Some point the other dev has to read through and navigate a page of executions. Be the structure of those executions be held in an update or if it’s just a lot of connected functions that begin somewhere. It is subjective. For example, only one programmer need be employed to manage the backend system. And their team may work the front end. But if you write a solution for work that is not your own product in the end, then yes being mindful of future coders could be important. But it all comes down to the system. Say you wanted to take it out of mono-behaviour or didn’t know if you were going to jump engines but keep a functional code.
     
  13. Max-om

    Max-om

    Joined:
    Aug 9, 2017
    Posts:
    499
    If you do not use some kind of event aggregation how do you communicate between models? For example if a player kills another player. Thats a typical case when you want a event sent to the UI to update the scoreboard. And its not that hard to debug. Just debug the publisher.

    Example from our game


    Code (CSharp):
    1.         [BRPC(DelayWithInterpolation = false)]
    2.         public void SubmitPoll(string map, byte gameMode, byte gameModeSetting, RpcContext ctx)
    3.         {
    4.             if (activePolls.ContainsKey(ctx.RpcCallerId)) return;
    5.             var poll = activePolls[ctx.RpcCallerId] = CreatePoll(ctx.RpcCallerId, map, gameMode, gameModeSetting);
    6.             UpdateNextTimeOutedPoll();
    7.          
    8.             EventBus.Publish(new PollUpdated { Poll = poll });
    9.             this.RPC(nameof(OnNewPoll), NetworkReceivers.Others, ctx.RpcCallerId.AsValueContainer(), map, gameMode.AsValueContainer(), gameModeSetting.AsValueContainer());
    10.  
    11.             CheckVotesPassed();
    12.             Notify();
    13.         }

    UI (Not same activePolls list or CreatePoll method)
    Code (CSharp):
    1.       public void Handle(PollUpdated evt)
    2.         {
    3.             if (!activePolls.ContainsKey(evt.Poll.Player))
    4.                 CreatePoll(evt.Poll);
    5.             else
    6.             {
    7.                 var poll = activePolls[evt.Poll.Player];
    8.                 poll.Refresh(evt);
    9.             }
    10.         }
    In this case I would put the break point on the if statement if the UI wouldnt update.
     
    Last edited: Dec 7, 2022
  14. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    The most common pattern I use is to connect stuff up with delegates at spawn time.

    For instance, I have a little jetblast tickler below my Jetpack Kurt Spaceship, which basically generates a little dust ring wherever the exhaust gases hit the ground. It is not the engine particles itself, but the white "splat" of dust below the flame:

    Screen Shot 2022-12-07 at 6.35.39 AM.png

    Here is how I attached it:

    Code (csharp):
    1.         JetblastNozzle = new GameObject( "JetblastNozzle").transform;
    2.         JetblastNozzle.SetParent( player.transform);
    3.         JetblastNozzle.localRotation = Quaternion.Euler( 90, 0, 0);
    4.         JetblastNozzle.localPosition = Vector3.zero;        // driven elsewhere depending on camera
    5.         jetblast_feeler.Attach( nozzle: JetblastNozzle,
    6.             GetAmount: () =>
    7.             {
    8.                 float amount = 30.0f * indicatedPower;
    9.                 return amount;
    10.             },
    11.             GetDistance: () =>
    12.             {
    13.                 return 8.0f;
    14.             }
    15.         );
    It calls those GetDistance and GetAmount delegates to find what the engine is doing and how far away to disturb the ground.

    I keep the local reference because it wiggles slightly depending on engine mode (steady or uneven), plus I also offset it slightly in first person camera mode so it "looks right," even if it would look wrong from the third person view.

    All sorts of stuff is hooked up like this. Here's how the player attaches the impact sensor:

    Code (csharp):
    1.         GenericImpactSensor.Attach(
    2.             rb: rb,
    3.             OnImpulse: OnImpactImpulse,
    4.             GetActive: () => {
    5.                 if (!ready) return false;
    6.  
    7.                 if (YouHaveDied) return false;
    8.  
    9.                 return true;
    10.             },
    11.             OnceOnly: false
    12.         );
    And there's a separate sensor that bridges noise to bumps and clunks as you are landing:

    Code (csharp):
    1.         BumpNoiseImpactSensor.Attach(
    2.             rb: rb,
    3.             GetActive: () => {
    4.                 if (!ready) return false;
    5.                 // doesn't matter if you died; we still keep making sound!
    6.                 return true;
    7.             }
    8.         );
    So basically everything is bridged by these little micro delegate connections, and since the objects are all parented to the Player, they all die with the player and get cleaned up.

    Same thing goes for connecting items to player save state. Let's say I have a class that contains the player's gold. Rather than copy that to some gold manager, then have to worry about getting it back to save it, I give the gold manager a direct bidirectional delegate to the stored gold model value, so the save state is always (in memory) up to date. For integers, I use this:

    Code (csharp):
    1. // just a simple bidirectional link object to
    2. // bidirectionally bridge data with presentation.
    3.  
    4. public class IntegerConduit
    5. {
    6.     public readonly System.Action<int> Set;
    7.     public readonly System.Func<int> Get;
    8.  
    9.     public IntegerConduit(System.Func<int> Get, System.Action<int> Set)
    10.     {
    11.         this.Set = Set;
    12.         this.Get = Get;
    13.     }
    14. }
    so instead of:

    Code (csharp):
    1. gold += 123;
    it would be:

    Code (csharp):
    1. int gold = goldConduit.Get();
    2. gold += 123;
    3. goldConduit.Set( gold);
    and the user never keeps track of the gold themselves.

    The next-most common pattern I use is to have central signaling mechanisms that interested parties can listen into, and anyone can signal. I mostly use my Datasacks module for this, but anything like a delegate works.

    Code (csharp):
    1.     void OnEnable()
    2.     {
    3.         DSM.SpaceFlightNotifyPlayerLanded.OnChanged += NotifyPlayerLanded;
    4.  
    5.         DSM.SpaceFlightThirdPersonHUD.IntentToggleSpaceFlightCamera.OnChanged += OnToggleCameraMode;
    6.     }
    7.  
    8.     void OnDisable()
    9.     {
    10.         DSM.SpaceFlightNotifyPlayerLanded.OnChanged -= NotifyPlayerLanded;
    11.  
    12.         DSM.SpaceFlightThirdPersonHUD.IntentToggleSpaceFlightCamera.OnChanged -= OnToggleCameraMode;
    13.     }
    Better living through lightweight delegates connections!
     
  15. Max-om

    Max-om

    Joined:
    Aug 9, 2017
    Posts:
    499
    Seems to be very little decoupling here. Which becomes hard to maintain fast in a large codebase. Also, your lambdas can allocate if you are not careful with what memebers you use (you can only use delegate parameters otherwise it allocates).

    Our EventBus is very lightway and fast basicly just (pseudo code).

    Code (CSharp):
    1. public void Publish<T>(T evt) where T : struct
    2.         {
    3.             var type = typeof(T);
    4.             if (!subscribers.ContainsKey(type)) return;
    5.  
    6.             var handlers = subscribers[type];
    7.  
    8.             for(int i = 0; i < handlers.Count; i++)          
    9.                 ((IEventHandler<T>)handlers[i]).Handle(evt);
    10.          
    11.         }
    Sure it includes one typecast, not a huge cost. Also since it uses an interface it forces none allocation like lambda can do if used wrong.
     
    Last edited: Dec 7, 2022
  16. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    I try to decouple the what I am doing, not so much the who I am doing it with.

    Decoupling the who tends to lead to code that is difficult to debug and over-genericized.

    Decoupling the what does not impair debugging, while it keeps things flexible for internal changes.

    My lambdas are welcome to allocate their brains out, I don't mind.
     
  17. Max-om

    Max-om

    Joined:
    Aug 9, 2017
    Posts:
    499
    It isn't harder than debugging a invocation of a delegate. And decoupling is a important step in making maintainable code. I guess we will not see eye to eye here
     
  18. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    Fair enough. I come from the POV that code itself is almost completely worthless. Code only has value to transform data (not my quote!), and code alone must still be understood and integrated for it to transform the correct data and to transform it correctly.

    If I was developing a generic jetpack game that anyone could use and plug whatever stuff into it to extend and improve it, yeah, I totally agree with you: decoupling can be a boon, genericness is a great, etc.

    However apart from my core of little simple classes shared by all games, absolutely NOTHING in any one of my games would ever be used in another of my games without significant modification. I see no value in trying to make an ultra generic "spaceship controller" because experience has proven time and time again, it's better to roll a fresh one, learn from everything I've done in the past, and make the new one do precisely what I need.

    And it's not like I haven't tried! Generic event connections just don't improve my problem space meaningfully, that's all.

    I am content to agree that we disagree and I hope that your way brings you great success and high value!
     
    Ryiah and AnimalMan like this.
  19. Max-om

    Max-om

    Joined:
    Aug 9, 2017
    Posts:
    499
    Lets say you want to Update both scoreboard and some other UI, play a sound. etc. The publisher needs to know about every subscriber with your method. Its not about generic code, each subscriber will have completely different code and function.

    And its not hard to debug. Recorded a quick demo of debugging.

     
  20. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    Totally generic problem, I totally agree with you on solving it with this approach.

    I agree with you so much that this problem is EXACTLY what my Datasacks solve: both data backing store as well as pub/sub signaling.

    20180724_datasacks_overview.png

    On the other hand, for all the myriad little bits and bobs and widgets and sensors and doodles and deedles that I have plumbed into the Jetpack Kurt Space Flight player example above, each one is quite different and has bidirectional interoperation. To try and cram all that through some kind of generic pub/sub system just would not improve it.
     
  21. Max-om

    Max-om

    Joined:
    Aug 9, 2017
    Posts:
    499
    But your code is coupled. With my example i can add how many publishers and subscribers of a certain event without they ever break because they have zero knowledge about each other
     
  22. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    So does Datasacks!

    The same signaling mechanism can be listened to by anyone, and you can push and pop if you need to hide other listeners temporarily, etc.

    And the key point about Datasacks is that my artists / designers can make the entire UI, all the prefabs, everything, drag totally generic Datasack handler scripts into place, and they just need to tell me it is done and I can write code and it all Just Works(tm).

    Best of all, if there is a transient bad value in some value, I can tag the Datasack to debug changes to it, I can force it to a value, I can Debug.Break when it changes, etc.
     
  23. Max-om

    Max-om

    Joined:
    Aug 9, 2017
    Posts:
    499
    The code your showed earlier was not decoupled but maybe you are talking about two different solutions now.
     
    Last edited: Dec 7, 2022
  24. AnimalMan

    AnimalMan

    Joined:
    Apr 1, 2018
    Posts:
    1,164
    Code (CSharp):
    1.  
    2.     private void Update()
    3.     {
    4.         if (Input.GetKeyDown(KeyCode.O))
    5.             Application.targetFrameRate = 60;
    6.         if (Input.GetKeyDown(KeyCode.P))
    7.             Application.targetFrameRate = -1;
    8.         ITERATEUNITS();
    9.         if (framedelay <= 0)
    10.         {
    11.             ANIMATE_AMBIENCE();
    12.             framedelay = FRAME_DELAY;
    13.         }
    14.         else
    15.             framedelay -= 0.05f;
    16.     }
    The great thing about mono is that you don't need to have the update. So in many scripts I write they are just data stores. But I may add or remove an update, the above update is just a means to iterate the active units and ambience during test. And allows me a convenient spot to put a few target frame rate checkers off the cuff just because it was quick to do. Likewise on my map editor script there is also an update that holds keycodes to activate developer functions that are to be removed from the public build. Objects of mine rarely hold any updates, this is to say a Unit itself does'nt hold its own update, it is not checking and updating its own health bar for example. It is part of an iteration and will happen when its turn comes to iterate, which is generally speaking every frame. A cam controller may contain another update for we have to check for clicks, drags, etc. and Control what happens when an object is selected in game and i right click or i left click, make a selection box or simply mouse over. The final game during game play will have likely only two perhaps three updates running.

    For my artist and desginer (me and me, and other aspects of myself) I say (now this screen is outdated now by a few months) but I used it to explain to my brother (who has no coding knowledge) what am I doing with my life. :]

    F0380E20-EF8B-40BA-B99A-2EDFD0A90F6B.png
    and so as there are not a tonne of contributors hey Si I’ll write this function for you and leave it here and you can peice it together or Si you broke my function with your new function and you fired me 3 weeks ago how are you going to progress? It’s pretty simple to manage the project in this way. And get an entry level young chap with a whole lot of Mojo you know what I mean, and just give them the ability to work smoothly within the parameter of the specified possibilities stress free ala text file directory dump

    so often we can talk about these things in very complex ways which makes it all seem a little bit more je ne say qua if you know what I mean, but at the end of the day, coroutine or update. Generic messengers. All these types of things are useful when your final output scope of the project is unknown or unplanned. Therefor I need a messenger system that has an indeterminate ability to send cues to another script without the messenger being overly personal to its function.

    But anyway I think the thread is straying a little into the realm of who has the best and most flawless system, and thus whose system provides authority in a claim to solution. Of which the solutions that had been provided, the most simple of all and fitting for the engine we are all working on is indeed wacking a timer in the update(); since nobody had specified they work part of a team or organisation it was likely never necessary to discuss the possibility of the solution regarding a team of college grads.