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);