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 Need to simplify my way of card creation.

Discussion in 'Scripting' started by CodeFang, Dec 7, 2020.

  1. CodeFang

    CodeFang

    Joined:
    Nov 20, 2020
    Posts:
    12
    I'm currently testing a few basic scripts for later developement of a card game and am wondering if there is an easier way of implementing the creation of card objects from my card prefab.

    So far I have a very basic IEffects interface:
    Code (CSharp):
    1. public interface IEffects
    2. {
    3.     void trigger();
    4. }
    that is inherited by Effects like the following:

    Code (CSharp):
    1. public class DamageEffect : IEffects
    2. {
    3.     public int damage;
    4.  
    5.     public DamageEffect(int damage)
    6.     {
    7.         this.damage = damage;
    8.     }
    9.  
    10.     public void trigger()
    11.     {
    12.         Debug.Log("Deal " + damage.ToString() + " damage");
    13.     }
    14. }
    Then there is a Base_Card Monobehaviour:

    Code (CSharp):
    1. public class Base_Card : MonoBehaviour
    2. {
    3.  
    4.     public List<IEffects> effects = new List<IEffects>();
    5.  
    6.     ///public Dictionary<string,int> effectDict = new Dictionary<string,int>();
    7.     ///
    8.  
    9.     public new string name;
    10.     public int energyCost;
    11.  
    12.     // Start is called before the first frame update
    13.     void Start()
    14.     {
    15.  
    16.     }
    17.  
    18.     // Update is called once per frame
    19.     void Update()
    20.     {
    21.         if(Input.GetKeyDown(KeyCode.Space))
    22.         {
    23.             foreach (var effect in effects)
    24.             {
    25.                 effect.trigger();
    26.             }
    27.         }
    28.     }
    29. }
    This monobehaviour I attached to a GameObject and created a prefab from it.

    Finally I have a Card Creator class ( currently monobehaviour but just for testing purposes.)

    Code (CSharp):
    1. public class Card_Creator : MonoBehaviour
    2. {
    3.  
    4.     public GameObject Card_Prefab;
    5.  
    6.     public void Start()
    7.     {
    8.         Attack_Card();
    9.         Heal_Card();
    10.     }
    11.  
    12.     public void Attack_Card()
    13.     {
    14.         GameObject CardGO = (GameObject)Instantiate(
    15.                            Card_Prefab,
    16.                            new Vector2(0,0),
    17.                            Quaternion.identity,
    18.                            this.transform
    19.                        );
    20.         CardGO.GetComponent<Base_Card>().name = "Burning Strike";
    21.         CardGO.GetComponent<Base_Card>().effects.Add(new DamageEffect(7));
    22.         CardGO.GetComponent<Base_Card>().effects.Add(new ApplyEffect("Burn", 2));
    23.     }
    24.  
    25.     public void Heal_Card()
    26.     {
    27.         GameObject CardGO = (GameObject)Instantiate(
    28.                            Card_Prefab,
    29.                            new Vector2(0, 0),
    30.                            Quaternion.identity,
    31.                            this.transform
    32.                        );
    33.         CardGO.GetComponent<Base_Card>().name = "Self Heal";
    34.         CardGO.GetComponent<Base_Card>().effects.Add(new HealEffect(4));
    35.     }
    36. }
    Now I'm wondering if this is a good way to do it. I obviously won't need a function in the Card_Creator per card, as I could pass things like the name and the values as parameters and just have one function for each effect combination. But I would still need a long enum of all cards in the game and a large switch-statement which calls the right creation function for the card with the correct parameters.

    I'm sorry if there is an obvious easier solution, but I'm quite the newbie to coding.

    Edit: I'm currently not using ScriptableObjects, because I wanted my cards to have certain dynamic features. For example they could get stronger with each use (but just that one instance of the card.

    Edit2 : Also I should probably save the component instead of doing "GetComponent" three times in a row^^
     
  2. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,833
    Your concept of IEffects seems like a good starting point, though I bet you'll discover that the interface needs to be a bit more complicated than that. For instance, when you play a card, do you designate a target for that card? Do some effects need a reference to the player who used it--say, so that "Self Heal" knows who to heal? How about the card object they're attached to, or perhaps to some sort of battlefield object that contains a list of all the combatants?

    You may find it helpful to separate the "base" functionality of a card (that never changes) from its upgrades or other changeable data. For example, suppose you create two classes: CardBlueprint and CardSleeve. For each card design (such as "Burning Strike") you create a single CardBlueprint object, which is immutable (it never changes for any reason). Then, when the player acquires this card during the game, you create a CardSleeve that has a reference to the corresponding CardBlueprint, plus some data fields for things like the card's level or upgrades or whatever. If the player acquires 3 Burning Strike cards, you create 3 CardSleeves, but they all point to the same underlying CardBlueprint.

    Then the CardBlueprints could easily become ScriptableObjects, if you like. Or you could load them from some sort of data file, or download a list from a game server, etc.

    Regardless, you avoid some duplication of data in memory, and you have an easy way to reset a card back to its original stats, or check if two cards were originally the same design, etc.

    I would further create some sort of unique identifier (e.g. a number, or code, or formal name, etc.) for every CardBlueprint, and you should make sure that identifier always stays the same even if you quit the game and restart; this will be useful for serialization. (Do NOT simply use the order in which they're loaded at startup, unless you're willing to commit to never ever deleting a blueprint or changing the order of your blueprints.) Create some sort of script that allows you to find the correct CardBlueprint object given its ID (perhaps a Dictionary somewhere).

    I would also suggest that CardSleeve should probably not be a MonoBehaviour. Create yet another class (let's call it CardView) that's responsible for the visual appearance of your card on the screen, and give it a reference to the CardSleeve. There will probably be times when you want to keep track of cards even when they're not on the screen (e.g. when they're shuffled into the draw pile), and there may even be times you want the same card to be on-screen in more than one place simultaneously (e.g. the card is in the player's hand, but then you also show a zoomed-in version of the card that's easier to read), and all of that is much easier if the card class that's used by your gameplay logic is not tied to any specific Unity GameObject. Plus, model/view separation is handy in general.
     
    Bunny83 likes this.
  3. CodeFang

    CodeFang

    Joined:
    Nov 20, 2020
    Posts:
    12
    Yeah targetting is something I have to get to in the future as well (especially since it is gonna play on a grid map.

    So far my very naive approach was to create various "effects" that change a "target" variable (a list of grid tiles) in what currently is the Base Card Script. Other effects like "Heal" would than use that target variable. That way I could just include it in the "effects" List and could very flexibly do stuff like selecting a target, do two effects to it, select another target and do another effect in one card.

    The Sleeve and blueprint sounds smart. But i probably have to figure out how to give the actual method with a Scriptable Object Blueprint to the sleeve and still have itm react to changeable things (like a player buff that increases all damage by +1) and have it both reflective in overall gameplay and in the card description.

    Yeah especially CardView sounds like a good idea. For tracking cards I was mostly thinking about using a reference, but a non-gameobject cardsleeve that could store additional information most likely would be smarter.

    All in all I guess I have to learn a little more about Scriptable objects apart from using them as simple data containers.
     
    Last edited: Dec 7, 2020
  4. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,833
    That could work, but notice it implies that you need some sort of shared data structure where effects can read or modify the "current target". So you'll need to figure out where that variable lives.

    You may want to create a sort of Blueprint vs Sleeve distinction for Effects, too, where the Blueprint is analogous to the text printed on the card, and the Sleeve is "what is actually happening in the game at this moment".

    So you have a damage-dealing card, which has a damage-based EffectBlueprint stored in its CardBlueprint. Actually playing that card causes a new EffectSleeve to be created from that EffectBlueprint, saying exactly how much damage of what type is about to be dealt to what target. Then, you iterate over all relevant buffs, environmental effects, etc. and say "here's the EffectSleeve that's about to happen; do you want to modify it?". The player's strength buff adds +1 to the damage value, and the fiery environment changes the damage type to fire, the monster's counter-attack ability adds a new effect to your resolution list in retaliation, and the spirit link buff redirects half of the damage to a different target (possibly by creating a second EffectSleeve, and splitting the total damage between them).

    After everyone has had their say, then you actually resolve the effect based on whatever data is in the EffectSleeve at that point. (And then you destroy the EffectSleeve when you're done. If the card is played again in the future, you'll get a new EffectSleeve at that time.)

    This prevents the Effect from needing to have any knowledge of those individual changes; it just creates all the data fields that control how the damage happens, and gives an open-ended opportunity for anything else in the game to modify them.

    In fact, any upgrades on the CardSleeve might work through this same system (modifying the EffectSleeves generated from the CardBlueprint before they get resolved).



    (Any other programmers know if this blueprint-sleeve pattern has an accepted name?)