3
\$\begingroup\$

I am making a simple card game in unity. The game is based on a standard 52-card pack, with identical backs for unique faces. In my particular game different cards are worth different values and have various special abilities. The game will have 52 cards on the table (on the draw position or in the face-down deck or in someone's hand) at all times, so this number won't change.

I thought that making a Card prefab and instantiating 52 of these manually would be a bad idea. Even doing it in code, I thought, would be a bit OTT, and that I should just instantiate visual cards when they are face-up to the player.

I have a sprite sheet of the 52 cards and the back, which is imported as a Sprite in multiple mode, sliced in to a grid containing all the cards needed to play the game.

The problem I now face is that, through my GameController script I want to generate a shuffled pack of cards, deal some to each player and then show those cards to a player. However, I am not sure of the best way, or even if it's possible, to do this dynamically with the sprite sheets as they are. For instance if I have the following:

private CardRank rank;
private CardSuite suite;
private GameObject visualCard; // the card in the scene

private void Start()
{
    this.rank = CardRank.Ace;
    this.suite = CardSuite.Spades;
}

This class would be instantiated by the game manager. I would have 52 of these in code.

Whenever I have to visually show a card in the scene, I would use a card prefab, which is essentially a game object with a SpriteRenderer on it. I would need to dynamically load the correct sprite for this object from the sprite sheet. Something like this:

SpriteRenderer spriteRenderer = this.visualCard.renderer as SpriteRenderer;
spriteRenderer.sprite = ...

The sliced sprites from the sprite sheet actually have names in the format AS (Ace of Spades), 7H (Seven of Hearts), etc - though this was a manual thing I did myself of course.

I have also tried various alternative solutions, including creating animations, having separate sprites not in a spritesheet and having an array of available sprites in an array with a specific index for each card, but none seem as elegant as trying to load the correct sprite at runtime, as I'm trying to.

So, how do I load a specific sprite from a spritesheet at runtime? I'm open to suggestions, even those that make me think differently about how to approach the problem.

\$\endgroup\$
1
\$\begingroup\$

Using Unity SpriteEditor cut out the sprites from the texture. In your script, add:

public Sprite[] cardSprites;

In script inspector set the size of 52. Then just fill it with sprites. You do that by going to project window and mouse-moving all sprites from sprite sheet. Now you have all your sprites individually.

Having said that, you absolutely shouldn't be having 52 scripts, each for a card. One script is enough. Have sprite list in your manager and just make a function GetSprite(CardRank, CardSuite) which will return sprite you want. You can use int values from enum to make it painless, like:

public enum CardSuite { Spades = 0, Clubs = 1, Hearts = 2, Diamonds = 3 } // enums get assigned ints from 0 automatically but it's nice to have them written down so you know you depend on it

public Sprite GetSprite(CardRank rank, CardSuite suite)
{
    // suit goes 0-3, rank goes 0-12
    return cardSprites[((int)suite + 1) * (int)rank]; // probably not correct but you get the point
}

You just have to place the sprites in inspector in a sorted way, like you have your enums.

\$\endgroup\$
1
\$\begingroup\$

You could Create one Sprite Card Object. Give it the the four suit textures, face card textures, and card background texture. Example : for the 7 of spades you would draw the spade texture at 7 different locations with a white texture of a card in the background, and use DrawString to Draw the 7. That way you could (in your game class) say Card card = new Card(7,spades), and it would then create an instance of a Card class you set up, and draw 7 spades on the card, with a 7 in the top right and bottom left upside-down.

\$\endgroup\$
1
\$\begingroup\$

my approach will be almost similar but I will prefer to use a full sprite sheet of cards to avoid the extra naming problems.

Before we get started here is the link to the sprite sheet. http://i.stack.imgur.com/gnv4Q.jpg

We need to create one script named Card which can hold its value and type. It should look like this:-

using UnityEngine;
using System.Collections;

public class Card : MonoBehaviour 
{

    public enum Suit { Hearts = 0 , Spades , Diamonds, Clubs };
    public Suit mySuit;
    public enum Value { Ace = 0 , Two , Three , Four , Five , Six , Seven , Eight , Nine , Ten , Jack , Queen , King }; 
    public Value myValue;

}

Now import the image into resources folder and slice it using default grid option. You will get nicely numbered sprites which we can use :)

enter image description here

Note that the enum in Card script are arranged as per the order of sprites in the sheet.

We need one prefab to be stored in Resources folder which will have a Sprite renderer and the Card script

enter image description here

The last part is the core logic to populate the cards.

  • Load the Sprites into an array from resources folder.

  • Run 2 nested for loops for the cards.

  • Make it a child of the current game object (optional)

  • Assign the card sprite , use i and j to calculate the index

  • Assign the enums for Suite and Value.

  • Then finally the name :) (optional)

Code:-

void PopulateCards()
{
    Sprite[] card_sprite = Resources.LoadAll<Sprite>("Cards");

    for(int i = 0 ; i < 3 ; i++)
    {
        for(int j = 0 ; j < 13 ; j++)
        {
            GameObject prefab = Resources.Load ("Card") as GameObject;
            GameObject card = Instantiate(prefab, Vector3.zero, Quaternion.identity ) as GameObject;
            card.transform.parent = this.gameObject.transform;
            card.transform.localPosition = Vector3.zero;

            card.GetComponent<SpriteRenderer>().sprite = card_sprite[(i*13)+j] ;
            card.GetComponent<Card>().mySuit = (Card.Suit)i;
            card.GetComponent<Card>().myValue = (Card.Value)j;
            card.gameObject.name = ((Card.Suit)i).ToString()+"_"+((Card.Value)j).ToString();

        }
    }
}
\$\endgroup\$
  • \$\begingroup\$ Nice solution, thanks! Noob question: would it be possible to move any of this (i.e. the Resources.Load()) out of the loop(s) to save on load time? \$\endgroup\$ – Leonard Challis Aug 24 '15 at 6:20
  • \$\begingroup\$ Yes you can. What you do is drag and drop the card prefab inside the main game object and duplicate it 52 times. Make an array variable of Card[ ] cards. And in start assign it by cards = GetComponentsInChildren<Card>(); and in Populate function for loop assign card = cards[ i ]; so that you don't need to instantiate. \$\endgroup\$ – Hash Buoy Aug 24 '15 at 7:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.