Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Help Conceptualizing Card Game Code (TCG)

Discussion in 'Scripting' started by MJacobsen186, Sep 1, 2015.

  1. MJacobsen186

    MJacobsen186

    Joined:
    Aug 28, 2015
    Posts:
    21
    Hello everyone! I've been working on a pet project as I learn my way around C#. Over the past few days I've been trying to make a road map of how the code for a card game would work. That is, the code that controls the stats for each card.

    Currently, I want to create an XML database with all the card information such as attack, defense, mana cost, effects, etc. I would then like those values to be called using a general 'card' class. Is this possible? I'm assuming I would need some type of identifier for each card like an ID number, basically a key. How would I go about attaching the key to each card so the correct stats are retrieved?

    Any help with this would be greatly appreciated!
     
  2. Fajlworks

    Fajlworks

    Joined:
    Sep 8, 2014
    Posts:
    344
    Instead of one general "Card" class, you can create smaller components that make up your Card gameObject.
    To better understand how to do this, we can try to reverse engineer a GameObject from a certain popular CCG.



    By analysing the image above we can determine:

    -ManaComponent (blue mana crystal; contains logic if the card meets condition for summoning, e.g. has enough mana)
    -DisplayComponent (image, name, description is all handled here)
    -MetadataComponent (rarity, type e.g. Dragon)
    -AttackComponent (lower left attack icon, contains logic to calculate damage and wether it can perform attack, e.g. if 0, cannot attack even if ready)
    -HealthComponent (lower right blood icon, logic wether the minion is alive, e.g. if life 0, go to graveyard)
    -AbilityComponent (meat of the card, the effect, this is probably a container component that holds smaller components with logic to perform ability)

    what is not shown on card is probably something like:
    -ActionComponent ( determines wether the monster can perform attack, how many attack (can be increased with ability), does it have summoning sickness (zzz), etc... )
    -PlayComponent ( actual code to handle when you click the card and drag it onto field )

    Further, you can analyse AbilityComponent down to even smaller parts:
    TriggerActivate: OnEnter, Friendly, Self
    TriggerDeactivate: None
    Target: Board, Self, target 0 ( 0 = all )
    SummonAbility: Whelp

    When you look at ability like this, modifying effects is easy.
    Want 2 whelps? Set target to 2.
    Want Deathrattle effect? Set TriggerActivate OnDead.
    Want to spawn Whelps when another enemy minion dies? Set TriggerActivate to OnDeath, Enemy, Other; and Target to 1.

    So, once you establish this, you can determine wether to go with:
    -XML approach: you have a external editor and save data to xml. You can use that data in runtime to generate your card gameObjects, e.g. create an instance of card prefab (with no components mentioned above) then create them and populate with data from xml
    -Unity prefab approach: create editor scripts that will enable you to customise your cards and save them as prefabs, later to be instantiated from prefabs

    Yeah, you could give them ID with number when you create them so you can easily fetch them with GetCard(int ID), but note if you plan to implement features like skins or other customisations you have to either consider wether to treat them as new cards (e.g. new ID) or some other way.

    Whatever approach you take, do not create a new class for each card. Not only is it cumbersome to handle all upcoming 100+ cards in near future, but editing them will become a nightmare. Utilise components and inspector to customise cards on the fly.

    Hope this helps!
     
    Ksanone and roojerry like this.
  3. MJacobsen186

    MJacobsen186

    Joined:
    Aug 28, 2015
    Posts:
    21
    @Fajlworks

    Thank you very much for taking the time to type out that response. I actually do know all the stats/abilities for the cards I'm going to be implementing. The game was created over the summer in physical form and tested in person. We decided that it would be fun to make it digital, as many of the effects did not translate well in a physical card game.

    XML approach: By external editor do you mean something like SQLite? I know you can create xml documents within monodevelop, that's what I originally tried to do. But, is it easier to manage via something like SQLite? Part of the original problem was that I'm not sure how to go about populating the fields with the data from xml. I've tried to find guides but most are for very specific problems unrelated to my issue. I think this is my preferred method. If anyone has a link to something I could read/watch to get me started that would be great!

    Thank you again!

    Edit: Is there a way to set it so that it can pull the data based on the gameObject's name? For instance if I attached the card script which pulls the values from the xml document to 'Angry Bear' and had a field in the xml document with the card's name, also 'Angry Bear' can I code it to pull that information? Alternatively I could pull it based on an ID, but would I have to attach a separate ID definition script to each card? I'm sorry, its taking me a bit to wrap my head around this.
     
    Last edited: Sep 1, 2015
  4. Fajlworks

    Fajlworks

    Joined:
    Sep 8, 2014
    Posts:
    344
    For XML approach I would advise you to create a custom editor app, either in or outside Unity, to customise your cards. Having an editor specifically for your cards will allow you to swiftly click/drag components, edit values, etc. which will increase your card testing tenfold, not to mention prevent errors in your xml structure. Manually structuring and editing your xml without care could lead to xml corruption, tracking faulty fields. In other words, waste of time.

    If you don't know how to make one, you could use some assets like: https://www.assetstore.unity3d.com/en/#!/content/11818

    I haven't used it so I can't really recommend, but this is what comes to mind if you want easy database going. Probably lots of tutorials out there.

    Once you have your xml ready, one way to create game objects from raw xml value would be to create Factory class. It should parse through XML fields, attach necessary components and populate with values.
     
  5. MJacobsen186

    MJacobsen186

    Joined:
    Aug 28, 2015
    Posts:
    21
    @Fajlworks

    Thank you again for your guidance. I have been reading a few of your posts in other topics regarding card games as well. I have been working on the database and would like to share what I've done. Hopefully others will find this useful.
    Card Class:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. [System.Serializable]
    5. public class Card {
    6.  
    7.  
    8.  
    9.     public string cardName;
    10.     public int cardID;
    11.     public int cardMana;
    12.     public int cardAttack;
    13.     public int cardDefense;
    14.     public int cardSpeed;
    15.     public CardType cardType;
    16.     public SubType cardSubType;
    17.     public Faction cardFaction;
    18.     public int cardGems;
    19.     public GameObject cardPrefab;
    20.  
    21.     public enum CardType{
    22.         Summon,
    23.         Chant,
    24.         Equipment
    25.     }
    26.  
    27.     public enum SubType{
    28.         Creature,
    29.         Structure,
    30.         Armor,
    31.         Weapon
    32.     }
    33.  
    34.     public enum Faction
    35.     {
    36.         Neutral,
    37.         Chaos,
    38.         Water,
    39.         Earth,
    40.         Wind,
    41.    
    42.     }
    43.  
    44.  
    45.     public Card(string name, int id, int mana, int attack, int defense, int speed, CardType type, SubType subType,
    46.                 Faction faction, int gems)
    47.     {
    48.  
    49.         cardName = name;
    50.         cardID = id;
    51.         cardMana = mana;
    52.         cardAttack = attack;
    53.         cardDefense = defense;
    54.         cardSpeed = speed;
    55.         cardType = type;
    56.         cardSubType = subType;
    57.         cardFaction = faction;
    58.         cardGems = gems;
    59.         cardPrefab = Resources.Load<GameObject>("Prefabs/" + name);
    60.  
    61.  
    62.     }
    63.                      
    64. }
    CardDB Class:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.  
    5. public class CardDB : MonoBehaviour {
    6.  
    7.     public List<Card> cards = new List<Card>();
    8.  
    9.     void Start(){
    10.         cards.Add (new Card ("Annoyed Knight", 0, 1, 2, 2, 3, Card.CardType.Summon,
    11.                              Card.SubType.Creature, Card.Faction.Neutral, 0));
    12.  
    13.         cards.Add (new Card ("Infuriated Bear", 1, 3, 5, 4, 4, Card.CardType.Summon,
    14.                              Card.SubType.Creature, Card.Faction.Neutral, 0));
    15.  
    16.         cards.Add (new Card ("Ballistic Bear", 2, 4, 5, 6, 5, Card.CardType.Summon,
    17.                              Card.SubType.Creature, Card.Faction.Neutral, 0));
    18.     }
    19. }
    20.  
    Disclaimer: please don't make fun of the test card names xD I'm using these for testing out vanilla creatures (creatures with no abilities).

    I currently have prefabs created for each of the three cards. Each prefab has:
    -a 2D box collider
    -a sprite renderer
    -3 text mesh child objects which represent: Attack, Defense and Mana cost

    Am I on the right track?

    I was wondering how I might go about creating the deck class next? Just briefly playing around with it, I have made:

    Deck Class:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.  
    5. public class PlayerSummonDeck : MonoBehaviour {
    6.  
    7.     public List<Card> playerSummonDeck = new List<Card>();
    8.     public CardDB database;
    9.  
    10.     void Start () {
    11.  
    12.         database = GameObject.FindGameObjectWithTag ("CardDB").GetComponent<CardDB>();
    13.         playerSummonDeck.Add (database.cards [0]);
    14.  
    15.    
    16.     }
    17.    
    18.  
    19.     void Update () {
    20.    
    21.     }
    22. }
    I was wondering how to start expanding the deck class further? I basically want it to be that when I click on the deck it draws the top card of the list, instatiates it as the prefab and adds it to the hand. It should then remove the instatiated card from the deck. The deck should also be shuffled at the start of its creation (but I think I know how to do that).

    I think this thread could become a valuable resource for those just starting out creating a card game. If you would like to continue to help me with this project I would be happy to keep posting the code I have created for others to see/use.

    Thank you for all your help so far!
     
  6. Fajlworks

    Fajlworks

    Joined:
    Sep 8, 2014
    Posts:
    344
    If you inspect your card code closely, you will see that it does not feature the component approach we discussed earlier.

    If you take spell card from a certain popular CCG and compare them with their minion counterparts:


    you will notice they don't have AttackComponent and HealthComponent we talked about earlier.
    Code (CSharp):
    1.   public Card(string name, int id, int mana, int attack, int defense, int speed, CardType type, SubType subType,
    2.                 Faction faction, int gems)
    3. {
    4.    ...
    5. }
    your current initialiser takes attack, defense, speed even though spells don't use them necessarily. While it doesn't matter you could say, you have to keep things organised in small modules to improve code clarity and enable easier extension. Let's say you want to add a new card type, like Field modifier in Yu-Gi-Oh! tcg. In your current setup you'd have to expand your constructor and bloat your existing cards with unnecessary info.

    As for certain CCG, I think they initialise their card once. Then they move it to deck, hand or field as it changes zones, as well as removing card layout once the minion gets into play. Think of the sequence where you play minion, get to choose target, then cancel. It probably just sets gameObject.SetActive() either false or true. Initialising cards in runtime can be time consuming for cpu so reusing same instances comes in handy.

    Your best bet would be to refactor Card into a MonoBehaviour that acts as a main script to connect your other components, like AttackComponent, HealthComponent, etc.
     
  7. Fajlworks

    Fajlworks

    Joined:
    Sep 8, 2014
    Posts:
    344
    If you want, you can practice how to save your cards into xml format by reverse engineering the "Illidan Stormrage" card linked above in last post.

    Try to save all necessary data needed to create him into xml. That way you will gain valuable insight how to structure your editor and how to lay out nodes in order to correctly initialise your GameObjects with your Factory class.

    Have fun :)
     
  8. MJacobsen186

    MJacobsen186

    Joined:
    Aug 28, 2015
    Posts:
    21
    @Fajlworks

    I tried my hand at creating Illidan in an xml database structure. This is what I came up with:

    Code (CSharp):
    1. <?xml version="1.0" encoding="UTF-8" ?>
    2.  
    3. <Card>
    4.     <Summon>
    5.     <ID>0</ID>
    6.     <Name>Illidan Stormrage</Name>
    7.     <Attack>7</Attack>
    8.     <Health>7</Health>
    9.     <Mana>7</Mana>
    10.     <Ability1>Battlecry</Ability1>
    11.     <Type>Creature</Type>
    12.     <Faction>Neutral</Faction>
    13.     <Rarity>Legendary</Rarity>
    14.     </Summon>
    15. </Card>
    16.  
    If this is incorrect, I'd appreciate if you could show me what it should've looked like. I tend to learn best by seeing examples then making them my own.

    A bit of background about my card game: I consider every card a "spell". Spells are broken down into three main categories: Summons, Chants and Equipment. This should explain why I have the 'summon' tag as the main identifier.

    I also did a bit of research regarding component based design. It actually seems quite useful for these purposes. I don't think I fully grasped what you were saying when we first spoke. I understand now haha.

    I created a mock up of the health component using a great tutorial found here:


    HealthComponent:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class HealthComponent : MonoBehaviour {
    5.  
    6.     public int current;
    7.     public int max;
    8.     public bool setMaxOnStart = true;
    9.  
    10.     // Use this for initialization
    11.     void Start () {
    12.         if (setMaxOnStart)
    13.             SetMaxHealth ();
    14.  
    15.     }
    16.  
    17.     public void RestoreHealth (int amount){
    18.         current += amount;
    19.         if (AtMaxHealth ())
    20.             SetMaxHealth ();
    21.     }
    22.  
    23.     public void ReduceHealth (int amount){
    24.         current -= amount;
    25.  
    26.         if (AtZeroHealth ())
    27.             SetZeroHealth ();
    28.     }
    29.  
    30.     public void SetZeroHealth(){
    31.         current = 0;
    32.     }
    33.  
    34.     public bool AtZeroHealth(){
    35.         return (current <= 0);
    36.     }
    37.  
    38.     public void SetMaxHealth (){
    39.         current = max;
    40.     }
    41.  
    42.     public bool AtMaxHealth(){
    43.         return (current >= max);
    44.     }
    45.  
    46.  
    47. }
    Moving forward, would you mind giving me an example of what the base class that communicates to all of these components would look like? Just a skeleton is fine, or use the health script I have above as an example. I might be asking too much at once, but should I create an ID component? Is that how one would go about attaching all of the xml values to the code?

    Thank you again!
     
    Last edited: Sep 5, 2015