CardConquest GameDev Blog #10: Player Bases and Limiting 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.

Now that the “Unit Placement” phase has been created in the previous post in this series, I want to make it so that there is some limitations on where the player can place their units. To do that, I will mark a specific tile as a player base, and make it so the player can only place units on tiles that are within a specific distance from their base.

I also want to make sure that I have some sort of visual element/cue to the player on what tiles are “bases” and what tiles they can and cannot place a unit on.

Creating the Player Bases

To mark a player base, I want it to be a simple visual based on color. This should help make bases of each player easy to identify for the players. Since the soldier sprites are green, I was going to make one base green, and then have the other base as red. Then I realized that green/red probably isn’t the best combination for any players who may be color blind, so I switched it to green and blue. I probably should have avoided green altogether, since you will see later that I use red for another visual cue, oh well!

The sprites for the bases are a simple hexagon “outline” similar to the yellow selection outlines used when a player selects a unit on a land tile. The difference is the base outlines were one pixel “thicker” than the selection outlines. I made the outlines in paint and then used Gimp to make them transparent. Here is the sprite for the green base:

And the blue base:

Adding the Bases to Unity

To add these sprites to Unity, simply copy/save them to your “Sprites” directory in your Unity project.

Once they are in Unity, you will need to add them to your scene. Drag the green base, which I have named “base-marker-green” into the scene. You should now see a big green hexagon in the scene.

The green base will need to be adjusted a bit to work in the game. First, set the X and Y scale values to 0.5 to make it the same size as the land tiles.

Then, make sure to set the “Order in Layer” to 2 so the base sprite renders above other landtiles.

The green base should be ready. Now, do the same for the blue base, which I named “base-marker-blue.”

After both bases are in the scene, they should look something like this:

Positioning the Bases

On my very small and simple map, I want the player bases to be on opposite end of the map. I want the green to be on the far left, and the blue to be on the far right. That would put them on these two tiles:

To get the bases in those locations, the green base needs to have its position coordinates set to: X:-8.05, Y:1.5

Then for the blue base, it should be positioned at X:5.75, Y:1.5

You should now see the bases in their positions on the map.

Tagging and Prefabs

With the bases setup in the game, they can be saved as prefabs. Before that, however, I want to make sure that the green base has a specific tag on it that will later be used by GameplayManager.cs to locate the player’s base.

To create the tag, select the green base and then select “Add Tag…” from the tag drop down in the Inspector window.

To create the new tag, click the “+” sign and then enter “PlayerBase” and click save.

Back in the Inspector window, go to the tag drop down again and select PlayerBase to apply the tag.

For now, you only need to tag the green base. The blue doesn’t need one yet. Maybe later.

The green and blue bases can now be saved as prefabs. Drag the base objects from the hierarchy into the prefabs directory in the project.

Making the “Can’t Place Here” Markers

Player bases are now visually marked on the map. The next step will be to have a way to make it so tiles that the player can’t place their units on are visually marked as well.

To do this, I took the player base sprite, made it red, and then drew an “X” shape in the middle of the outline. I was lazy and just drew the lines for the X freehand in paint instead of making sure everything lined up properly, so you may notice that they are slightly lopsided or uneven. Oh well. I think they get the point across. Here is the sprite:

Adding to Unity

To add to Unity, drag the “base-marker-cant-place-here” sprite into the game scene.

This will need to be resized and adjusted the same as the player bases were. Set the X and Y scale to 0.5

Then, set the “Order in Layer” to 2.

If you place the marker over a land tile, it should look like this:

Go ahead and save the marker as a prefab.

It can not be deleted from the scene.

Adding “Can’t Place Here” Marker to LandScript.cs

The “Can’t Place Here” marker will need to be instantiated and placed in the game somehow. I did this by attaching it to a variable in LandScript.cs and then creating functions to either create or destroy the marker.

To be able to attach the marker, first add the following to LandScript.cs in the global variables section:

public GameObject cannotPlaceHereOutline;
private GameObject cannotPlaceHereOutlineObject;
public bool cannotPlaceHere = false;

cannotPlaceHereOutline will be used to store the marker’s prefab. cannotPlaceHereOutlineObject will be used when instantiating the new object. The boolean cannotPlaceHere will be used to determine if the land tile should have the marker on it or not. Later, GameplayManager.cs will calculate which land tiles should have their cannotPlaceHere value set to true or false.

After the variables are added, save LandScript.cs and go back into Unity. Open up the prefab for the land tile. In the Inspector, drag the marker’s prefab onto the “Cannot Place Here Outline” variable.

Functions to Create and Remove Marker

Two new functions will be created to create or remove the marker object. They are “CreateCannotPlaceHereOutline” to create the object, and “RemoveCannotPlaceHereOutline” to remove it.

Below is the code for CreateCannotPlaceHereOutline:

public void CreateCannotPlaceHereOutline()
{
	if (cannotPlaceHereOutlineObject == null && cannotPlaceHere)
	{
		cannotPlaceHereOutlineObject = Instantiate(cannotPlaceHereOutline, transform.position, Quaternion.identity);
		cannotPlaceHereOutlineObject.transform.parent = this.gameObject.transform;
	}
}

First, CreateCannotPlaceHereOutline checks to see if “cannotPlaceHere” is set to true and if cannotPlaceHereOutlineObject (the new object you will make) is currently set to null.

Then, if those are both true, cannotPlaceHereOutlineObject is used to instantiate a new object using the prefab stored in cannotPlaceHereOutline. The land tile’s transform.position is used to set the location of the marker object in the same location as the land tile. The cannotPlaceHereOutlineObject object is then set as a child of the land tile to help with organization in the scene hierarchy.

Next, here is the code for RemoveCannotPlaceHereOutline:

public void RemoveCannotPlaceHereOutline()
{
	if (cannotPlaceHereOutlineObject != null)
	{
		Destroy(cannotPlaceHereOutlineObject);
	}
}

RemoveCannotPlaceHereOutline is pretty simple. When called, it first checks if the cannotPlaceHereOutlineObject exists. If it does exist, cannotPlaceHereOutlineObject is destroyed to remove it from the game.

Calculating What Tiles The Player can Place Units On

This has all been a build up to figuring out what units a player can and cannot place their units on during the Unit Placement phase.

I figured that based on the size of my map, the player should be able to place their units that are no more than two tiles away from their base. In a hypothetical two player game, this would result in the middle third column of tiles to be a “no mans zone” that neither player could initially place in. Here is a quick diagram of what I mean. The player would not be able to place any units on tiles with a red squiggle on them.

This will give the player 6 possible tiles to place their units on (the base is included as an allowed tile).

I could just tag these tiles manually, but I wanted to do it programmatically since that should provide more flexibility in case things change later. This will all be done in the GameplayManager.cs script.

First, to access all the land tiles, I will need to make sure that the LandTileHolder object that holds all the land tiles is tagged. I created a new tag called LandHolderand added it to the LandTileHolder object.

In GameplayManager.cs, a new function called LimitUserPlacementByDistanceToBase will be created. The code is shown below.

void LimitUserPlacementByDistanceToBase()
{
	Vector3 playerBaseLocation = GameObject.FindGameObjectWithTag("PlayerBase").transform.position;
	GameObject allLand = GameObject.FindGameObjectWithTag("LandHolder");
	foreach (Transform landObject in allLand.transform)
	{
		LandScript landScript = landObject.gameObject.GetComponent<LandScript>();
		float disFromBase = Vector3.Distance(landObject.transform.position, playerBaseLocation);
		Debug.Log(landScript.gameObject.name + "'s distance from player base: " + disFromBase.ToString());
		if (disFromBase <= 6.0f)
		{
			landScript.cannotPlaceHere = false;
		}
		else
		{
			landScript.cannotPlaceHere = true;
			landScript.CreateCannotPlaceHereOutline();
		}
	}
}

LimitUserPlacementByDistanceToBase does the following:

  1. Gets the green base object by its tag “PlayerBase” tag
  2. Gets the LandTileHolder object by its “LandHolder” tag
  3. Uses a foreach loop to iterate through each land tile that is a child of LandTileHolder
    1. The landscript of each land tile is retrieve
    2. The distance from the land tile to the PlayerBase tile is calculated
    3. If the the distance from the land tile to PlayerBase is less than or equal to 6.0f, set the land tile’s cannotPlaceHere value to false
      1. This means that the player CAN place on this tile
      2. The value of six was determined through trial and error when testing this out
    4. If the distance is greater than 6.0f, set the land tile’s cannotPlaceHere value to true and call its CreateCannotPlaceHereOutline function
      1. this means the player CANNOT place on this tile, and the marker object will be spawned

LimitUserPlacementByDistanceToBase will then be called in GameplayManager.cs’s Awake() function.

Now when the game is started, you should see the markers are spawned on the correct tiles.

Limiting the Player’s Placement

If you left the game as is, the player would still be able to place units on tiles that have the “Can’t Place Here” marker. This is because there is not check on if the player can place there.

To make it so the player can’t place there, the CanAllSelectedUnitsMove function in UnitScript.cs will need to be modified. Currently, the check for the Unit Placement phase looks like this:

This simply needs to be modifed so that the above only executes when the land tile the unit’s are moving to has its cannotPlaceHere value set to false. Below is the code:

if (GameplayManager.instance.currentGamePhase == "Unit Placement")
{
	if (landScript.cannotPlaceHere)
	{
		Debug.Log("Can't place here. Too far from base.");
		return false;
	}
	else if (!landScript.cannotPlaceHere)
	{
		foreach (GameObject unit in MouseClickManager.instance.unitsSelected)
		{
			UnitScript unitScript = unit.GetComponent<UnitScript>();
			unitScript.placedDuringUnitPlacement = true;
		}
		GameplayManager.instance.CheckIfAllUnitsHaveBeenPlaced();
		canMove = true;
		return canMove;
	}            
}

Now, the unit placement restriction should work!

Remember to Remove the Markers…

After the Unit Placement phase is over, the “Can’t Place Here” markers should be removed. This will be done with a new function called RemoveCannotPlaceHereOutlines in GameplayManager.cs. The code is shown below.

void RemoveCannotPlaceHereOutlines()
{
	GameObject allLand = GameObject.FindGameObjectWithTag("LandHolder");
	foreach (Transform landObject in allLand.transform)
	{
		LandScript landScript = landObject.gameObject.GetComponent<LandScript>();
		if (landScript.cannotPlaceHere)
		{
			landScript.RemoveCannotPlaceHereOutline();
			landScript.cannotPlaceHere = false;
		}
	}
}

RemoveCannotPlaceHereOutlines will do the following when called:

  1. Get the LandTileHolder object by its “LandHolder” tag
  2. Use a foreach loop to iterate through all land tiles that are children of LandTileHolder
  3. If the cannotPlaceHere value of a land tile is set to true, then RemoveCannotPlaceHereOutline will be called to remove the marker and cannotPlaceHere will then be set to false

This should do a good job of removing the markers. Now, RemoveCannotPlaceHereOutlines just needs to be called from somewhere. The place that made the most sense to me to call it from is from the EndUnitPlacementPhase function in GameplayManager.cs. EndUnitPlacementPhase is called when the player clicks the button to end the Unit Placement phase.

If you did everything correctly, it will look just like this exciting video!

Next Steps

Next, I hope to add a little GUI to the Unit Movement phase that will force the user to move in “turns.”