CardConquest GameDev Blog #9: Creating “Phases” in the Game Starting with Unit Placement

Disclaimer: I am neither a professional game developer nor am I a professional programmer. I doubt I am following best practices. This is just what worked for me at the time. Hopefully it works for you if you’re playing along while reading!

Version of Unity used: 2019.4.13f1 Personal

The previous posts in this series can be found under the CardConquest category on this blog. The github for the project can be found here. The zip archive for this specific post can be found here.

The goal in this post is to add different “phases” to the game that players move through. Specifically, I want the user to start in an “Unit Placement” phase, and then move to an “Unit Movement” phase. This will be done through a “GameplayManager” object in Unity, and communicated to the user through the UI.

The end result of all this should look something like this:

  • The user starts in the “Unit Movement” phase
    • The map is “zoomed out” a bit
    • The player’s units are off the map
    • the player places each unit onto the map
  • After units are placed, the player moves to the “Unit Placement” phase
    • This will basically be what has already been created: The player can move the units around the map

Later, other phases will be added, such as combat and card selection phases, but the game isn’t even close to those in terms of development so there’s no point in worrying about them!

Preparing the Scene

I want the player to have 10 total units to place on the map: 4 tanks and 6 infantry. So, the first step is to copy and paste the current units to get those numbers. You can copy/paste objects in Unity from the hierarchy. You don’t need to worry about where the units are placed in the scene, they just need to exist. The GameplayManager script that will be created later will move them to where they need to be.

Note that all the units are child objects of the “PlayerUnitHolder” object. PlayerUnitHolder is important as it will be what is referenced when other scripts look for a player’s units. Unity allows you to search for objects by the name of the object, but for this, I’m going to have Unity look for PlayerUnitHolder based on a tag I assign it. By search for the object based on tag instead of title, it could make things more flexible if you end up need to make changes to multiple similar objects that might have different names, but the same tag. Right now the game detects if a unit is a Tank or an Infantry unit based on the object’s tag, so this isn’t entirely new.

To create the tag for PlayerUnitHolder, first select it in the hierarchy. Then, in the Inspector window, click on the drop down next to “Tag” and click “Add Tag…”

Click the “+” sign to add a new tag

Name the tag “PlayerUnitHolder” and click save

Great! Now your scripts can find the PlayerUnitHolder object by using GameObject.FindGameObjectWithTag and the tag “PlayerUnitHolder.”

Creating GameplayManager

To allow the user to advance between phases, and to setup the phases in terms of UI and everything, a new object will be created in Unity called GameplayManager. GameplayManager will be an empty object with a script attached to it, similar to the MouseClickManager and EscMenuManager objects that have already been created.

To create GameplayManager, right click in the hierarchy and select “Create Empty”

Rename the new object to GameplayManager. Then, go to the Scripts directory of the project and create a new C# script baned “GameplayManager”

Attach the GameplayManager script tot he GameplayManager object by dragging and dropping the script on the object.

After the script is attached, double click on the script to open it in Visual Studio.

Initial Script Setup

The GameplayManager script will need a few things added to it. First, at the top, add using UnityEngine.UI; beneath using Unity.Engine;. This will be needed later when the script needs to modify UI elements in the game.

Next, add two variables. One “GameplayManager” variable named instance, and a string named currentGamePhase.

public static GameplayManager instance;
public string currentGamePhase;

currentGamePhase just hold the string of the game’s current phase and is public so other scripts/objects in the game can access it. “Instance” will be how other scripts and objects reference the GameplayManager script/object. This will essentially be the same as the “instance” variables used in EscMenuManager and MouseClickManager. Other objects in the game will be able to reference GameplayManager by using GameplayManager.instance.

So, create a new function called MakeInstance

void MakeInstance()
{
	if (instance == null)
		instance = this;
}

Then, change the Start() function to Awake(). In the Awake() function, make a call for MakeInstance().

void Awake()
{
	MakeInstance();
}

Now, after the game has started, other scripts/objects can reference the GameplayManager script through “instance.”

The final thing to do in GameplayManager.cs, for now, is to declare two lists. These lists will be used to store the infantry and tank units the user has to place. Declare the lists in the same section you declared instance and currentGamePhase.

public static GameplayManager instance;
public string currentGamePhase;

private List<GameObject> infToPlace;
private List<GameObject> tanksToPlace;

Then, in the Awake() function, initialize the lists as new, empty lists.

void Awake()
{
	MakeInstance();
	infToPlace = new List<GameObject>();
	tanksToPlace = new List<GameObject>();
}

Creating Game Phase UI

The first game phase, the “Unit Placement” phase, will have all the player’s units off the map in a little “box” that they then select from to place on the map. So, a UI will need to be created to do this, something to display the “box” that holds the units.

First, back in Unity, create a new UI Canvas object. Right click in the hierarchy, go to UI, then select Canvas

Rename the new Canvas to “GameplayUI.” The canvas will need to be configured similarly to previous UI canvas’s

  • Under the Canvas section
    • Set “Render Mode” to “Screen Space – Camera”
    • Add the Main Camera in the scene as the “Render Camera”
    • Set the “Sorting Layer” to “UI-Text”
    • Set “Order in Layer” to 1
  • Under the Canvas Scaler section
    • Set “UI Scale Mode” to “Scale with Screen Size”
    • Set the “Reference Resolution” to 1280×720
    • Set the “Match” value to 0.5

The next, and easiest, UI element to create will be some text that just says what the current phase of the game is. Right click on the GameplayUI canvas, go to UI, then select Text.

Rename the Text to “GamePhase.” For now, that’s all you need to do. Remember that it exists, though!

After the GamePhase Text has been created, you can move onto creating the rest of the UI elements for the “Unit Movement” phase.Right click on GameplayUI in the hierarchy and create a new panel object as a child

Rename the panel to UnitPlacementUI. Edit the “Color” of the panel so it’s alpha value is 0, making it transparent.

Under this UnitPlacementUI panel, the following additional UI elements will be created as children of UnitPlacementUI

  • A text object named “AvailableUnits”
  • A panel object named “UnitBox”
    • Make sure to uncheck the “Fill Center” box
  • A button object named “endUnitPlacementButton”

How to Orient the UI Elements

All the UI elements have now been created, but they are haphazardly placed within the scene. Now they will need to be put into the correct positions so it makes sense to the player and actually communicates something to them.

Adjusting the Camera

When the “Unit Placement” phase begins, the map will be a little “zoomed out” for the player. This is done by changing the “size” of the Main Camera in the scene. Later, the GameplayManager.cs script will adjust the camera’s size automatically, but while the UI elements are placed, it will be easier to see how everything will fit if the camera is adjusted now.

Select the Main Camera in the hierarchy, then in the Inspector Window, under “Camera,” set the “Size” to 8.

I also set the “Background” color of the Camera to a neutral grey. I found this made it easier to see my units and the text. To set the camera color to grey, set all the RGB values to 128.

Positioning the GamePhase Text

The one UI element that isn’t a child of the UnitPlacementUI panel is GamePhase text. This was not made a child because the GamePhase text will be displayed throughout multiple phases of the game, not just during Unit Placement.

Select GamePhase in the hierarchy, and then in the Inspector window adjust the Text settings.

First, set the Rect Transform’s anchor to Top Center.

Next, set the Pos X to 0, Pos Y to -25, Width to 350, and Height to 50

Under the “Text” section, make the following changes:

  • Set the text to “Game Phase” for now. The GameplayManager.cs script will change this text to the appropriate phase later
  • Set the Font to “ThaleahFat”
  • Set the “Font Size” to 50
  • Set the Alignment to Centered
  • Change the color to white (255 for all RGB values)

You should now see the GamePhase text in the top, center of your game view like this:

“AvailableUnits” Text

The AvailableUnits text object will simply be a label above where the player’s units will be.

Select AvailableUnits in the hiearchy, then in the Inspector window, change the Rect Tranform’s anchor to Top Left.

Then, set the position of AvailableUnits to Pos X 175, Pos Y -25, Width 300, and Height 50

Then, under the “Text Section,” set the following:

  • Text to “Available Units”
  • Font to ThaleahFat
  • Font Size to 45
  • Color to white
The “UnitBox” Panel

The UnitBox panel object will be used to create a box in the UI that later the player’s units will be placed in. In terms of the UI, this will basically be an empty panel. The only thing the panel is actually doing is drawing its own outline in the game.

Select the UnitBox in the hierarchy. In the Inspector window, modify the panel’s position to it is: Left: 25, Top: 35, Right: 950, Bottom: 575

That’s it, really, for UnitBox. You should now see it like this in your game view

endUnitPlacementButton

The endUnitPlacementButton will be how the player “ends” the Unit Placement phase and advances to the Unit Movement phase.

Select endUnitPlacementButton in the hierarchy.

In the Inspector window, set the Rect Transform’s anchor to Bottom Left

Set the position values to: Pos X: 175, Pos Y: 25, Width: 300, Height: 50

Under the “Image” section, make sure that the Fill Center checkbox is unchecked

Next, select the “Text” child object of endUnitPlacementButton in the hierarchy. In the Inspector window, under “text,” set the following:

  • Set the text to “done placing units”
  • Font to ThaleahFat
  • Font Size to 35
  • Alignment to Centered
  • Color to White

In the Game view, the UI should now look like this:

And, that’s it for the UI! Everything should be in place to start making the “gameplay” of the Unit Placement phase.

Back to GameplayManager.cs

Now some code will need to be added to GameplayManager.cs to do the following things:

  • Set the GamePhase text
  • Activate the “UnitPlacementUI” so the user can see it
  • Move all of the units under “PlayerUnitHolder” within the box created by UnitBox
  • Keep track of what units the player has placed on the map
  • Allow the user to move to the next phase, Unit Movement
  • Remove the UnitPlacementUI when the phase changes

Set the GamePhase Text

This should be fairly straightforward to code. GameplayManager.cs will need access to the GamePhase text object, and it will then need to update the text.

First, add a new variable as a SerializedField to the global variables.

[SerializeField]
private Text GamePhaseText;

Next, in the Awake() function, make sure to set the string currentGamePhase to “Unit Placement.” This will be used to set the GamePhase text.

currentGamePhase = "Unit Placement";

A new function called SetGamePhaseText will be used to set GamePhase’s text field to the currentGamePhase value. The, very simple, code is shown below.

void SetGamePhaseText()
{
	GamePhaseText.text = currentGamePhase;
}

SetGamePhaseText then needs to be called in the Awake() function to make sure that when the Gameplay scene starts, the GamePhase is set to the first phase, “Unit Placement.”

void Awake()
{
	MakeInstance();
	infToPlace = new List<GameObject>();
	tanksToPlace = new List<GameObject>();

	currentGamePhase = "Unit Placement";
	SetGamePhaseText();
}

Save the GameplayManager.cs script in Visual Studio, then go back into Unity. Select the GameplayManager object in the hierarchy and make sure to add the GamePhase text object to the script’s “Game Phase Text” variable.

Now, when you start the game, the GamePhase UI text should update to “Unit Placement.”

Activating the UnitPlacementUI

When the game starts, the UnitPlacementUI may not necessarily be active in the scene, depending on what the future may hold! In fact, right now, make it so UnitPlacementUI is not active in the hierarchy. Select UnitPlacementUI, then in the Inspector, uncheck the box next to its name.

You should now see the everything in UnitPlacementUI disappear from the scene.

Back in the GameplayManager.cs script, a new function called ActivateUnitPlacementUI() will be created to activate the UI. First, a SerializedField variable will need to be added so the script can access UnitPlacementUI.

[SerializeField]
private GameObject UnitPlacementUI;

Save GameplayManager.cs. Then, back in Unity, make sure that the UnitPlacementUI object is attached to the GameplayManager object under the “Unit Placement UI” variable.

Back in GameplayManager.cs, the ActivateUnitPlacementUI() function can now be created.

void ActivateUnitPlacementUI()
{
	Camera.main.orthographicSize = 8f;
	Camera.main.backgroundColor = Color.gray;
	if (!UnitPlacementUI.activeInHierarchy && currentGamePhase == "Unit Placement")
		UnitPlacementUI.SetActive(true);
}

ActivateUnitPlacementUI does the following:

  • Makes sure the camera size is set to 8, so it matches what the UI was meant to do
  • sets the camera background color to gray (with the convenient Color.gray!)
  • If the UnitPlacementUI is not active in the hierarchy, AND currentGamePhase is “UnitPlacement,” activate the UnitPlacementUI

Make sure that ActivateUnitPlacementUI() is called in the Awake() function, save GameplayManager.cs, and then when you start the game in Unity, you should see the UI activate in the scene.

Move All units into UnitBox

At the beginning of the phase, GameplayManager.cs will find all of the units under the PlayerUnitHolder object, and then reposition the units so they are within the borders of the UnitBox UI object.

A new function called PutUnitsInUnitBox will be created for this. The first thing PutUnitsInUnitBox will do is find the PlayerUnitHolder object based on its tag.

GameObject unitHolder = GameObject.FindGameObjectWithTag("PlayerUnitHolder");

Next, it will then use a foreach loop to put each tank and infantry unit into the respective infToPlace and tanksToPlace lists to keep track of what units have to be placed on the map.

foreach (Transform unitChild in unitHolder.transform)
{
	if (unitChild.transform.tag == "infantry")
	{
		infToPlace.Add(unitChild.gameObject);
	}
	else if (unitChild.transform.tag == "tank")
	{
		tanksToPlace.Add(unitChild.gameObject);
	}

}

After that, PutUnitsInUnitBox will go through each tank and infantry object and reposition them within the UnitBox border by changing their transform.position value. The values I used for the new positions were found by manually moving the units in unity and seeing what their position values were.

//Begin moving the units into the unit box
for (int i = 0; i < tanksToPlace.Count; i++)
{
	if (i == 0)
	{
		Vector3 temp = new Vector3(-14.0f, 8.25f, 0f);
		tanksToPlace[i].transform.position = temp;
	}
	else
	{
		int previousTank = i - 1;
		Vector3 temp = tanksToPlace[previousTank].transform.position;
		temp.x += 1.0f;
		tanksToPlace[i].transform.position = temp;
	}
}
for (int i = 0; i < infToPlace.Count; i++)
{
	if (i == 0)
	{
		Vector3 temp = new Vector3(-14.25f, 7.25f, 0f);
		infToPlace[i].transform.position = temp;
	}
	else
	{
		int previousInf = i - 1;
		Vector3 temp = infToPlace[previousInf].transform.position;
		temp.x += 0.8f;
		infToPlace[i].transform.position = temp;
	}
}
//end moving units into unit box

Call PutUnitsInUnitBox() in the Awake() function, save GameplayManager.cs, and then start the game in Unity. You should see that the units have been moved inside the UnitBox.

Some New Issues to Fix…

If you try and move the units out of the UnitBox now, you will notice that the game crashes. This is because the checks used for unit movement will fail since the units don’t have a currentLandOccupied value and so on. Some changes will need to be made to UnitScript.cs.

First, in the Start() function, comment out the call to GetStartingLandLocation by using two slashes, “//”

void Start()
{
	outline = Instantiate(outline, transform.position, Quaternion.identity);
	outline.transform.SetParent(gameObject.transform);
	ClickedOn();
	//GetStartingLandLocation();

}

Then, go to the CanAllSelectedUnitsMove function in UnitScript.cs. A new check will need to be added that will allow the player to move units during the “Unit Placement” phase while ignoring tile distance. Basically, it will allow the player to move units to any tile, so long as there are not more than five units there, if the current phase is “Unit Placement.”

if (GameplayManager.instance.currentGamePhase == "Unit Placement")
{
	canMove = true;
	return canMove;
}

Save the UnitScript.cs script, and you should now be able to move the units out of the UnitBox!

Track what Units Have been Placed

The purpose of “tracking” what units have been placed and what units have not is really for one thing:

  • Player MUST place all units during Unit Placement phase
  • After all units have been placed, the player can advance to the “Unit Movement” phase
    • When all units are placed, the “Done Placing Units” button will appear
    • click the button will advance the phase

How to Keep Track of Placed Units

In terms of how to track what units have been placed on the map and which ones have not, that will be done with a new boolean variable in UnitScript.cs called “placedDuringUnitPlacement.” This will start as false, and after a unit has been placed, will be set to true.

Add placedDuringUnitPlacement at the beginning of UnitScript.cs with the other global variables:

public bool placedDuringUnitPlacement = false;

The code that will change placedDuringUnitPlacement to true will be in the CanAllSelectedUnitsMove function. If the check for GameplayManager.instance.currentGamePhase == "Unit Placement" returns true, then each unit that is selected will have its placedDuringUnitPlacement value set to true. This can be done with a foreach loop.

if (GameplayManager.instance.currentGamePhase == "Unit Placement")
{
	foreach (GameObject unit in MouseClickManager.instance.unitsSelected)
	{
		UnitScript unitScript = unit.GetComponent<UnitScript>();
		unitScript.placedDuringUnitPlacement = true;
	}
	canMove = true;
	return canMove;
}

Now, when a unit is placed on the map, the placedDuringUnitPlacement value will be set to true

Advance the Phase when All Units Have been Placed

After all units have been placed, the “Done Placing Units” button should appear and allow the user to advance to the next phase. First, the button will need to be attached to the GameplayManager.cs script.

Add a new GameObject variable called endUnitPlacementButton as a SerializedField. There is already one GameObject variable as a SerializedField in the script, UnitPlacementUI. So, you can add a new variable by putting a comma after UnitPlacementUI and then putting endUnitPlacementButton.

[SerializeField]
private GameObject UnitPlacementUI, endUnitPlacementButton;

Save GameplayManager.cs in Visual Studio, go back to Unity, and then add the button to the GameplayManager object’s script’s “End Unit Placement Button” variable.

Activating endUnitPlacementButton

Now that endUnitPlacementButton has been added to GameplayManager.cs, you’ll need some way to detect when all the units have been placed and make endUnitPlacementButton active in the hierarchy so the player can click it.

A new function will be created for this called CheckIfAllUnitsHaveBeenPlaced. It will get the PlayerUnitHolder object, iterate through each unit, and if they all have placedDuringUnitPlacement set to true, the endUnitPlacementButton will be set to active. It’s important that this function is made “public” so that other scripts/objects can call it.

public void CheckIfAllUnitsHaveBeenPlaced()
{
	GameObject unitHolder = GameObject.FindGameObjectWithTag("PlayerUnitHolder");
	bool allPlaced = false;
	foreach (Transform unitChild in unitHolder.transform)
	{
		if (!unitChild.gameObject.GetComponent<UnitScript>().placedDuringUnitPlacement)
		{
			allPlaced = false;
			break;
		}
		else
			allPlaced = true;
	}
	if (allPlaced)
	{
		endUnitPlacementButton.SetActive(true);
	}
}

Before CheckIfAllUnitsHaveBeenPlaced is called from anywhere, it’s important to make sure that the endUnitPlacementButton is not active when Unit Placement starts. This can be done in the previously created ActivateUnitPlacementUI function. Simply add a check in the function that, if endUnitPlacementButton is active in the hierarchy when the phase starts, make it so endUnitPlacementButton is NOT active.

if (endUnitPlacementButton.activeInHierarchy)
	endUnitPlacementButton.SetActive(false);

Now, when you start the game in the Unit Placement phase, you will see that the “Done Placing Units” button is no longer there.

But you will, after all units have been placed, want the button to be activated. This is done with the CheckIfAllUnitsHaveBeenPlaced function in GameplayManager.cs. The function just needs to be called.

One place to call CheckIfAllUnitsHaveBeenPlaced that makes sense is from UnitScript.cs after each unit’s placedDuringUnitPlacement value was set to true. Basically, UnitScript.cs will set placedDuringUnitPlacement to true when the units are placed on the map, and after that will check if there are any remaining units to place by calling GameplayManager.cs’s CheckIfAllUnitsHaveBeenPlaced function. If all units have been placed, GameplayManager.cs’s CheckIfAllUnitsHaveBeenPlaced will activate endUnitPlacementButton.

In UnitScript.cs, in the CanAllSelectedUnitsMove function, simply add the call after placedDuringUnitPlacement is set to true.

foreach (GameObject unit in MouseClickManager.instance.unitsSelected)
{
	UnitScript unitScript = unit.GetComponent<UnitScript>();
	unitScript.placedDuringUnitPlacement = true;
}
GameplayManager.instance.CheckIfAllUnitsHaveBeenPlaced();

Save UnitScript.cs, and when you start the game, you will see the “Done Placing Units” button appear after all units are out of the “Available Units” box and on the map.

Advancing to Unit Movement Phase

Finally, at long last, you can make it so the user can leave the dreaded Unit Placement phase and advance to the Unit Movement phase of the game.

First, a function in GameplayManager.cs will need to be created that advances the phase, or “ends” the Unit Placement phase. This function, called EndUnitPlacementPhase, will do the following:

  • Set currentGamePhase to “Unit Movement
  • Readjust the camera size and “zoom in” for the Unit Movement view
  • Call SetGamePhaseText to update the GamePhase text object
  • Remove the UnitPlacementUI
public void EndUnitPlacementPhase()
{
	if (!EscMenuManager.instance.IsMainMenuOpen)
	{
		currentGamePhase = "Unit Movement";
		Camera.main.orthographicSize = 7;
		SetGamePhaseText();
		UnitPlacementUI.SetActive(false);
	}

}

Now, you just need to add the EndUnitPlacementPhase function to the button in Unity. In the Inspector window, under the “On Click ()” section, click the “+” sign. Then, attach the GameplayManager object. From the drop down, select the EndUnitPlacementPhase function.

Here’s a video of everything working!!!

Simply wonderful. A whole gameplay phase was created! Game play! In a game! Or at least something sort of resembling it…

Next Steps

The next thing I want to do is to actually limit where on the map player’s can place their units. This will be done by creating a unit “base”, and only allowing the units to be placed a certain distance from the base. See you later, nerds!!!