How do you understand IEnumerators?

I see… That actually means the code that I provided won’t truly tell you if you’ve cleared what you need for Scene2, because mine assumed you wanted to wipe Scene1 before you could get to Scene2, but you’re only after the barbarians…

This is probably ventured a bit away from IEnumerators…

Ok, the first part is actually easy… you can’t move to Scene 2 until you’ve cleared a certain type of enemy… You’ll need to expose the characterClass in BaseStats, say a method
public CharacterClass GetCharacterClass() {return characterClass;}

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

The second part is much trickier. I’d probably design a “ConditionManager” and a Condition scriptable object to describe the conditions…

[CreateAssetMenu(filename="New Condition", menuName = "Condition")]
public class Condition: ScriptableObject
{
    ///These Conditions need to be created in a folder named Resources,
    /// and cannot under any circumstances have the same name as anything else 
    ///in any other folder named Resources.
    public int scene=1;
    public CharacterClass characterClass;
}
///Put this the player character.
public class ConditionManager: Monobehavior, ISaveable
{
     public Condition[] conditionsToManage;
     Dictionary<Condition, bool> conditionList;

      void Awake()
      {
            if(conditionList==null)
                 conditionList=new Dictionary<Condition, bool>();

     public void TestConditions()
    {
         foreach(Condition condition in conditionsToManage())
         {
               if(condition.scene = SceneManager.GetActiveScene().buildIndex)
               {
                     conditionList[condition] = EnemiesAllDead(condition.characterClass);
               }
        }
}

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

public bool ConditionSatisfied(Condition condition)
{
    TestConditions();
     if(conditionList.ContainsKey(condition) return conditionList[condition];
     return false;
}

     
[System.Serializable]
public struct ConditionStruct
{
    public string conditionName;
    public bool fulfilled;
}

               
public object CaptureState()
{
     List<ConditionStruct> conditions = new List<ConditionStruct>();
     foreach(Condition condition in conditionsToManage)
     {
          If(conditionList.ContainsKey(condition)
          {
               ConditionStruct cs = new ConditionStruct();
               cs.conditionName = condition.name;
               cs.fullfilled=conditionList[condition];
               conditions.add(cs);
           }
      }
      return conditions;
}

public void RestoreState(object state)
{
     List<ConditionStruct> conditions = (ConditionStruct)state;
     conditionList = new Dictionary<Condition, bool>();
     foreach(ConditionStruct condition in conditions)
     {
          Condition c = UnityEngine.Resources.Load<Condition>(condition.conditionName);
          if(c!=null)
          {
               conditionList[c]=condition.fulfilled;
          }
      }
}

Now, Portal will have a Condition attached… (it’s a ScriptableObject).
Once you’ve gotten the player:
if(!player.GetComponent<ConditionManager>().ConditionSatisfied(condition) return;

As I said, it’s a bit more work, but it is doable.

1 Like

Hello, i have read this so many times, but i think i understand now. First AIController gets a CharacterClass, so we can check class and isAlive on Player.EnemiesAlldead. Then you make Condition: ScriptableObject that has two public vars, which the ConditionManager uses to make and fill a dictionary with Awake and TetsCondition of each list of enemies(Condition[ ]) and thier bool value (alldead?). ConditionSatisfied Does the actuall test for all dead. then i think the end really messes me up. CaptureState and RestoreState uses conditionStructs to make a list and transfer across scenes. SO then Ontrigger if(!player.GetComponent().ConditionSatisfied(condition) return; for the ACTUAL check. i promise im trying to grasp but is this right? Thank you again for your time and also for writing all this code i hope i can do it justice.

You’re fairly close.
I actually wrote the code in the post, so there may be some bugs to iron out.

The conditionsToManage is the list of actual conditions that need to be checked. In my original design, these are to be dragged onto the ConditionManager that is put on our player prefab.
The Dictionary is a fast lookup…

In the EnemiesAllDead, all AIControllers in the scene are gathered. Each one is checked to see what the actual class of the character is, and if it matches the class we’re interested in counting. If any of them are alive, then the condition has not be satisfied, and therefore it returns false.

TestConditions() cycles through all of the conditions and tests them, storing the results in the Dictionary. You could, theoretically, only test for the one condition you’re looking for, but this means in cross scene conditions, that a condition may never have been checked for because it never came up. This basically tests every condition in the scene.

The CaptureState and RestoreState are necessary to save the Dictionary between scenes. Dictionaries don’t serialize in CaptureState/RestoreState, so we convert it into a List of conditions and bools. We would need to do some conversion anyways since the SO’s can’t be saved directly.

Hahaha i was off. thanks for clarifing, but to be honest i still can’t get it in my head ,just on paper. and we are learning about ISaveables now so shold be informative. i think im going to switch my design back to linear levels so i can follow along with corse easier. then implement my dope tele system when i have more of a game to tele to and from. thank you again for the help

Yield allows for accessing an object then throwing it away to collect. It loads things one at a time. Coroutines hold multiple instructions one at a time without storing them in the RAM. A directory of coroutines allows for actions to be taken outside of a frame.

Especially enjoyed this lecture - the analogies help a lot!

Some thoughts of possible uses of coroutines:

  1. Could add conditions/stat effects that tick.
  2. Could create more precise timing on conditions.
  3. Increasing the waves of spawns on conditions.

Would a use for them be for checking enemy aggro? Instead of running a check to see if there’s anything with the player tag in x distance from an enemy, in update. You could use a coroutine to limit the amount that checks being called, so reducing the cost of it?

I could see that as a possible use, yes. Bear in mind, you might lose a little responsiveness, depending on how long you set your yield return new WaitForSeconds(interval);

They can be used to create a fixed progression in level. So, we tell the Coroutine to wait for some game event (for example, a boss received a certain amount of damage, etc) before doing or changing game variables and elements that will make the level progress

1 Like

That’s a great example, one that probably doesn’t occur to most. Well done.

1 Like

Thanks Brian!

Not sure if this has been covered already but I would think that you could use a co-routine for things such as crafting or building timers along with skill cooldown timers. You could possibly also use them for things such as drowning after being submerged after a certain time or triggering events at specific times.

Could they also be used to keep track of counters such as ammo? For example, an arrow is removed every time you shoot with your bow and the co-routine pauses until the bow use condition is activated again.

Crafting, building, and skill timers are definitely good candidates for coroutines (in fact, if you look something like Invoke(“DeleteMe”, 2); what you’re really doing is setting a coroutine to wait two seconds and then call the method DeleteMe()
Drowning when submerged is a less viable candidate, as the coroutine runs separately, so you’d need to take extra steps to stop the coroutine once you’re not submerged.
In terms of Ammo… where they could be useful is restocking the ammo, say after 5 seconds, you grow another arrow magically.

1 Like

I usually use IEnumertors when I want to string together a series of events over time.

For instance, loading a new level where you would trigger a loading screen animation, wait for the scene to load, and then fade out.

I’ve also used them for stuff like drawing a hand of cards. You would take the top card in the deck, add it to the hand, wait for a little bit, and repeat the process so it doesn’t all happen instantaneously.

1 Like

I think a good use of IEnumerators is to wait for a condition that the player has to achieve to take an action, like detecting a certain energy level was achieved to show the player he can use a special.
I used them also wait for resources to be available in the game, like a cetains character spawned dinamically, to locate them and do something.

Good morning!
Although the concept was extremely well explained, once again I’ll need more practice with it. But an example comes to mind:
Crafting over-time maybe. especially if a particular step requires more time than others, or if player input is necessary to move from one step to another. I may be wrong but It is how I could see it

A lot of my thoughts were to use IEnumerators and coroutines for managing NPC states, quest states and even combat actions. It’ll be useful as it can wait for a trigger to occur and allow for a more seemless encounter.

Going to take a stab at it here, using firearms as my example.

If you use tracer rounds mixed in with regular rounds, then the IEnumerator would keep track of which round is next in line to be fired.
Or maybe some rounds have had special attention paid to them, like a name carved into it, and that bullet is meant for whose name was carved into it. So you have to choose targets based on which bullet is next in line.

This is simple, and I feel like I don’t have a grasp on it. Am I trying to overthink it?

1 Like

I think they come in handy for everything time-related like cooldowns; using a coroutine is more readable and manageble than updating a timer with Time.deltaTime(i think?).Also they are good to avoid the overhead caused by overchecking with Update in some scenarios;if ,for example , we need to check if the player is near an enemy we could do that every tenth of a second using a coroutine instead of checking it every frame with Update.

Edit: everywhere I said IEnumerator, I really should have said IEnumerable!

As a mathematician, I’d see IEnumerators as useful as a way of performing abstract operations on sequences.

So, one could (in principle, I’ve seen it done in Python so it is probably doable with C# IEnumerators) have sequences like lists, or infinite lists even given the lazy evaluation possibilities, and you could have a method that takes two IEnumerators and appends them, returning an IEnumerator representing the concatenation (not that appending a sequence after an infinite sequence would accomplish much!), but more complex things than that. You could have a method that filters: return an IEnumerator of elements that satisfy a predicate (a criterion, perhaps specified by an Action).

Or things like a Cartesian product (an IEnumerator returning ordered pairs (using Tuple) of every possibility of an element from the first and an element from the second).

Or Intersection of sequences: all elements in the first sequence that can be found in the second. To do that, would probably need the second to be a finite sequence and all its elements added to a Set so you can test the elements of the first against it.

Pythons “IterTools” library has other sequence functions: count, cycle (turn a finite sequence into an infinite one by cycling back to the first when you reach the end), repeat (run the sequence multiple times), ZipLongest (first item from sequence A paired with first item from B, then second from A paired with second from B, and so on, using nulls when one sequence runs out to continue through the other sequence), Permutations, Combinations, CombinationsWthReplacement, and more.

(I’ve been in C# so long I forgot to use snake_case and used CamelCase instead).

Privacy & Terms