Advice for handling cards

Hi all,
Let me preface with I have an advanced beginners knowledge of coding.

I am working on a card game where I would like to have 3 discreet types of cards. Plant, Status and Passive.
I want each card to have its own prefab. I also want to create the cards as Scriptable Objects. The different cards are given a cardType (Plant, Status or Passive) to help with instantiation etc.

I am unsure about the best way to handle this.

Previously I have ran with the idea of having three scripts for the different SO (i.e PlantSO, StatusSO and PassiveSO) then bump into the issue of sending this to the CardController script. Particularly when inheriting information (hope this is the correct term).
i.e
public void Initialize(Card card, int ownerID)
{
this.card = new Card(card);
{
this.card.ownerID = ownerID;
}
illustration.sprite = card.illustration;
cardName.text = card.cardName;
description.text = card.description;
days.text = card.days.ToString();
harvest.text = card.harvest.ToString();
orignalParent = transform.parent;
}

I have also tried a different method where the three cards are created in one SO and, again, sent to the CardController.
i.e
private void CollectInfoFromCardSO()
{
if (cardData == null)
{
Destroy(this.gameObject);
return;
}
if (cardType == CardTypes.PLANT)
{
cardName = cardData.cardName;
cardDays = cardData.cardDays;
cardHarvest = cardData.cardHarvest;
cardFlavour = cardData.cardFlavour;

}
else if (cardType == CardTypes.STATUS)
{
cardName = cardData.cardName;
cardDescription = cardData.cardDescription;
cardFlavour = cardData.cardFlavour;

}
else if ( cardType == CardTypes.PASSIVE)
{
cardName = cardData.cardName;
cardDescription = cardData.cardDescription;
cardFlavour = cardData.cardFlavour;

}

Then the issue becomes when generating the card.
I think this would be handled by checking the list of cardsInDeck and calling for the prefab based on cardType.
Understanding that I think this is how I would check the type
private void GenerateCards()
{
foreach (Card card in avaliableCardsInDeck)
{
if (cardType = Plant)
{
CardController newCard = Instantiate(PlantCardPrefab, player1Hand);
newCard.transform.localPosition = Vector3.zero;
newCard.Initialize(card,0);
}
}

IDK. This coding thing is hard aye. Hahaha

Any idea’s, pointers? How should I go about this?

Thanks

Please use code tags, your whole post adjusted:

Hi all,
Let me preface with I have an advanced beginners knowledge of coding.

I am working on a card game where I would like to have 3 discreet types of cards. Plant, Status and Passive.
I want each card to have its own prefab. I also want to create the cards as Scriptable Objects. The different cards are given a cardType (Plant, Status or Passive) to help with instantiation etc.

I am unsure about the best way to handle this.

Previously I have ran with the idea of having three scripts for the different SO (i.e PlantSO, StatusSO and PassiveSO) then bump into the issue of sending this to the CardController script. Particularly when inheriting information (hope this is the correct term).
i.e

public void Initialize(Card card, int ownerID)
{
   this.card = new Card(card);
   {
      this.card.ownerID = ownerID;
   }
   illustration.sprite = card.illustration;
   cardName.text = card.cardName;
   description.text = card.description;
   days.text = card.days.ToString();
   harvest.text = card.harvest.ToString();
   orignalParent = transform.parent;
}

I have also tried a different method where the three cards are created in one SO and, again, sent to the CardController.
i.e

private void CollectInfoFromCardSO()
{
   if (cardData == null)
   {
      Destroy(this.gameObject);
      return;
   }
   if (cardType == CardTypes.PLANT)
   {
      cardName = cardData.cardName;
      cardDays = cardData.cardDays;
      cardHarvest = cardData.cardHarvest;
      cardFlavour = cardData.cardFlavour;

   }
   else if (cardType == CardTypes.STATUS)
   {
      cardName = cardData.cardName;
      cardDescription = cardData.cardDescription;
      cardFlavour = cardData.cardFlavour;

   }
   else if ( cardType == CardTypes.PASSIVE)
   {
      cardName = cardData.cardName;
      cardDescription = cardData.cardDescription;
      cardFlavour = cardData.cardFlavour;


   }

Then the issue becomes when generating the card.
I think this would be handled by checking the list of cardsInDeck and calling for the prefab based on cardType.
Understanding that I think this is how I would check the type

private void GenerateCards()
{
   foreach (Card card in avaliableCardsInDeck)
   {
      if (cardType = Plant)
      {
         CardController newCard = Instantiate(PlantCardPrefab, player1Hand);
         newCard.transform.localPosition = Vector3.zero;
         newCard.Initialize(card,0);
      }
   }

IDK. This coding thing is hard aye. Hahaha

Any idea’s, pointers? How should I go about this?

Thanks

1 Like

Thank you, new to posting on the Unity Forums.

So, briefly looking at this, I’d say you might want to use inheritance instead of an enum. Then you mention prefabs and scriptable objects. What are you exactly trying to do at this point? I’m guessing you’re trying to convert from classes to MonoBehaviour (prefabs.)

In that case (and other cases) I’d go for inheritance instead of an enum:

// Base card class
public class Card {
   public readonly string Name;

   public Card(string name) {
      Name = name;
   }
}

// Inherited plant card class, add for other card types
public class CardPlant : Card {
   public readonly int Harvest; // I'm not sure what type this is

   public CardPlant(string name, int harvest) : base(name) {
      Harvest = harvest;
   }
}

// Base generic card MonoBehaviour / prefab
public abstract class CardPrefab<T> : MonoBehaviour where T : Card {
   public void SetCard(T card);
}

// Inherited plant card MonoBehaviour / prefab, add for other card types
public class CardPlantPrefab : CardPrefab<CardPlant> {
   public void SetCard(CardPlant card) {
      // Read the plant card information or just save a reference to the actual card
   }
}

// Central MonoBehaviour that points to the prefabs and allow instantiating them, singleton
public class CardPrefabInstantiate : MonoBehaviour {
   public CardPlantPrefab prefabPlant;
   // Add references for other card types

   public void Instantiate(Transform parent, Card card) {
      if ((card is CardPlant cardPlant) && (prefabPlant != null)) {
         CardPlantPrefab instancePlant = Instantiate(prefabPlant, parent);
         instancePlant.SetCard(cardPlant);
      }
      // Add instantiation for other card types
   }
}

There are many ways to do this of course, but I’d probably go for something like this. You can also work around using a generic by checking the card type inside CardPlantPrefab, but I kind of like it this way around.

Sorry, maybe I wasn’t clear on the intention.

My goal is to have the card instantiated based on the card type.
Plant cards instantiated on the plantCardPrefab. Status on status, passive on passive.

My confusion is around the best way to create the scriptable object. Whether I have 3 separate scripts for each card type or all in one script. Either way, I would have the CardManager script inherent the data and execute the instantiation.

As this will be a card game with multiple card types in the deck - the code needs to draw a card, determine it’s card type and then instantiate the correct prefab. Which I think can be done like this.

private void GenerateCards()
{
   foreach (Card card in avaliableCardsInDeck)
   {
      if (cardType = Plant)
      {
         CardController newCard = Instantiate(PlantCardPrefab, player1Hand);
         newCard.transform.localPosition = Vector3.zero;
         newCard.Initialize(card,0);
      }
   }

My confusion is around - what is the best way to feed three different “cards” into the manager to then “generate” them in the game.

Ok, well, I think I’ve shown a setup for this. I would use inheritance for a part of it to make things easy to expand. I also always like to have a specific MonoBehaviour in the root of a prefab to communicate with after instantiating. I prefer that to just using a random GameObject as prefab root. (I my example that is an implementation of CardPrefab, for example CardPrefabPlant. After making an instance of this prefab it gets sent the CardPlant it was instantiated for.)

I think the confusion is with your terminology of “scriptable object”. In Unity this is a separate class next to the MonoBehaviour. This was why I was unsure whether you were instantiating a MonoBehaviour or working with a ScriptableObject.

There’s really no “best way” … it will be totally dependent on how your brain has modeled the problem of cards and your game behavior, as well as obviously the game behavior itself. Creating the card game “War” is going to be completely different than Magic: The Gathering or Pokemon or Hearthstone, obviously.

I suggest you set your project gently aside and plunge headlong into some basic card tutorials from Youtube. When you do these tutorials, do them to understand WHY each part is done the way it is, and to understand if that function is relevant to your problem or not.

Ask yourself for each part: is this just for presentation of the card? Is this game logic? Is this user interaction? etc. See Step #2 below especially.

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!

Thanks for the advice, project is on hold for now while I learn about more specifics in unity and c#.
What I have decided - which is directing my learning - is to build a library and have each card as its on class with appropriate properties (I think I have the terminology correct here).