Properly Inherit Classes

Hi, I am a bit confused on how to properly inherit a class when Monobehavior is involved. I am trying to make a simple 2d blackjack game. I have a Cards class that is being inherited by a Decks class. I have both scripts attached to an empty objects as I will be trying to dynamically instantiate 52 cards from a prefab, and reassign the sprite to each once individually (and eventually control each separately). I would just like to know the Unity way to do this, for now I’m trying to instantiate the cards through Deck::Start() but unfortunately i cant use multiple inheritance in C# which is confusing me. I’m also getting an error about instantiating a null object and I’m trying to see where I went wrong there.

I can tell using the “new Card()” is messing it up based on the warnings

Attempting to make Deck inherit Monobehavior and making Card be a standalone class with public access varibles creates the problem of assigning the cardPrefab through the editor.

Any tips would be awesome, thanks.

EDIT: I also just realized ill probably be needing to move the Instantiate line after changing the sprites and etc, whoops

Here are my classes so far:
Deck.css

public class Deck : Card
{

    private List<Card> deck;
    public Transform deckTransform; //for deck position on table
    
 
    void Start()
    {

        deck = new List<Card>() {};  //initialize deck
        //spades
        deck.Add(new Card("ace", "spades", 11)); deck.Add(new Card("2", "spades", 2)); deck.Add(new Card("3", "spades", 3)); deck.Add(new Card("4", "spades", 4)); 
//(etc.... for all cards)




        foreach (var card in deck)
        {
            Instantiate(card.cardPrefab, deckTransform.position, Quaternion.identity); //instantiate game object
        card.cardSpriteR = card.cardPrefab.GetComponent<SpriteRenderer>(); // get Sprite Renderer Component
        card.cardSprite = Resources.Load<Sprite>("Images/Cards/" + card.spriteStr); //assign image to sprite var
        card.cardSpriteR.sprite = card.cardSprite; //set the card's sprite
         //create all cards, pass in the "Deck position"
        }

    }

Card.css

public class Card : MonoBehaviour
{

    public SpriteRenderer cardSpriteR { get; set; }
    public Sprite cardSprite { get; set; }
    public int value { get; set; }
    public string suit{ get; set; }
    public string sValue { get; set; } //card value as a string (for Ace, Jack, etc)
    public string spriteStr { get; set; } //sprite string becomes (sValue + "_of_" +  suit)

    //Public 
    public GameObject cardPrefab; 
    public Transform pCardsTransform; // this is going to be part of a different class for player card pos
    

    public Card(string sVal, string su, int v) {
        value = v;
        suit = su;
        sValue = sVal;
        spriteStr = sValue + "_of_" + suit;
    }

    public Card() { //should never run default constructor 
        value = 0;
        suit = "Doom";
        sValue = "Joker";
        spriteStr = sValue +"_of_" + suit;
    }

}

First of all, making your Deck class inherit from Card does not make sense. A deck is not a card, it’s a collection of cards.

Then, as indicated by Unity, instantiating monobehaviour can’t be done using a constructor.

I suggest you a complete rewrite of your system. You may feel worried at first, but I believe it will be easier to use in the long run. Separating the data from the UI will make the future changes easier.

I’ve commented the code so that you will know how to use and what to do with the scripts. But don’t hesitate to comment and ask questions if something is not clear.


Card.cs

Manage the data about cards

using UnityEngine;

public enum Suit
{
    Diamonds,
    Clovers,
    Hearts,
    Spades
}

// Create each card, one by one
// by clicking on "Assets / Create / ScriptableObjects / Card
// And fill the `DecksManager.Decks` array by dragging & dropping the scriptable objects in the inspector
[CreateAssetMenu( fileName = "Card", menuName = "ScriptableObjects/Card" )]
public class Card : ScriptableObject
{
    [SerializeField] private int id;
    [SerializeField] private Sprite sprite; // Drag & drop the sprite. It does not need to be in the `Resources` folder
    [SerializeField, Range(1,13)] private int value = 1;
    [SerializeField] private Suit suit;

    public int ID
    {
        get { return id; }
        private set { id = value; }
    }

    public Sprite Sprite
    {
        get { return sprite; }
        private set { sprite = value; }
    }

    public int Value
    {
        get { return value; }
        private set
        {
            if ( value <= 0 || value >= 14 )
                throw new System.ArgumentException( "Value must be between 1 and 13" );
            this.value = value;
        }
    }

    public Suit Suit
    {
        get { return suit; }
        private set { suit = value; }
    }

    public string SValue
    {
        get
        {
            switch ( Value )
            {
                case 11: return "Jack";
                case 12: return "Queen";
                case 13: return "King";
                case 1: return "Ace";
                default: return Value.ToString();
            }
        }
    }

    public Card( int id, int value, Suit suit, Sprite sprite )
    {
        ID     = id;
        Value  = value;
        Suit   = suit;
        Sprite = sprite;
    }
}

Deck.cs

Manages the data about decks

using System.Collections.Generic;
using UnityEngine;

[System.Serializable]
public class Deck
{
    [SerializeField] private List<Card> cards = new List<Card>();

    public int CardsCount { get { return cards.Count; } }

    public Card this[int index] { get { return cards[index]; } }
}

CardsDatabase

Manages a collection of cards in the editor

This scriptableObject is not used anywhere else in the code, but you may want it in the future to have a complete list of your cards.

using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif

// Create this database by clicking on "Assets / Create / ScriptableObjects / CardsDatabase
// And click on the gear in the top-right corner of the inspector, then `FillDatabase`
// the database will be filled automatically with all the cards in your project
[CreateAssetMenu(fileName = "CardsDatabase", menuName = "ScriptableObjects/CardsDatabase")]
public class CardsDatabase : ScriptableObject
{
    [SerializeField] private List<Card> cards;

    public int CardsCount { get { return cards.Count; } }

    public Card this[int index] { get { return cards[index]; } }

#if UNITY_EDITOR

    [ContextMenu("FillDatabase")]
    public void FillDatabase()
    {
        cards.Clear();
        string[] guids = AssetDatabase.FindAssets( "t:Card" );
        for ( int i = 0 ; i < guids.Length ; i++ )
        {
            string assetPath = AssetDatabase.GUIDToAssetPath( guids *);*

Card card = AssetDatabase.LoadAssetAtPath( assetPath );
if ( card != null )
cards.Add( card );
}
}
#endif
}
## CardRenderer.cs
Manages the visual representation of the card data
You have to create a prefab which will contain all the needed components to visually manage the card : SpriteRenderer, Animation, …
Once the prefab is created and this component attached to it, drag & drop in in the cardPrefab field of the DecksManager object in your scene
using UnityEngine;

public class CardRenderer : MonoBehaviour
{
// Drag & drop the spriteRenderer of the prefab
// this component is attached to
[SerializeField] SpriteRenderer spriteRenderer;

public void Setup( Card card )
{
spriteRenderer.sprite = card.Sprite;
}
}
## DeckRenderer.cs
Manages the visual representation of the deck data
You have to create a prefab which will contain all the needed components to visually manage the deck. Maybe nothing for now)
Once the prefab is created and this component attached to it, drag & drop in in the deckPrefab field of the DecksManager object in your scene
using System.Collections.Generic;
using UnityEngine;

public class DeckRenderer : MonoBehaviour
{
private List CardRenderers = new List();

public void Add( CardRenderer card )
{
CardRenderers.Add( card );
}
}
## DecksManager.cs
Responsible for the “logic” to instantiate and manage the decks
This component must be attached to an object in your scene, it can be a simple empty. Then, fill the inspector with the needed data.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DecksManager : MonoBehaviour
{
// Fill the inspector with the decks
[SerializeField] private List Decks;

// Create a prefab with a CardRenderer component
// and all the needed components to visually manage a card
[SerializeField] private CardRenderer cardPrefab;

// Create a prefab with a DeckRenderer component
// and all the needed components to visually manage a deck
[SerializeField] private DeckRenderer deckPrefab;

private List deckRenderers = new List();

void Start()
{
for ( int deckIndex = 0 ; deckIndex < Decks.Count ; deckIndex++ )
{
deckRenderers.Add( CreateDeckRenderer( Decks[deckIndex], deckIndex, transform ) );
}
}

private DeckRenderer CreateDeckRenderer( Deck deck, int deckIndex, Transform parent )
{
// Create the deckRenderer responsible for displaying a deck
DeckRenderer deckRenderer = Instantiate( deckPrefab, parent );
deckRenderer.name = string.Format( “DeckRenderer ({0})”, deckIndex );

// Instantiate all the card renderers for the deck
for ( int cardIndex = 0 ; cardIndex < deck.CardsCount ; cardIndex++ )
{
CardRenderer cardRenderer = CreateCardRenderer( deck[cardIndex], cardIndex, deckRenderer.transform );
deckRenderer.Add( cardRenderer );
}

return deckRenderer;
}

private CardRenderer CreateCardRenderer( Card card, int cardIndex, Transform parent )
{
CardRenderer cardRenderer = Instantiate( cardPrefab, parent );
cardRenderer.Setup( card );

return cardRenderer;
}
}