Search Unity

Making a coroutine wait for input from UI button

Discussion in 'Scripting' started by KnightsHouseGames, Nov 25, 2017.

  1. KnightsHouseGames

    KnightsHouseGames

    Joined:
    Jun 25, 2015
    Posts:
    850
    So I'm working on a prototype for a turn based battle system.

    Right now, the main battle clock is a coroutine which runs on a while loop that checks if the allies or enemies are all dead or not, checks if anyone is ready to take a turn, checks if any actions with warmup times are ready to execute, and ends when the battle is resolved one way or the other.

    Currently it can run itself full automated, all the the characters in the battle have only one action (attack), so they automatically select that action, and attack a random target of the opposite alignment(ally or enemy) until the battle resolves.

    All of this runs via debug.logs in the console, I've used this to make sure most of the most basic core mechanics of the game work, and thus far, the system works.

    What I want to do now is make it so the player side is no longer automated, and instead the game loop pauses and waits for input from the player via UI buttons that I want to have appear when one of the player characters is ready to take orders.

    My thought process right now is maybe I make those methods the battle clock coroutine calls have some sort of while loop which waits until the player has made some sort of input each time it needs input? I want the player to select an action, select a target for that action, then for there to be a confirmation screen.

    I think my biggest hangup is wondering how to do the button aspect of it? Would it be a good idea to write a seperate class to take the input from the UI buttons? And if so, how would I activate that when I wanted it to get input from the player?

    I want it to show buttons for each valid target in the targeting step, which is something thats dynamic, since as you kill enemies,, the number of valid targets will change, so I'm thinking maybe I need a way of generating the buttons based on the numbers of valid targets or abilities as I figure out how to add additional abilities to the game.

    So I guess my problem has a few parts. What do I need to do to pause my program to wait for input, then how to I deliver that input with UI buttons?
     
  2. fire7side

    fire7side

    Joined:
    Oct 15, 2012
    Posts:
    1,819
    I'm doing something vaguely similar. I have a bool playerTurn, so when it's false, the other things go ahead and do their thing. When they are finished, they switch playerTurn to true, which puts up some input. When the input is taken care of, playerTurn is false again. I do the switching from update and call coroutines for enemy or player movement. I kind of took the idea from the Rogue tutorial in the learn section.
     
  3. SirIntruder

    SirIntruder

    Joined:
    Aug 16, 2013
    Posts:
    49
    Are you thinkg something along these lines?

    Code (CSharp):
    1.  
    2. public class GameLoop
    3. {
    4.     public Player player;
    5.  
    6.     IEnumerable GameLoopStuff()
    7.     {
    8.         while(true)
    9.         {
    10.             yield return player.WaitForAction();
    11.             Debug.Log(player.action); // do something with player action
    12.  
    13.         }
    14.     }
    15. }
    16.  
    17. public class Player : MonoBehaviour
    18. {
    19.     public string action { get; private set; }     // this should be some "PlayerAction" custom class  with acion type/target/whatever else is needed.
    20.  
    21.     public IEnumerable WaitForAction()
    22.     {
    23.         action = null; // clear last action, we want a new one
    24.         while(action == null) { yield return null; }
    25.     }
    26.  
    27.     void OnButton1Clicked() { action = "Action1"; } // these methods will cause WaitForAction to break
    28.     void OnButton2Clicked() { action = "Action2"; }
    29. }
    30.  
     
    KnightsHouseGames likes this.
  4. KnightsHouseGames

    KnightsHouseGames

    Joined:
    Jun 25, 2015
    Posts:
    850
    OK, this is sorta like what I was thinking. Does it need to be a seperate coroutine for the pausing piece, or can I have that loop run in a regular method, like the targeting method I'm calling from the main battle clock coroutine? Will that still make the game wait for input?

    I think it's connecting the button to this which is really confusing me, it's been a while since I've used the UI elements. Should I be writing a seperate script to handle the button stuff or would that all go in the battle controller with everything else?
     
  5. KnightsHouseGames

    KnightsHouseGames

    Joined:
    Jun 25, 2015
    Posts:
    850
    So I tried this late last night:

    I tried setting up a second coroutine to run in the targeting method if the character looking for a target was an ally, with code in it similar to the code in the example in the previous post.

    Depending on how I set it up, the game either hangs, or the coroutine doesn't have a chance to make the selection because the code in the background continues on and doesn't pause.

    I think the challenge is most of the stuff that happens in the main loop happens in outside methods, because it isn't guarenteed that any characters will take a turn during a single "step" of time in my system. It uses methods to check if any characters are ready to act each segement of time. It seems like once it's outside of the main coroutine, it's much harder to pause it...
     
  6. Fido789

    Fido789

    Joined:
    Feb 26, 2013
    Posts:
    343
    Maybe you could take a look at the event aggregator pattern.
     
  7. KnightsHouseGames

    KnightsHouseGames

    Joined:
    Jun 25, 2015
    Posts:
    850
    Do you have any sources that explain this in the context of Unity?
     
  8. Fido789

    Fido789

    Joined:
    Feb 26, 2013
    Posts:
    343
    There is no need to explain it in the context of Unity, but if you'll google "unity3d event aggregator", there are some nice articles.

    In my game I am using implementation similar to Caliburn Micro's Event Aggregator, because I am used to it from WPF programming, but there are many other implementations.

    There is an event aggregator class, I call mine Messenger. Any class in your project that is interested in publishing or receiving messages, must get a reference to the instance of the Messenger class. Now you will create some messages, for example

    Code (CSharp):
    1. public class PlayerMadeMoveMessage()
    2. {
    3. }
    and everyone interested in receiving such a message will implement

    Code (CSharp):
    1. public class GameEngine : IHandle<PlayerMadeMoveMessage>
    2. {
    3.     public void Handle(PlayerMadeMoveMessage message)
    4.     {
    5.           // Do something interesting
    6.     }
    7. }
    And now if player will move you will just publish a message like this:

    Code (CSharp):
    1. messenger.Publish(new PlayerMadeMoveMessage());
    and everyone who implements IHandle<PlayerMadeMoveMessage> and subscribed to the messanger will get its Handle(PlayerMadeMoveMessage message) method called.

    The great thing is that your code is greatly decoupled, every class knows just the messenger and nothing else.
     
  9. KnightsHouseGames

    KnightsHouseGames

    Joined:
    Jun 25, 2015
    Posts:
    850
    While this seems really cool and useful, I'm not sure how it solves my problem...

    All I really want to do is make the program pause and wait for input from the player from the UI. Even if I completely redid my entire system using this, I'm still not sure how I would pause it for input
     
  10. fire7side

    fire7side

    Joined:
    Oct 15, 2012
    Posts:
    1,819
    Just activate the buttons and have it pause and wait for a bool. Have the button change the bool when the input is finished.
     
  11. Fido789

    Fido789

    Joined:
    Feb 26, 2013
    Posts:
    343
    Well I just dont't feel that the game loop is the right pattern for the turn based game logic, but maybe I am wrong.

    Anyways, if you just need to pause your game loop, you can probably do it like this:
    Code (CSharp):
    1. public IEnumerator MyGameLoop()
    2. {
    3.     // ...
    4.     // Waiting for playerMadeMove == true
    5.     while (playerMadeMove != true)
    6.         yield return null;
    7.     // ...
    8. }
    and from your button click method set
    Code (CSharp):
    1. playerMadeMove = true;
     
  12. andymads

    andymads

    Joined:
    Jun 16, 2011
    Posts:
    1,614
    How does the Handle method get called? How does the messenger know about it?
     
  13. Fido789

    Fido789

    Joined:
    Feb 26, 2013
    Posts:
    343
    Somewhere in your code, usually in the class interested in handling messages (constructor, Awake, etc) you just call
    Code (CSharp):
    1. messenger.Subscribe(this);
     
    andymads likes this.
  14. KnightsHouseGames

    KnightsHouseGames

    Joined:
    Jun 25, 2015
    Posts:
    850
    So I ended up restructuring my code a little, and using the method @SirIntruder detailed. I made it so that the automated system still applied to enemies, then made a coroutine that would be called for player input after the turn checks in the main battle clock coroutine that only activates if a turn is active. So now it will wait for the player to select a target for an attack before continuing.

    So now I'm thinking about writing a seperate class for handling things on screen, like making the buttons appear, disappear, and become inactive when a target is dead. I'd also like to transition the text I'm outputting from the console to the screen, and find a way to show it showly enough that a player can process it, because right now it just flies by so fast that it's impossible to read.