Is this the best way to use Coroutines/Actions while "stalling" the main process

Hi everyone,

I’ve been working on a Unity implementation of a boardgame for the past few weeks and have a rough prototype ready but am now at the point where I want to make it shine. This is a card-driven game so the player will drop a card down on an active collider and then a variety of things will happen in various locations on two boards/play-mats. My prototype thus far has been able to correctly display what should be on the board at the end of a players turn, but since there can potentially be several things happening as a card is played, I am looking to break things down a bit, so the player can follow each step of gameplay. As a simple example of one of the things I want to do, rather than enabling and displaying 3 pawns at home-plate (this is a baseball game) all at once, I would want the pawns to activate and become visible one at a time with a short delay between them.

I have been fooling around with coroutines to accomplish this and am slowly coming to grips with the way they behave.

My main function for the playing of a card, looks something like…

public void PlayCard(int cardId, GameControlScript.Players team) {

		print("Playing card: " + cardId + " for team: " + team.ToString());

		// Move card to discard pile 
		DiscardsController discardsController = activeDiscards.GetComponent<DiscardsController>(); 
		discardsController.AddCard(cardId);

		if (this.gameState == GameStates.NormalPlay) {

			// Play the immediate action 
			DoImmediateAction(cardId, team); 

			// Get The Characteristics of the card 
			CardDictionaryController cardDictionaryController = cardDictionary.GetComponent<CardDictionaryController>(); 

			// Resolve the hits on the opponents board 
			ResolveHits(team); 

			// Place threatened hits on team board 
			print("Call PlaceThreatenedHits()");
			**StartCoroutine(PlaceThreatenedHits(cardId, team);**
			print("After PlaceThreatenedHits()");

		}


           DO OTHER STUFF 

	}

My first attempt at turning the function PlaceThreatenedHits() into a Coroutine failed pretty miserably because the code in DO OTHER STUFF was being executed after PlaceThreatenedHits() performed its first of 4 yields and altered some members/properties used by PlaceThreatenedHits() when it continued its execution. I thought about passing the entire game context into PlaceThreatenedHits() as a parameter, but that is a fairly heavy solution.

In the end, to maintain the game-state as it was when PlaceThreatenedHits() was initially called, I ended up moving all of the code denoted by DO OTHER STUFF into a new function,

	**public void PlayCardPart2(int cardId, GameControlScript.Players team)** {

		// Reset the controller sets 
		SetGameObjects(team);

		if (this.gameState == GameStates.ExtraInningResolve) {
			
			ResolveExtraInning(); 
			
		}

		// Toggle the active player 
		activeTeam = FlipTeam(activeTeam);
		if (activeTeam == Players.Player1) {
			player2ActiveIndicator.GetComponent<ActivePlayerIndicatorController>().active = false; 
			player1ActiveIndicator.GetComponent<ActivePlayerIndicatorController>().active = true; 
		}
		else {
			player2ActiveIndicator.GetComponent<ActivePlayerIndicatorController>().active = true; 
			player1ActiveIndicator.GetComponent<ActivePlayerIndicatorController>().active = false; 
		}


		// Check for end of game - the hand counts must have been updated before calling PlayCard 
		CheckEndOfGame(); 
	} 

…and I modified the original coroutine call to pass the new function in

			// Place threatened hits on team board 
			print("Call PlaceThreatenedHits()");
			**StartCoroutine(PlaceThreatenedHits(cardId, team, PlayCardPart2));**
			print("After PlaceThreatenedHits()");

This approach DOES work, but I’m wondering if it is the best way. I’m concerned at the amount of chopping up of functions I will have to do as I continue forward using this approach. It’s a bit of a shame that I couldn’t keep all that logic in a single PlayCard() function for readability and maintenance purposes. Things will start getting more difficult to follow the more I start chaining things this way and I fear it may even become unwieldly.

Fairly soon, I will want to write an in-game tutorial that will consist of nothing more than a scripted sequence of short animations happening on the screen, most of which will probably be implemented via coroutines. But it looks like I won’t be able to store the master sequence of events for a tutorial section in a single function or other chunk of code.

Any advice or 3rd party add-ons which might help out would be greatly appreciated.

Just turn all of the old PlayCard into a coroutine! Alternatively, if PlayCard has many call sites that would be inconvenient to change, have PlayCard do nothing but start a coroutine called PlayCardC which has the actual code in it.

If you want to keep PlaceThreatenedHits in its own separate coroutine instead of merging it into PlayCard, then you can leverage the fact that coroutines can yield on other coroutines; just change that line to:

yield return StartCoroutine(PlaceThreatenedHits(cardId, team));