Nagging general architecture question

I’m designing a card game, new to Unity, but have been developing c# business apps for some time. I always come back to the same nagging architecture problem:

Where is an appropriate place to store common data, and by what mechanism does that instance get shared among the scripts?

For a concrete example, I have the following scripts are attached to the same game object with a sprite renderer. Now I want to add a 3rd script to show card details on mouse-over but am running into these issues.

All 3 scripts need common card-related data but I never know where to store that data. In a business app it would go in a model/poco/dto class. But in Uniity, should it go in another MonoBehavior attached to the same game object or a scriptable object? It could go in a simple model/poco class but then how does that instance get shared between all the scripts?

    public class CardManager : MonoBehaviour
    {
        // it feels like these properties should go somewhere else
        public CardValue Value { get; private set; }
        public CardSuit Suit { get; private set; }
        public bool IsFlippable { get; private set; }
        public bool IsFaceUp { get; private set; }
        public bool IsDraggable { get; set; } = false;
        public bool IsInspectable { get; set; } = false;

        public int SortingOrder
        {
            get => _spriteRenderer.sortingOrder;
            set => _spriteRenderer.sortingOrder = value;
        }

        private SpriteRenderer _spriteRenderer;
        private CardFlip _cardFlip;

        private void Awake()
        {
            _spriteRenderer = GetComponent<SpriteRenderer>();
            _cardFlip = GetComponent<CardFlip>();
        }

        public IEnumerator Flip()
        {
            if (IsFlippable)
                yield return _cardFlip.Flip();

            yield return null;
        }
    }
    public class CardFlip : MonoBehaviour
    {
        public float TimeToFlip = 1f;

        private Sprite _faceImage;
        private Sprite _backImage;
        private SpriteRenderer _spriteRenderer;
        private bool _isFlipping;

        public void Initialize(Sprite faceImage, Sprite backImage)
        {
            _faceImage = faceImage;
            _backImage = backImage;

            _spriteRenderer.sprite = /* need reference to IsFaceUp from the other script */ ? _faceImage : _backImage;
        }

        void Awake()
        {
            _spriteRenderer = GetComponent<SpriteRenderer>();
        }

        void Update()
        {
            if (_isFlipping)
            {
                 // continue flip animation
            }
        }

        public IEnumerator Flip()
        {
            if (_isFlipping)
                yield break;

            _isFlipping = true;

            // begin flip animation

            yield return null;
        }
    }

If your script requires something from another script, you can always use the RequireComponent attribute to make sure that whatever object your script is on has that other component. Then, it should theoretically always allow you to just GetComponent it.

Personally, I am a fan of using ScriptableObjects where I can, but this instance, where you’re only sharing the data between components on the same object, seems a bit extraneous. Maybe one that just includes the Card Value, Suit, and Images, but the others seem related to the object itself.

Perhaps you could even just create a “data” component on your object. Much like how people often use ScriptableObjects as “data sacks”, you could create a MonoBehavior which only contains data, accessed by just requiring and getting the component in the beginning. I’m pretty sure it would also be performant, as when you don’t have any of the Start or Update methods, Unity treats it differently. This would also allow you to have a CardFlip without having a CardManager, I imagine there’s some instance where you would want that.

I’ve never really seen anyone else mention this kind of component before though, so not sure if it’s taboo or not.

Your suggestions are exactly what I’m looking for. I started down the path of a common data class as a “data sack”, to use your term, and think that will work well. ScriptableObjects have been great for sharing in a wider scope, but like you mentioned, might be more than what I might need here.

Thanks for the quick reply!

1 Like