CardConquest GameDev Blog #13: Creating Cards and other Card Related Stuff

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.

Lucky number 13! I will finally be creating some cards for a game I have called “CardConquest.” The cards won’t do anything, but they will be there and you will be able look at them. Oh, and guess what? There will be a bunch of UI and buttons to add for this too! I’m getting all bothered and hot just thinking about it.

Making the Cards

To add cards to the game, I first need to have card sprites that would be imported into Unity. I started by searching for “trading card template” and looked for a really simple card that I could then edit. The blank card template I settled on is this:

I wanted the card to have the color of the player (in this case, green) and for that color to be easily changed with something like paint’s paint bucket tool. Nothing too complicated. No varying shades or gradients that would be annoying. Because I am using paint this didn’t actually work out that well but anyway, that’s the blank card.

Based on my original text only version on CardConquest, the card should display three different values:

  • A “Power” value
  • An “Attack” value
  • A “Defense” value

For the power, I was just going to put some text in the lower green box. For attack and defense, I decided to make some little sprites that I would add to the white area.

Here is the “attack” sprite:

And here is the “defense” sprite:

If a card has an attack value of 2, there would be two crossed sword sprites on it. If it has an attack of 4, there would be 4 sword sprites, and so on. Same goes for defense. 2 defense, two defense sprites, yadda yadda yadda

I was going to make 5 cards for the player. There would be a card for power 1, power 2, and so on to power 5 for a total of 5 cards. Each card would have its own attack and defense values.

I wanted the cards I was going to make to match the text based version’s cards. Those card had the following values:

So, the sprites for each card, starting at power 1 and going to power 5, are below:

So many beautiful, beautiful cards…

Adding the Cards to Unity

Save all the cards to the “Sprites” directory of your Unity project. I have all the attack/defense sprites and the blank card template in my sprites directory, but those aren’t really necessary. You only need the actual card sprites in there.

To add a card to the scene, simply drag and drop it from the Sprites directory into the scene. For right now, do this with “card-power-5.”

You will notice that the card looks quite small and is hard to see in the game scene. Select the card in the hierarchy, and then in the Inspector window, adjust the cards scale so its x and y values are 1.75.

Depending on where you placed your card in the scene, you may have noticed that is rendering “behind” other objects like land tiles.

To adjust this, a new sorting layer will be created. The cards should render over everything except for UI elements when the player is viewing them.

Expand “Sorting Layers” and then click on the “+” sign.

Add a new layer called “PlayerCards”

Then, make sure to move the PlayerCards layer so it is above UI-Text by dragging it up

Select the card in the scene again. In the Inspector, under “Sprite Renderer,” set the sorting layer to PlayerCards and set the order in layer to 1.

Back in the scene, you should now see the card rendering over top of the land tiles and units.

Card Script

The last thing to add to the card will be a “Card” script. The script will be what is used to actually track the power, attack, and defense values of the cards, and will later be used for any card specific functions.

For right now, go to the Scripts directory of your project and create a new C# Script.

Rename the script to “Card”

Double click on the Card script to open it in VisualStudio. Add the following variables to the script.

public string CardName;
public int Power;
public int AttackValue;
public int DefenseValue;

Save Card.cs and go back to Unity. Attach Card.cs to the card in the scene by dragging and dropping the script onto the card object in the hierarchy.

If you select the card object now, you should see the variables from Card.cs in the Inspector window. Edit the variables so they match the values from the text based version. I have the “power 5” card in my scene, so the values should be set to:

  • Card Name: Power5
  • Power: 5
  • Attack Value: 2
  • Defense Value: 2

Saving the Card as a Prefab

Now the card can be saved as a prefab. To make it easier to find the card specific prefabs, create a new sub-directory in the Prefabs directory called “CardPrefabs”

Open the CardPrefabs directory and then drag and drop the card from the hierarchy to the directory to save the prefab.

The card has been saved! Now, you get to do all of this 4 more times for each card. For each card, do the following:

  • Drag the sprite into the scene
  • Set the X and Y scale to 1.75
  • Set the sorting layer to PlayerCards and the Order in Layer to 1
  • Attach Card.cs
  • Set the correct card values

The card values are, for each card:

  • Power 4
    • Card Name: Power4
    • Power: 4
    • Attack Value: 2
    • Defense Value: 1
  • Power 3
    • Card Name: Power3
    • Power: 3
    • Attack Value: 3
    • Defense Value: 0
  • Power 2
    • Card Name: Power2
    • Power: 4
    • Attack Value: 0
    • Defense Value: 2
  • Power 1
    • Card Name: Power1
    • Power: 1
    • Attack Value: 1
    • Defense Value: 1

All the cards should now be saved as prefabs:

Organizing the Cards

Right now, my cards are all haphazardly placed in my scene

The location of these cards doesn’t really matter right now. Later, a script will be used to organize their location on the player’s screen.

Right now, though, I want an easy way to organize them in the hierarchy. Start by creating a new empty game object. Right click in the hierarchy and select “Create Empty”

Rename the empty object as “PlayerCardHand.” This object will hold all the card objects as “children” and will be used as a reference when repositioning the cards. Because it will be the position reference, it is important to make sure that the PlayerCardHand object’s position values are all set to 0 in the Inspector window.

As a little tip/trick, you can either change the position values manually or you can change them all to 0 at once by right clicking on the “Transform” bar in the Inspector window and then selecting “Reset”

Next, to allow for scripts and other objects to quickly find the PlayerCardHand object, a tag will be added to it. Click on the drop down next to “tag” and select “Add Tag…”

Click on the “+” sign, set the new tag name to “PlayerHand,” and then click save.

After the tag has been created, select PlayerCardHand in the hierarchy again, go to the Inspector window, and from the “Tag” dropdown select “PlayerHand”

With the tag set, you can now select all of the cards in the hierarchy and drag them onto PlayerCardHand. This will make the cards children of PlayerCardHand.

To make things easy for now, hide all the cards in the scene by unchecking them in the inspector. You can select them all in the hierarchy (hold down control while clicking them) and uncheck them all at once.

Time for some UI!

To view the cards, there will be a button in the UI that displays the player’s hand. There will also be a button to display the player’s discard pile, which will be cards they have played.

ALSO, the way this game should (eventually) work, is that a player should be able to see the cards in the other player’s hand as well. Part of the strategy of the game is that both players always know what cards are available to their opponent, and to plan their attacks based on that. So, that means there will also be buttons to see the opponent’s hand and discard pile.

That means there will be 4 new buttons. For now, the only button that will actually do anything is the button to show the player’s hand. The other 3 buttons will be created, but for now, they will be useless.

But wait, actually! I will want a fifth button. The fifth button will be used to hide the player’s hand when they are viewing the cards. So, in the normal UI, there will be a button to show the player’s hand. When the player clicks that, the cards appear. Also, the “show hand” button will disappear and a “hide hand” button will replace it. So, one more button! (and even more later!)

Right now, the buttons for the cards will be placed under the “UnitMovementUI.” This will be so they are activated/deactivated whenever the other UnitMovementUI elements are as well. This may change in the future, as I will likely want players to be able to view cards outside of just the unit movement phase, but for now this is what I am doing.

Making the Buttons

The new buttons are going to be the same size as the “End Unit Movement” and “Reset All Movement” buttons. So, a quick way to create 5 new buttons is to copy an existing button, such as ResetAllMovementButton, and pasting it 4 times. The result should look like this:

The new, pasted buttons can all be renamed as the following:

  • showPlayerHandButton
  • hidePlayerHandButton
  • showPlayerDiscardPile
  • showOpponentsHand
  • showOpponentsDiscard

When you pasted all the buttons, it also meant that each new button had the same “On Click ()” function set from ResetAllMovementButton. This will need to be removed. Go to each new button and click on the “-” sign in the On Click section to remove the function.

Positioning the Buttons

Now, the fun of positioning everything. showPlayerHandButton and hidePlayerHandButton will be in the same location, so you can select them both to position them at the same time.

In the Inspector, set the anchor to Top Right

Then, set the positions to: Pos X: -175 Pos Y: -25

Next, set showPlayerDiscardPile to the following:

  • Anchor: Top Right
  • Pos X: -175
  • Pos Y: -75

For showOpponentsHand:

  • Anchor: Bottom Right
  • Pos X: -175
  • Pos Y: 75

And showOpponentsDiscard:

  • Anchor: Bottom Right
  • Pos X: -175
  • Pos Y: 25

The buttons should now look like this:

Updating the Button Text

The text for the buttons doesn’t make any sense right now. All the buttons should have the following updated text:

  • showPlayerHandButton
    • Text: Cards in Hand
    • Font Size: 40
  • hidePlayerHand
    • Text: Hide Hand
    • Font Size: 40
  • showPlayerDiscardPile
    • Text: Discard Pile
    • Font Size: 40
  • showOpponentsHand
    • Text: Opponent’s Hand
    • Font Size: 40
  • showOpponentsDiscard
    • Text: Opponent’s Discard
    • Font Size: 35

The buttons with their new text will look like this:

Time to Script Everything Out

All the bits and pieces from the cards to the card holder to the UI should now be created. Next, a bunch of scripting will need to be done to get all this working.

To get this scripting ball rolling, first create a new script called PlayerHand.cs.

Then, add the PlayerHand.cs script to the PlayerCardHand object.

The PlayerHand.cs Script

First, to setup the PlayerHand.cs script, you will need to add using System.Linq; under using UnityEngine;. System.Linq allows you to use Linq to manipulate lists, which will happen later in the script. Then, the following global variables can be added:

public static PlayerHand instance;
public List<GameObject> Hand;
public List<GameObject> DiscardPile;

public bool isPlayerViewingTheirHand = false;

PlayerHand instance will be used to create an instance of the PlayerHand object, as has been done before with other objects. Hand will be a list of the cards in the player’s hand. DiscardPile will be a list of the cards in the discard pile. The boolean isPlayerViewingTheirHand is used to track when the player is viewing cards in their hand.

The first function to add to PlayerHand.cs is the “MakeInstance” function to create a instance of PlayerHand. The “Start()” function can also be renamed to “Awake().” Then, MakeInstance will be called from Awake

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

Creating the Player’s Hand

Now, a function will be created to create the player’s hand called InitializePlayerHand. InitializePlayerHand will find the PlayerCardHand object by its tag and then put all the cards into the “Hand” list of PlayerHand.cs. The code is shown below:

void InitializePlayerHand()
{
	GameObject playerCardHandObject = GameObject.FindGameObjectWithTag("PlayerHand");
	foreach (Transform cardChild in playerCardHandObject.transform)
	{
		Hand.Add(cardChild.gameObject);
	}
	// Sort the hand based on the power?
	Hand = Hand.OrderByDescending(o => o.GetComponent<Card>().Power).ToList();
}

That last line, Hand = Hand.OrderByDescending(o => o.GetComponent().Power).ToList();, reorganizes the Hand list and makes sure the the cards are ordered by their “Power” value, going from highest to lowest. This means the list will start with Power5, then Power4, and so on down to Power1.

InitializePlayerHand will then need to be called in the Awake function. And before InitializePlayerHand is called, the Hand and DiscardPile lists will need to be initialized as empty lists.

Showing the Player’s Hand

Ideally, when the “Cards in Hand” button is pressed, it will run a function that displays all the player’s cards to them. The card objects will be activated and they will be placed on the screen in a specific position.

Visually, there are some other things I will want to have happen, also. When the player is looking at their cards, some elements of the UI should probably be hidden, such as the buttons to end the turn or to reset movement. Really, all the user would see are the cards and the button to hide to the cards. Also, there are some other UI elements that should probably be hidden, such as any “x2” text next to units on land objects.

First, let’s get the “show the cards” part out of the way. In PlayerHand.cs, create a function called ShowPlayerHandOnScreen. The code is shown below:

public void ShowPlayerHandOnScreen()
    {
        isPlayerViewingTheirHand = true;
        Vector3 cardLocation = new Vector3(-10f, 1.5f, 0f);
        foreach (GameObject playerCard in Hand)
        {
            if (!playerCard.activeInHierarchy)
            {
                playerCard.SetActive(true);
                playerCard.transform.position = cardLocation;
            }
            cardLocation.x += 4.5f;
        }
        // Hide land text since it displays over cards
        GameObject landHolder = GameObject.FindGameObjectWithTag("LandHolder");
        foreach (Transform landChild in landHolder.transform)
        {
            LandScript landScript = landChild.GetComponent<LandScript>();
            landScript.HideUnitText();
        }
    }

ShowPlayerHandOnScreen does the following:

  • sets isPlayerViewingTheirHand to true
  • creates a Vector3 cardLocation variable for the card location. I found this value by trial and error placing cards around in the Unity editor
  • Uses a foreach loop to iterate through each card in the Hand list
    • If the card is not active:
      • make the card active
      • set the card’s transform position to cardLocation’s coordinates
    • Adds 4.5f to cardLocation’s X coordinate to space out the next card
  • After all cards have been activated and positioned, the following happens:
    • the landHolder object is found by the tag “LandHolder”
    • a foreach loop iterates through each land object in landHolder
      • the land object’s HideUnitText function is called to hide any unit texts (the “x2”)

Then, to hide the cards, the function HidePlayerHandOnScreen will be made. Code below:

public void HidePlayerHandOnScreen()
{
	isPlayerViewingTheirHand = false;
	foreach (GameObject playerCard in Hand)
	{
		if (playerCard.activeInHierarchy)
		{
			playerCard.SetActive(false);
		}
	}
	GameObject landHolder = GameObject.FindGameObjectWithTag("LandHolder");
	foreach (Transform landChild in landHolder.transform)
	{
		LandScript landScript = landChild.GetComponent<LandScript>();
		landScript.UnHideUnitText();
	}
}

HidePlayerHandOnScreen does the following:

  • Sets isPlayerViewingTheirHand to false
  • uses a foreach loop to iterate through each card in the Hand list
    • deactivates the card object
  • Gets the landHolder object by the “LandHolder” tag
  • goes through each land object in landHolder with a foreach loop
    • calls the land objects UnHideUnitText function to re-activate the unit text if it exists

Calling the Functions to Show/Hide Player’s Cards

Next, something will need to call the ShowPlayerHandOnScreen and HidePlayerHandOnScreen functions. I decided to do this from the GameplayManager.cs since that script currently handles a lot of other functions that are executed when a button is clicked.

To get started, the showPlayerHandButton and hidePlayerHandButton objects will need to be added to GameplayManager.cs so that those buttons can be activated/deactivated as necessary. Remember, the hide button should only be available when the cards are viewed, and the show button only when the cards aren’t being viewed.

Add the following to the global variables in GameplayManager.cs

[SerializeField]
private GameObject showPlayerHandButton, hidePlayerHandButton;

Then, in the ActivateUnitMovementUI function of GameplayManager.cs, add the following check to make sure the hidePlayerHandButton is deactivated when the Unit Movement phase starts.

if (hidePlayerHandButton.activeInHierarchy && !PlayerHand.instance.isPlayerViewingTheirHand)
	hidePlayerHandButton.SetActive(false);

Save GameplayManager.cs and then go back to Unity. Select the GameplayManager object in the hierarchy, and then make sure its script has the “Show Player Hand Button” and “Hide Player Hand Button” variables set to the corresponding buttons.

Functions for the Buttons

Now, finally, it is time to crete the functions that the buttons will call. In GameplayManager.cs, two new functions named ShowPlayerHandPressed and HidePlayerHandPressed will be created. First, the code for ShowPlayerHandPressed is shown below:

public void ShowPlayerHandPressed()
{
	if (!EscMenuManager.instance.IsMainMenuOpen)
	{
		endUnitMovementButton.SetActive(false);
		resetAllMovementButton.SetActive(false);
		showPlayerHandButton.SetActive(false);
		unitMovementNoUnitsMovedText.gameObject.SetActive(false);
		hidePlayerHandButton.SetActive(true);
		PlayerHand.instance.ShowPlayerHandOnScreen();
	}

}

ShowPlayerHandPressed is pretty straightforward. So long as the Esc Menu isn’t open, it does the following:

  • deactivates the endUnitMovementButton
  • deactivates the resetAllMovementButton
  • deactivates the showPlayerHandButton
  • deactivates the “No Units Moved” text
  • activates the hidePlayerHand button
  • calls ShowPlayerHandOnScreen from PlayerHand

The code for HidePlayerHandPressed is below:

public void HidePlayerHandPressed()
{
	if (!EscMenuManager.instance.IsMainMenuOpen)
	{
		endUnitMovementButton.SetActive(true);
		showPlayerHandButton.SetActive(true);
		if (haveUnitsMoved)
		{
			resetAllMovementButton.SetActive(true);
		}
		else if (!haveUnitsMoved)
		{
			unitMovementNoUnitsMovedText.gameObject.SetActive(true);
		}

		hidePlayerHandButton.SetActive(false);
		PlayerHand.instance.HidePlayerHandOnScreen();
	}

}

As long as the Esc Menu isn’t open, HidePlayerHandPressed will do the following:

  • activate the endUnitMovementButton
  • activate the showPlayerHandBeutton
  • If haveUnitsMoved is true, activate the resetAllMovementButton
  • if haveUnitsMoved is false, activate the “No Units Moved” text
  • deactivate the hidePlayerHandButton
  • call HidePlayerHandOnScreen from PlayerHand

GameplayManager.cs can now be saved.

Attaching the Functions to the Buttons

First, let’s attach ShowPlayerHandOnScreen from GameplayManger.cs to the showPlayerHandButton. In Unity, select showPlayerHandButton in the hierarchy, then in the Inspector window, click on the “+” sign under “On Click ().” Attach the GameplayManager object.

Then, click on the “No Function” drop down, go to GameplayManager, and select ShowPlayerHandPressed

Do the same for the hidePlayerHandButton. Select hidePlayerHandButton in the hierarchy, click the “+” sign under “On Click ()”, add the GameplayManager object, then select the HidePlayerHandOnScreen function.

Hiding Cards with Esc

One last thing I wanted to add was the ability for the player to hide the cards by hitting the “esc” key instead of having to press the button. This can be down by addin simple checks in EscMenuManager.cs. The checks will do the following:

  • Only open the Esc Menu if isPlayerViewingTheirHand from PlayerHand.cs is false
  • If isPlayerViewingTheirHand is true, call HidePlayerHandPressed from PlayerHand.cs
if (Input.GetKeyDown(KeyCode.Escape) && PlayerHand.instance.isPlayerViewingTheirHand == false)
{
	Debug.Log("Opening the ESC menu");

	IsMainMenuOpen = !IsMainMenuOpen;
	escMenuPanel.SetActive(IsMainMenuOpen);
}
else if (Input.GetKeyDown(KeyCode.Escape) && PlayerHand.instance.isPlayerViewingTheirHand == true)
{
	GameplayManager.instance.HidePlayerHandPressed();
}

Now, save everything, and it’s time to test it out. Let’s roll that beautiful video…

Next Steps

After a little over a month, the blog version of CardConquest has now caught up with my local version. So, now I need to get working on actually developing my game.

I was thinking of trying to make a “complete” version of this game that is all singleplayer, and then trying to adapt that to multiplayer. Basically try to make a “hotseat” version of the game and then later making it multiplayer.

But, I started this project to learn how to make games, specifically multiplayer games, so I decided that I’m just going to dive it at this point into learning how the hell multiplayer works in Unity. Hopefully I can figure it out!

From now on, posts in this series will probably be fewer and far between. Up to now I was just redoing all my old work that I had already figured out how to do. Now, I will first need to figure out how to do something, and then redo it for the blog. Sounds like it will be fun!