Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question How do you create a deck of cards by adding them into a List at runtime

Discussion in 'Scripting' started by drdale, Sep 28, 2023.

  1. drdale

    drdale

    Joined:
    Apr 24, 2020
    Posts:
    6
    I’m creating a card game. The cards will initially be created with a sprite on the front and a sprite on the back. The fronts and backs may be a more compound images in the future, made from multiple sprites or other images, but let’s start with a single sprite on each side.

    The cards WILL NOT all have the same back. There will be about 15 or so of types of back sprites and about 10 or so types of front sprites.
    So a card with a back with a sprite called “A” can have a front with sprite “X”. Another card with a back “A” can have a front with sprite “Y”, or “Z". …and so on.
    During play, these images are used in combination with each other on the same card and represent 2 actions by the player. The player chooses an action based on the back sprite’s capability and the result of the action depends on the front sprite’s capability. That's enough about the game play.

    I don’t want to statically pre-store all of the many, many combinations of front and back sprites. I’d like to instantiate a list[] AT RUN TIME from a data source (i.e. json) or just simple C# code logic to create the deck to be used in the game. You can see that this method requires far less space to only have about 25 front/back sprites (for now) and not hundreds of statically created cards of these combinations, which is tedious, time consuming and prone to error.

    I have searched and searched for a way to do this, but I can’t seem to find a solution. I’ve loaded many C# / Unity card game projects from GitHub, watched countless youtube videos, read unity game books and watched online courses. Not a single one of these show how to do what I’d like to do.

    Here's a simple card class to use:

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;

    public class Card : MonoBehaviour
    {
    public Sprite cardFace;
    public Sprite cardBack;
    public int cardIndex;
    public string cardName;
    public string cardDescription;
    }


    The card class could also be a scriptable object, something like this: (now referred to as CardSO)
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;

    [CreateAssetMenu(fileName = "Deck Cards", menuName = "Deck/Card")] // to create an instance of the scriptable object
    public class CardSO : ScriptableObject
    {

    public Sprite cardFace;
    public Sprite cardBack;
    public Vector3 currentPosition;
    public int cardIndex;
    public string cardName;
    public string cardDescription;

    }


    Then we can create another script to builds the deck, which has some code this.

    If we use the 'card' class:

    public static List<Card> cardDeck = new List<Card>();

    Then I have logic that's beyond the scope of this discussion, that figures out which sprites to put on the back and front (by their filename), and other fields of each card, like this:

    cardName = "GreenFront";
    cardDescription = "Leaf";
    cardFace = Resources.Load("Images/Front Green") as Sprite;
    cardBack = Resources.Load("Images/Back GPY") as Sprite;

    So, if I create a temporary instance of a card class and populate the all of the values like shown above,

    --> How do I "add" (instantiate) that newly populated card to the "cardDeck" List?

    Sorry, I don't have more code to show because I can't figure out how to do this, so I literally have nothing to show. All of the examples I've seen, use the same sprite for the card backs and they create the deck by dragging unity objects into the fields by hand, one-at-a-time.

    Thank you in advance!
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    Don't start in a vacuum... this is all EXTREMELY well-covered by tutorials.

    Part of the task will be basic data preparation and processing in collections and part of it will be interoperating with Unity prefabs to visualize it.

    Everything else is details you need to decide based on your design. Try some tutorials and learn from them to bring your design to life.

    Tutorials and example code are great, but keep this in mind to maximize your success and minimize your frustration:

    How to do tutorials properly, two (2) simple steps to success:

    Step 1. Follow the tutorial and do every single step of the tutorial 100% precisely the way it is shown. Even the slightest deviation (even a single character!) generally ends in disaster. That's how software engineering works. Every step must be taken, every single letter must be spelled, capitalized, punctuated and spaced (or not spaced) properly, literally NOTHING can be omitted or skipped.

    Fortunately this is the easiest part to get right: Be a robot. Don't make any mistakes.
    BE PERFECT IN EVERYTHING YOU DO HERE!!


    If you get any errors, learn how to read the error code and fix your error. Google is your friend here. Do NOT continue until you fix your error. Your error will probably be somewhere near the parenthesis numbers (line and character position) in the file. It is almost CERTAINLY your typo causing the error, so look again and fix it.

    Step 2. Go back and work through every part of the tutorial again, and this time explain it to your doggie. See how I am doing that in my avatar picture? If you have no dog, explain it to your house plant. If you are unable to explain any part of it, STOP. DO NOT PROCEED. Now go learn how that part works. Read the documentation on the functions involved. Go back to the tutorial and try to figure out WHY they did that. This is the part that takes a LOT of time when you are new. It might take days or weeks to work through a single 5-minute tutorial. Stick with it. You will learn.

    Step 2 is the part everybody seems to miss. Without Step 2 you are simply a code-typing monkey and outside of the specific tutorial you did, you will be completely lost. If you want to learn, you MUST do Step 2.

    Of course, all this presupposes no errors in the tutorial. For certain tutorial makers (like Unity, Brackeys, Imphenzia, Sebastian Lague) this is usually the case. For some other less-well-known content creators, this is less true. Read the comments on the video: did anyone have issues like you did? If there's an error, you will NEVER be the first guy to find it.

    Beyond that, Step 3, 4, 5 and 6 become easy because you already understand!

    Finally, when you have errors, don't post here... just go fix your errors! Here's how:

    Remember: NOBODY here memorizes error codes. That's not a thing. The error code is absolutely the least useful part of the error. It serves no purpose at all. Forget the error code. Put it out of your mind.

    The complete error message contains everything you need to know to fix the error yourself.

    The important parts of the error message are:

    - the description of the error itself (google this; you are NEVER the first one!)
    - the file it occurred in (critical!)
    - the line number and character position (the two numbers in parentheses)
    - also possibly useful is the stack trace (all the lines of text in the lower console window)

    Always start with the FIRST error in the console window, as sometimes that error causes or compounds some or all of the subsequent errors. Often the error will be immediately prior to the indicated line, so make sure to check there as well.

    Look in the documentation. Every API you attempt to use is probably documented somewhere. Are you using it correctly? Are you spelling it correctly? Are you structuring the syntax correctly? Look for examples!

    All of that information is in the actual error message and you must pay attention to it. Learn how to identify it instantly so you don't have to stop your progress and fiddle around with the forum.
     
  3. drdale

    drdale

    Joined:
    Apr 24, 2020
    Posts:
    6
    Kurt, Thanks for the quick reply. I've seen your exact reply before and I disagree with you. I am not a new programmer, but very new to Unity. I've watched videos by Brackeys, Imphenzia, Sebastian Lague and many others, all talented and well-versed with Unity. I've followed step-by-painful-step of each one of their postings for other information in whatever form (YouTube, single posting, etc.), and they work just fine and I can follow them
    I know also how to debug - that's not an issue.
    I can't post code, because I don't have any on this specific topic.

    Here's where I'Il disagree with your canned reply. I have not come across one single example of code that shows how to do what I am asking. If you know how to do this, please provide some insight, whether you show a working (not just a "I think this will work") code example or provide a pointer/link to somewhere on the net that does this functionality. I would think that it can't be too difficult, if you know how to do it, I just don't have a clue for how to do it and moreover, nobody else seems to encode a deck of cards programmatically at runtime and I don't understand why, as I explained in the original post.

    If you state this:
    Re: Don't start in a vacuum... this is all EXTREMELY well-covered by tutorials.
    So, you must know where this exact topic is covered? Please elaborate with examples and don't just brush me off. Teach people.

    Again, thanks for your answer, but it doesn't help to move this to a resolution. I have multiple things that I don't understand.
    WHY they are card game projects done that way with decks (in Unity)?
    HOW can I do what I am asking (in Unity)?
    Maybe the answer to the WHY question will provide insight to the HOW question by using a different method, but it doesn't seem like it based on my cards, which are different than any I've seen.
     
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    Make a prefab, add two sub children Quads to them, flip one over to face the other way. Make two materials, put them on those quads. Now swap out those materials according to what you want to show on front and back of each card.

    Once you have made that prefab along with some sample materials, work with it until you are 100% confident you could replicate what you just did.

    Now make a root script to "drive" desired materials into the two slots of that prefab, get to parsing your JSON or whatever data. NOTE: I highly recommend instead of JSON just use ScriptableObject instances.

    Iterate that collection of definitions and spawn each card, configuring it by its root script.

    If you want an example of a root script used to dynamically configure instantiated objects based on a flexible data-driven input stream, see the enclosed project that spawns X number of "Cards" in a UI grid.

    If you need examples of spawning and instantiating all kinds of random stuff, look at just about any game tutorial, or you're welcome to sniff through my MakeGeo project.

    MakeGeo is presently hosted at these locations:

    https://bitbucket.org/kurtdekker/makegeo

    https://github.com/kurtdekker/makegeo

    https://gitlab.com/kurtdekker/makegeo

    https://sourceforge.net/p/makegeo
     

    Attached Files:

  5. zulo3d

    zulo3d

    Joined:
    Feb 18, 2023
    Posts:
    510
    Code (CSharp):
    1. public List<GameObject> cardDeck = new List<GameObject>();
    2.  
    3.     void Start()
    4.     {
    5.         GameObject cardObj=Instantiate(cardPrefab,pos,Quaternion.identity);
    6.         Card card=cardObj.GetComponent<Card>();
    7.         card.cardName = "GreenFront";
    8.         card.cardDescription = "Leaf";
    9.         cardDeck.Add(cardObj);
    10.     }
     
  6. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    I don't know why you'd want to store all these combinations statically. This is all stuff you'd just design via prefabs and scriptable objects, that can simply be combined as required at run time.

    It's also important to note that you need a distinction between the authored card data (the assets), and the data operating at runtime, such as having a scriptable object that gets injected into a plain C# class, that represents a live instance of a card in your runtime.

    The SO can have a pre-determined card back/front, while outside modifiers might change up both of these at runtime by operating on the live instance.
     
  7. drdale

    drdale

    Joined:
    Apr 24, 2020
    Posts:
    6
    Thank you. This is what I originally thought I could do, but I haven't seen a running example that does it. All of the examples I saw, put the cards into Lists or similar. The Lists might be for the deck itself, or a 'deck' that represents each player's hand, or a card on the table that is owned by one of the players, etc. At the least, the deck List was always pre-determined by dragging card images and other values into them at development time.

    I also haven't seen anyone's example code create an SO, with some (random) Sprite dragged into it in Unity (all of this is set up at development time), THEN change the Sprite at run time.

    Please elaborate on:
    It's also important to note that you need a distinction between the authored card data (the assets), and the data operating at runtime, such as having a scriptable object that gets injected into a plain C# class, that represents a live instance of a card in your runtime.

    Thank you!
     
  8. drdale

    drdale

    Joined:
    Apr 24, 2020
    Posts:
    6

    Do I need to do anything to populate the Sprites? I would not need to have a separate object for each one, right?
    Lines 1-2 instantiate the card object (as I referred to as a temp object), with whatever values in it at development time, including some Sprites (that need to get changed someplace/somehow?).
    Lines 3-4 add some ID strings, which for me are mostly for debugging.
    Line 5 does the insert of the new card object at the end of the List.

    Thank you!
     
  9. drdale

    drdale

    Joined:
    Apr 24, 2020
    Posts:
    6
    Wow! This is a lot to digest. I loaded your UI project and I'll see what I can possibly re-use from it.
    Does your makegeo project run anywhere online?
    Which of these makegeo parts would be the most useful for the solution to my questions?

    This is a bit terse for me to understand. Please elaborate.
    Make a prefab, add two sub children Quads to them, flip one over to face the other way. Make two materials, put them on those quads. Now swap out those materials according to what you want to show on front and back of each card.

    For example, one question, why are you flipping one face? Don't you just flip the object itself and have the faces the right way and not flipped?

    Thank you again!
     
  10. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    Go get a card deck. ANY card deck: Hoyle, Pokemon, Magic, doesn't matter.

    1. The card deck is the authored data in your game.

    Now deal those cards out onto a table into some kind of game between a bunch of players.

    2. The information encoding how those cards are set up is the runtime operating data (state) of your game.

    ScriptableObjects is good for the first, not so much for the second.

    MonoBehaviours and plain old C# classes generally serve for the second.

    Don't mix those two concepts up.

    Remember this is just data.

    The logic to say "My Lightning Bolt does 3 points of damage to your Dirkwood Boars" is something else entirely. That's the game's business logic. It will operate with and on the above data.
     
  11. zulo3d

    zulo3d

    Joined:
    Feb 18, 2023
    Posts:
    510
    You can create the card object/mesh in Unity with two quads facing opposite directions for front and back and then set the textures through their material. Or you can create the card in a modelling program to half the draw calls.

    Personally I would use a large texture like this and then set the texture offset for each card that I instantiated. Or you could create a unique UV mapped mesh for each card so then they can all share the same material. This will keep the draw calls to a minimum.

    It all depends on how far you want (or need) to go to optimize it. So many options..
     
  12. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    Why do you need to see this code though? If you're not new to coding, as you say, you should be able to take in ideas and concepts and apply them to your own programming patterns. Not to mention come up with your own solutions to any given problem; problem-solving being the primary skill needed in programming.

    You won't ever find a full example of this kind of stuff, or tutorials to do very specific implementations, because it's not worth anyone's time to do so. It would also take hundreds of hours to cover.

    Ultimately you need to plough ahead with this kind of stuff on your own to some degree. We can only offer general ideas and techniques you can use, but we're likely not going to lay it out in code.
     
  13. SeerSucker69

    SeerSucker69

    Joined:
    Mar 6, 2021
    Posts:
    65
    I would make a card from a number of gameobjects (1 game object for the card background, 1 for any text, 1 for any other images etc) Then make that a prefab.

    To create a card you just instantiate the prefab, and change the images in the sprite renderers in a DrawCard Function.

    Store all your different card data in an array of objects.

    Here's some code using this technique from one of my games, I am drawing 2D hex tiles in a grid, but you get the idea.

    Code (CSharp):
    1.  
    2.  
    3. // To create the card/hex tile (do this once)
    4.  
    5.            for (int y = 1; y <= MaxY; y++)
    6.             {
    7.                 var Spritename = "Black";
    8.                 var SpawnedHexTile = Instantiate(_HexTilePrefab, MyHexTilesParent.transform.position + MapToScreen(x, y), Quaternion.identity, MyHexTilesParent.transform);
    9.                 MapArray[x, y].MyGO = SpawnedHexTile;    // Store the created game object into the map array
    10.                 var myrend = SpawnedHexTile.GetComponent<SpriteRenderer>();
    11.                 myrend.sprite = Resources.Load<Sprite>("Hexes/" + Spritename);
    12.                 SpawnedHexTile.name = $"Hex {x},{y}";
    13.             }
    14.  
    15.  
    16. // To update a card/hextile any time you want
    17.  
    18.  
    19.     public void DrawHex(int x, int y)    // Draws the passed Hex on the Map
    20.     {
    21.         var MyHexData = ReturnHexTypeData(MapArray[x, y].HexType);
    22.         var MyGO = MapArray[x, y].MyGO;
    23.         var myrend = MyGO.GetComponent<SpriteRenderer>();
    24.         myrend.sprite = Resources.Load<Sprite>("Hexes/" + MyHexData.Spritename);
    25.     }
    26.  
    27.  
    28.  
    29.  
     
  14. drdale

    drdale

    Joined:
    Apr 24, 2020
    Posts:
    6
    Thanks for the replies! I was away on some family business for several days.
    I managed to get some sprites into Lists, and things are somewhat better understood. I am still working on it.

    Here's one of the places where I am getting lost. Per the suggestion above and from other sources, I should use this to create a List:
    public List<GameObject> cardDeck = new List<GameObject>();

    Well, I can't seem to make that work with a Sprite.

    I had to use:
    public List<Sprite> cardBackSpriteList; // = new List<Sprite>();
    public List<Sprite> cardFrontSpriteList; // = new List<Sprite>();

    Why the difference?

    I added the Sprites to the List using:
    cardBackSpriteList.Add(Resources.Load<Sprite>("Sprites/Rock Fill"));

    To display the sprites, I had to
    1. create an empty game object (EGO) in the hierarchy.
    2. Make the sprites into prefabs.
    3. .Create a C# script to manage the placement of the sprites using an array. (I was just drawing them in-line along the X axis).
    Declare:
    public GameObject[] cards;​
    Align:
    float space = (lastTrans.position.x - firstTrans.position.x) / (cards.Length - 1);

    for(int i = 0; i < cards.Length; i++)
    {
    cards.transform.position = new Vector2(firstTrans.position.x + space * i, firstTrans.position.y);
    }



    4. Drag the prefabs under the EGO to the screen.
    5. Drag those prefabs from under the EGO to the Inspector, to the 'cards' array.

    Run it. Seems to work, but it seems like a lot of manual work that I hoped to be able to do under program control.
    If I change anything, like when debugging, for example, change the array size from 10 to 3 in the Inspector, I have to delete all of the items in the array and re-add them. Seems like odd behavior, because if I change the array size back to 10 (before deleting them of course), all of the prefabs are still there in the array. I don't get any errors. Nothing happens on the screen.

    I also don't understand why I need to pre-drag them to the screen.

    Again, this is just playing in the sandbox to try to understand these concepts and how Unity and C# work together.
    I just get lost when I see examples of ways to do things (sometimes under older versions of Unity) that either don't work, are deprecated, or don't apply for some reason to what I am attempting to do.

    Again, thanks for the patience and for the guidance. I am getting there.
     
  15. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    A
    List<GameObject>
    could never contain a sprite. That's not a thing. C# is a type-safe language.

    A
    List<Sprite>
    is where you could drag sprite assets

    A
    List<SpriteRenderer>
    is where you could drag GameObjects WITH SpriteRenderers on them, which in theory might have Sprites in their slots, might not.

    Otherwise...

    Unity's component architecture

    https://forum.unity.com/threads/custom-gameobject.967582/#post-6299125

    Terminology primer for assets:

    https://forum.unity.com/threads/con...nto-materials-for-quads.1369056/#post-8632563

    Also,

    If you post a code snippet, ALWAYS USE CODE TAGS:

    How to use code tags: https://forum.unity.com/threads/using-code-tags-properly.143875/

    - Do not TALK about code without posting it.
    - Do NOT post unformatted code.
    - Do NOT retype code. Use copy/paste properly using code tags.
    - Do NOT post screenshots of code.
    - Do NOT post photographs of code.
    - ONLY post the relevant code, and then refer to it in your discussion.