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. Dismiss Notice

Question foreach loop running each turn at the same time

Discussion in 'Scripting' started by TheOnlyRail_, Aug 15, 2023.

  1. TheOnlyRail_

    TheOnlyRail_

    Joined:
    Jun 18, 2023
    Posts:
    4
    Hi there,

    I'm working on a turn based rpg project and I'm having a bit of trouble writing a turn handler that functions properly.
    It can sort the units by agility stay like it's supposed to but the turns seem to be happening at the same time regardless of what I change in my code. I've been having trouble with this the past week or so and can't seem to find anything that will help. I am new to coding so I likely just don't know how to word the question properly.
    At the moment the turn handler has a boolean to determine if a turn has ended but it doesn't wait for it and an int to check to see if they have any turns left to take (this is only useful separately as I plan on having a way to increase how many turns you can take each round that is separate from the agility stat. It also check which script to run based on another bool to determine if the unit is a player or not, to help determine the battlestate for my state machine.

    Here's the turn handler code:
    Code (CSharp):
    1.  IEnumerator TurnHandler()
    2.     {
    3.         GameObject[] Party = GameObject.FindGameObjectsWithTag("Player");
    4.         foreach (GameObject Player in Party)
    5.         {
    6.             turnOrder.Add(Player);
    7.         }
    8.         GameObject[] Enemies = GameObject.FindGameObjectsWithTag("Enemy");
    9.         foreach (GameObject Enemy in Enemies)
    10.         {
    11.             turnOrder.Add(Enemy);
    12.         }
    13.         if (turnOrder.Count > 0)
    14.         {
    15.             turnOrder.Sort(delegate (GameObject a, GameObject b)
    16.             {
    17.                 return (b.GetComponent<Unit>().agility).CompareTo(a.GetComponent<Unit>().agility);
    18.             });
    19.         }
    20.  
    21.         foreach (GameObject combatant in turnOrder)
    22.         {
    23.             bool turnEnded = combatant.GetComponent<Unit>().turnEnd;
    24.             int turns = combatant.GetComponent<Unit>().turns;
    25.             if (turns == 0)
    26.             {
    27.                 continue;
    28.             }
    29.             if (turnEnded == true)
    30.             {
    31.                 if (turns == 0)
    32.                 {
    33.                     continue;
    34.                 }
    35.                 if (turns > 0)
    36.                 {
    37.                     yield return StartCoroutine(combatant.GetComponent<Unit>().Turn());
    38.                     combatant.GetComponent<Unit>().turns = combatant.GetComponent<Unit>().turns - 1;
    39.                 }
    40.             }
    41.             if (turnEnded == false)
    42.             {
    43.                 if (turns == 0)
    44.                 {
    45.                     continue;
    46.                 }
    47.                 if (turns > 0)
    48.                 {
    49.                     yield return StartCoroutine(combatant.GetComponent<Unit>().Turn());
    50.                     yield return new WaitUntil(() => combatant.GetComponent<Unit>().turnEnd == true);
    51.                     combatant.GetComponent<Unit>().turns = combatant.GetComponent<Unit>().turns - 1;
    52.                 }
    53.             }
    54.             foreach (GameObject combatant1 in turnOrder)
    55.             {
    56.                 combatant1.GetComponent<Unit>().turnEnd = false;
    57.             }
    58.         }
    59.        
    60.     }
    The OnPlayerTurn code:

    Code (CSharp):
    1. public class OnPlayerTurn : MonoBehaviour
    2. {
    3.     private BattleSystem BS;
    4.  
    5.     public IEnumerator TurnStart()
    6.     {
    7.         BS = GameObject.Find("BattleSystem").GetComponent<BattleSystem>();
    8.         BS.state = BattleState.PLAYERTURN;
    9.         GetComponent<Unit>().turnEnd = true;
    10.         GetComponent<Unit>().turns = GetComponent<Unit>().turns + 1;
    11.         StartCoroutine(BS.GetComponent<BattleSystem>().PlayerTurn());
    12.        
    13.         yield break;
    14.     }
    15. }
    16.  
    and the OnEnemyTurn code:

    Code (CSharp):
    1. public class OnEnemyTurn : MonoBehaviour
    2. {
    3.     private BattleSystem BS;
    4.  
    5.     public IEnumerator TurnStart()
    6.     {
    7.         BS = GameObject.Find("BattleSystem").GetComponent<BattleSystem>();
    8.         BS.state = BattleState.ENEMYTURN;
    9.         yield return new WaitForSeconds(1f);
    10.         GetComponent<Unit>().turnEnd = true;
    11.         GetComponent<Unit>().turns = GetComponent<Unit>().turns + 1;
    12.         StartCoroutine(BS.GetComponent<BattleSystem>().EnemyTurn());
    13.         yield break;
    14.     }
    15. }
    16.  
    If any more information is needed please let me know.
    Thanks for your help.
     
  2. wideeyenow_unity

    wideeyenow_unity

    Joined:
    Oct 7, 2020
    Posts:
    728
    I too am working on a similar turn-based project at the moment. I haven't fully thought out my turn system just yet, but I plan on:
    Code (CSharp):
    1. int currentPlayer; // team index
    2. int globalTurn; // count for all, increased with new round
    3. int currentTurn; // global check
    4. int Player.movesLeft; // allowance for current turn
    5.  
    6. if (globalTurn == currentTurn)
    7. {
    8.    Player player = allPlayerList[currentPlayer];
    9.    if (player.movesLeft > 0)
    10.    { HandlePlayersTurn(); }
    11.    else { currentPlayer++; }
    12. }
    13. // handle globalTurn and currentTurn appropriately
    14. if (currentPlayer > allPlayerList.Count)
    15. { currentTurn++; currentPlayer = 0; }
    16.  
    Take that with a grain of salt though, it's basically pseudo code(thought written out). But that's kinda what I plan on doing. :)
     
    TheOnlyRail_ likes this.
  3. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    Coroutines may not be an appropriate solution here.

    A simple ordered collection of players along with an index to move through them is far simpler and gives you a much better surface to operate with your game's problem space.
     
    TheOnlyRail_ and Yoreki like this.
  4. kdgalla

    kdgalla

    Joined:
    Mar 15, 2013
    Posts:
    4,326
    I wouldn't use a loop at all. You really don't even need an Update because this script won't be doing anything most of time. Just store an index to the current combatant in a variable. Then you really just need one or two methods, maybe. One to tell the current combatant to start their turn and another method to increment who the current combatant is (obvious you'd want it to loop back around to the beginning after the index > combatants if the battle is still going).

    Let a combatant take it's turn and when the combatant is finished the combatant can call a method in your script to increment the current combatant. You could use a UnityEvent to trigger it, for example.

    Edit: Update and any method you write will begin and end within a single frame, usually. It's difficult at first, but you should get accustomed to writing code that breaks up an action over multiple frames but still only handles just what needs to be done in a single frame when it's called.
     
    Last edited: Aug 15, 2023
    TheOnlyRail_ likes this.
  5. TheOnlyRail_

    TheOnlyRail_

    Joined:
    Jun 18, 2023
    Posts:
    4
    Alright thanks for the help guys I’ll give it a try when I get home from work tonight and see what happens, cheers.
     
  6. TheOnlyRail_

    TheOnlyRail_

    Joined:
    Jun 18, 2023
    Posts:
    4
    Took me a minute but I've almost got it working now. For some reason I need to call the TurnLoop Method twice before the next turn can start but it works, the only problem I seem to be having now is when the currentCombatant index goes over the range of turnOrder it's suppose to trigger an If statement and then reset itself back to 0, for the first item of the list, but it just kinda doesn't? I'm a bit confused to be honest. As far as I can tell this is the last thing stopping it from working so after this it should be golden. The turnHandler routine now only gets called once a round to resort/ resize the list as needed(in case of stat changes, enemy appearances and the like).


    Code (CSharp):
    1. public int currentCombatant = 0;
    2. public int round = 0;
    3. public int currentTurn = 0;
    4. .
    5. .
    6. .
    7.  
    8. public void TurnLoop()
    9.     {
    10.         int numberOfCombatants = turnOrder.Count;
    11.         int lastRound = round - 1;
    12.         GameObject combatant = turnOrder[currentCombatant];
    13.  
    14.         if (lastRound < 0)
    15.         {
    16.             lastRound = 0;
    17.         }
    18.  
    19.         if (round == currentTurn)
    20.         {
    21.             if (currentCombatant > numberOfCombatants)
    22.             {
    23.                 round++;
    24.                 currentTurn++;
    25.                 currentCombatant = 0;
    26.                 TurnHandler();
    27.             }
    28.          
    29.             if (combatant.GetComponent<Unit>().turns > 0)
    30.             {
    31.                 StartCoroutine(combatant.GetComponent<Unit>().Turn());
    32.                 combatant.GetComponent<Unit>().turns--;
    33.                 currentCombatant = currentCombatant++;
    34.             }
    35.             else
    36.             {
    37.                 currentCombatant++;
    38.             }
    39.         }
    40.         if (currentCombatant > numberOfCombatants)
    41.         {
    42.             round++;
    43.             currentTurn++;
    44.             currentCombatant = 0;
    45.             TurnHandler();
    46.         }
    47.      
    48.     }
    49. .
    50. .
    51. .
    52.  
    53. if (isDead)
    54.         {
    55.             state = BattleState.LOST;
    56.             EndBattle();
    57.         }
    58.         else
    59.         {
    60.             Debug.Log("The enemy has attacked");
    61.             TurnLoop();
    62.             TurnLoop();
    63.         }
    Thanks for the already fantastic help you guys have given me.
     
  7. wideeyenow_unity

    wideeyenow_unity

    Joined:
    Oct 7, 2020
    Posts:
    728
    lastRound, if how I think you're using it, should just change when round does, so there should be no reason to modify it to say "- 1", as it technically should just be one less until changed. But you might have a different way you're using it, so nevermind.

    In my example, the List wasn't gameObjects, it was the classes. like:
    public List<Unit> allPlayers = new List<Unit>();

    That way all you have to do is call the List and reference the index, making that method look more like:
    Code (CSharp):
    1. // currentCombatant == _player;
    2.     if (allPlayers[_player].turns > 0)
    3.     {
    4.         // I wouldn't recommend using coroutines here
    5.         StartCoroutine(allPlayers[_player].Turn());
    6.         allPlayers[_player].turns--;
    7.         // currentCombatant = currentCombatant++; ???
    8.     }
    9.     else
    10.     {
    11.         _player++;
    12.     }
    But I've seen coroutines run multiple times, and cause bugs that are hard to find. So if they're not double checked to only run once, they will compound on themselves each frame. Which may be part of your other problem of it not working until 2 clicks.

    But keep playing around with it, you're definitely making progress. :)
     
    TheOnlyRail_ likes this.
  8. TheOnlyRail_

    TheOnlyRail_

    Joined:
    Jun 18, 2023
    Posts:
    4
    I got it working!
    Thanks for your help, here's how it looks now:

    Code (CSharp):
    1. public void TurnLoop()
    2.     {
    3.  
    4.         int numberOfCombatants = turnOrder.Count - 1;
    5.         int lastRound = -1;
    6.  
    7.         if (currentCombatant > numberOfCombatants)
    8.         {
    9.             lastRound++;
    10.             round++;
    11.             currentTurn++;
    12.             currentCombatant = 0;
    13.         }
    14.  
    15.         GameObject combatant = turnOrder[currentCombatant];
    16.         if (round == currentTurn)
    17.         {
    18.          
    19.             if (combatant.GetComponent<Unit>().turns > 0)
    20.             {
    21.                 combatant.GetComponent<Unit>().Turn();
    22.                 combatant.GetComponent<Unit>().turns--;
    23.                 currentCombatant++;
    24.             }
    25.             else
    26.             {
    27.                 combatant.GetComponent<Unit>().turns++;
    28.                 currentCombatant++;
    29.                 TurnLoop();
    30.             }
    31.         }
    32.  
    33.     }
    the only problem is for some reason the currentTurn and Round go up by 2 each loop but for UI text if I decide to display the number of round I'll make the code something like:
    Code (CSharp):
    1. If (Round < 2)
    2. {
    3. UIText.Text = "Round 1"
    4. }
    5. else
    6. {
    7. UIText.Text = "Round" + Round/NumberOfCombatants
    8. }
    Thanks for all your help guys.

    Edit:
    turnOrder.Count was displaying 2 so to make the index stay within range I had to -1.
     
    Kurt-Dekker and kdgalla like this.