How do you understand IEnumerators?

From what I understand an IEnumerator is a list of instructions that have to happen in the order we give them, and the next instruction can’t / won’t start until that last instruction completes, but we could also set this to not even start the next instruction unless we call MoveNext(), so it’s like an assembly line where the next builder is either waiting for the parts or is waiting for the “go ahead”.

One example - this little machine at the supermarket that allows you to take a number when waiting to be looked after at the meat stands.

Take number 36 - wait for it to be called (by the co-routine) then do your business.
State is stored and displayed by the overhead box that shows what number was last called.

So the Ienumerator is the machine holding the list of tickets.

2 Likes

Um maybe defeating a boss, perhaps would need to defeat several horde waves before we can start attacking him, needing to destroy certain components e.g shields or shield generators, on a boss, before you can damage him etc.

For me, I will use IEnumerators in some of this is situation.
With MoveNext()/yield return … It possible to use it like :

  • Chain quest.
  • Story state. NPC state etc.
  • Boss phase like in WoW
  • Buff , or Action charge like Megaman charging his weapon.
    And other activity that need to go from Activity1 -> Activity2 -> Activity3 … etc.

very cool course. I have done almost no programming course. & now I fully understand : before I saw corroutines as methodes working over time (wich is kinda true) but for anyone that dont get it an IEnnumerator is a queu, a static collection with a given size & place in memory. The very clever thing is that u can use it in 2 ways : the first is instead of looping trough the whole “pringle tube” for loading an environemnt part per example wich would make the frame super long & freeze the game, u just load 1 pringle every frame. The other utility is that u can chose the lenght of the tube so u can execute a function during a given time.
this is fascination how cders created tools with the promming language they had & then those tool became part of the language :open_mouth:

I feel like they’re appropriate if you want to create a time-based conditional. Such as: if you have eaten the pringle you have, then you can take another pringle out of the can. The taking action is suspended (not permitted) until the eating is completed. The eating will take time (either frames or real time).

1 Like

One thing I learned recently is that you can store references to Coroutine instances as a variable. I was having an issue with a specific Coroutine for sequential spawning not stopping reliably, but storing it as a Coroutine variable and specifying that in StopCoroutine was able to fix the behavior.

1 Like

In the 2d course, I used an enumerator to give some time before reloading the level after the player died. For this case, the transition as it currently stands is quite jarring, loading straight into the next level. A coroutine could make the game slow down and then load the next level after a second or so, instead of loading the next level immediately.

IEnumerators look like a fancy for loop with extra features like being able to wait and what not. Looks pretty useful with properties though. I was thinking you could start coroutines when setting a property that way you could do things like attack periodically instead of having to check if a certain amount of time has passed to be able to use an attack off cooldown.

The waiting part in Unity is really related to the coroutine implementation that leverages the yield behavior of IEnumerators to do that job.

To be fair, iteration with an IEnumerator outside of the context of a coroutine (whether in Unity or “plain” C#) is more like handling the pages of a book.

Every time you request for the next page, the IEnumerator yields (returns) it whilst remembering where it was, like it is placing a bookmark in the book temporarily.

So as you request each new page, it repeats that process until you reach the end of the book or you just stop asking it to give you the next page - perhaps once you’ve found the text that you were looking for.

3 Likes

That’s an excellent analogy.

Could you use an IEnmerator to check if all enemies in the pringlestube have been killed before enabling a portal to another scene?

In the traditional sense of ienumerators? Probably, though whether you’d need to is doubtful.

This is because you’d be setting up ienumerator to basically iterate over your enemy component classes looking for those that meet the criteria, and there are already a number of built-in ways to do it assuming you’re already keeping track of your enemies in some kind of list/queue/dictionary, and they already expose an ienumerator you can foreach() over.

Or you could utilize linq and find the items that match your criteria.

Only if you had your own container for the objects would you maybe want to roll your own, but why? Chances are you’d gain no objective efficiencies over the code already available.

Under the hood, in most cases you are, although the most effective ways of achieving this wouldn’t reference IEnumerators directly…

For example: for the portal, you’d probably start the OnTriggerEnter with something like

if(EnemiesAreStillAlive()) return;

and then have a method

bool EnemiesAreStillAlive()
{
     foreach(AIController controller in FindObjectsOfType<AIController>())
     {
         if(controller.GetComponent<Health>().isAlive() return false;
     }
     return true;
}

Under the hood, the foreach loop is using an IEnumerator to cycle through each of the results of FindObjectsOfType(), but it’s nothing you need to worry about at all.

That Linq expression topdog mentioned, by the way, just for the fun of it:

If(FindObjectsOfType<AIController>().Where(AIController a => a.GetComponent<Health>().IsAlive()).ToList().Count>0) return;

Again, it’s actually iterating over the collection using an IEnumerator, but it’s all under the hood.

1 Like

Cool, thats sick! these things are powrful.
I tried writing out the code so that i wouldn’t loadscene unless every enemy connected to that portal is dead and then in scene2(Which is a non-linear like level that only has teles to spots around scene 1) make sure the player cant take any portals they havent unlocked. i did it by making a list on START of the all GameObjects enemies in Portal.cs and then .Remove() each time one dies in health. then when ever Ontriger is called with the player + portal collider i check the list. It works in scene1(becuase scene1 has all the enemies in it) but i think when i load scene2 the list gets rewritten on start in scene2 and there are no enemies to count. and the player can take any tele. also i know that i have asked for help but not provided the nessesary peices for a complete anwserable question. becuase i dont know what to give, pleae tell me. Also thank you so much for you previuos reply i appreciate your tiempo.

Oh i see, I tried to do this with a list and i ran into a problem when trying to check the enemies in scene1 with a portal in scene2. there were no enemies to find. how would i get back to that list in scene1.

Here Are the two scripts i manipulated
1rst one is health.cs 2nd is portal.cs

Summary
using UnityEngine;
using RPG.SceneManagement;

namespace RPG.Core
{
    public class Health : MonoBehaviour
    {
        [SerializeField] float healthPoints = 100f;

        GameObject assignedPortal;
        bool isDead = false;

        private void Start() {
            assignedPortal = GameObject.FindGameObjectWithTag(gameObject.tag + "Portal");
        }
Summary
        public bool IsDead()
        {
            return isDead;
        }

        //you could call death on update and update health
        public void TakeDamage(float damage)
        {
            healthPoints = Mathf.Max(healthPoints - damage, 0);
            if(healthPoints == 0)
            {
                Die();
                assignedPortal.GetComponent<Portal>().UpdateEnemyList(gameObject);
                GameObject.Destroy(gameObject, 1.5f);
Summary
            }
        }

        private void Die()
        {
            if (isDead) return;
            GetComponent<Animator>().SetTrigger("die");
            isDead = true;
            GetComponent<ActionScheduler>().CancelCurrentAction();
        }
    }
}
Summary
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.AI;

namespace RPG.SceneManagement
{
    public class Portal : MonoBehaviour
    {
        enum DestinationIdentifier
        {
            A,B,C,D
        }

        [SerializeField] int sceneToLoad = -1;
        [SerializeField] Transform spawnPoint;
        [SerializeField] DestinationIdentifier destination;
        [SerializeField] string enemyTag;
        List<GameObject> enemiesToKill = new List<GameObject>();
    
        private void Start()
        {
            Scene scene = SceneManager.GetActiveScene();
            DontDestroyOnLoad(gameObject);
            if(scene.name == "SandBox")
            {
                enemiesToKill.AddRange(GameObject.FindGameObjectsWithTag(enemyTag));
                foreach(GameObject i in enemiesToKill)
                {
                    print(i);
                }
            }
            print(enemyTag);
        }
        
        private void OnTriggerEnter(Collider other) {
            if (other.tag == "Player")
            {
                if (enemiesToKill != null)
                {
                    if (enemiesToKill.Count == 0)
                    {
                        StartCoroutine(Transition());
                    }
                }
            }
        }
Summary
        private IEnumerator Transition()
        {
            DontDestroyOnLoad(gameObject);
            yield return SceneManager.LoadSceneAsync(sceneToLoad);
            Portal matchingPortal = GetMatchingPortal();
            UpdatePlayer(matchingPortal);
            //matchingPortal = null;
            Destroy(gameObject);
        }

        private Portal GetMatchingPortal()
        {
            foreach (Portal portal in FindObjectsOfType<Portal>())
            {
                if(portal == this) continue; 
                if(portal.destination != destination) continue;
                return portal;

            }
            return null;
        }
        private void UpdatePlayer(Portal destPortal)
        {
            GameObject player = GameObject.FindWithTag("Player");
            player.GetComponent<NavMeshAgent>().Warp(destPortal.spawnPoint.position);
            //player.transform.position = matchingPortal.spawnPoint.position;
            player.transform.rotation = destPortal.spawnPoint.rotation;
        }
        public void UpdateEnemyList(GameObject enemy)
        {
            enemiesToKill.Remove(enemy);
        }
    }
}

hahaha im sure this looks insane but \o/

The enemies in Scene1 only exist in Scene1 (along with any variables)… so accessing them from Scene2 wouldn’t make a great deal of sense. Even if the list was Static, the references would be removed when you switched scenes.

A better approach than maintaining an active list is to wait until you want to access the portal, and then getting a list of enemies and checking their health stats… There really isn’t a need to maintain an active list (which can lose references due to circumstances beyond your control) when you can get that information on the fly

In Portal.cs:

bool EnemiesAllDead()
{
    var enemies=FindObjectsOfType<AIController>();
    foreach(var enemy in enemies)
    {
        if(enemy.GetComponent<Health>().isAlive()) return false;
    }
    return true;
}

Then in the OnTriggerEnter of Portal:

if(EnemiesAllDead())
{
     StartCoroutine(Transition());
}

Thank you for you reply. I am deffinently scrapping my crazy code for these simple clean lines and am very greatful for them, but would FindObjectsOfType(); called from portal in scene 2 be able to find enemies in scene1. like if the player kills one camp and uses that tele into scene2 i dont want them to be able to take a locked portal in scene2 back to a locked area in scene 1, only the portals that the player has cleared the camps.

No, this would only work for the characters in the current scene… The characters from scene 1 don’t exist in scene 2, and vice versa.

A quick logic point… if you are trying to prevent the player from going into Scene 1 while there are still enemies alive in scene 1… there will be no way for the player to eliminate the enemies in scene 1… I guess I don’t understand exactly what you are trying to achieve. My assumption was that you were looking to prevent the character from moving out of the current scene until he had killed all the enemies in the current scene (which my code would accomplish).

  • What conditions are you wanting to move between scenes exactly?

I am sorry i have been so unclear! I have scene one with 4 camps (barbarians, gangmembers, boss, pitofevil) each camp has a tele that unlocks when the camp is clear. the player can use the tele to enter scene 2 an area (where when i get actual skills will provide the player with items). scene 2 has all 4 teles but i dont want the player to use a tele that they havent first killed the respective camp in scene1. then when the player does laod back into scene1 they have to clear a camp again to get back to scene2. i think maybe i am way out of my “pay grade”. maybe i will put this to rest and get back to the actaul course

Privacy & Terms