0
\$\begingroup\$

I have a couple questions involving software architecture and best practices in Unity. Although I have some programming knowledge, I am just starting with Unity and C#, and I would like some input from people with more experience than myself so I can learn Unity the right way and do not let bad habits take root.

In a nutshell: I am trying to put together a simple card drawing mechanic, where I would define a card layout (kind of like a class) and then initialise two-three at a time with data pulled randomly from a data source. Cards would have standard attributes like, cost, image, name, etc.

My first question is: what would be the optimal way to define the card template? Off the top of my head, I would say that it should be a prefab. But I am aware that there are other options as well, such as scriptable objects.

My other, related question, is: what would be a good way to store card data with future expandability in my mind. If my idea ever comes to fruition, I would like to release new card packs periodically, expanding the main card stock bit-by-bit. So, I am guessing, storing it in a simple JSON is probably not the best choice from a software architecture point of view.

Thank you for your answers!

\$\endgroup\$
1
  • \$\begingroup\$ It sounds like you have workable ideas in mind already (prefabs, JSON). What specific problem do you observe with this approach that we can help you solve? Or what aspect do you find unsatisfactory that we can help you improve? There's rarely one objectively "optimal" way to do anything in games, just different trade-offs depending on what matters most for your use case and preferred style of working. \$\endgroup\$
    – DMGregory
    Apr 27, 2021 at 14:10

1 Answer 1

2
\$\begingroup\$

Your guesses about how to plan your game look good and give you room for further improvement too. Prefabs make GameObject management easier, and let you edit once and apply changes everywhere in your project. The JSON format is easy to understand and debug, and enough for your starting builds; it isn't either good or bad, it just depends on what you need to do.

This means all answers here are going to be subjective and depend on others' thinking and approach to coding, then you may want to be more specific about what you need to solve.

My two pennies worth:

Scriptable objects are a good way to set up instances sharing the same codebase but behaving differently. You can easily change public variables through the inspector, then creating the same prefab with different starting values. This approach can be combined with prefab variants so that Unity recognises these as the same GameObject type (even if you have 10 different variants).

Card info can be stored as JSON, plain text, a dictionary... Any solution is suitable as long as it works - until you want to optimise and improve your workflow. You'd want to store unique information only, such as cards name, cost, description; whereas features like spawn, mulligan, and special effects are scripted and can be referenced with simple labels (different cards may have the same effect, even with different parameters), adding/removing them at runtime.


EDIT: game state and animations

Additionally, I suppose your game needs animations (e.g. drawing cards, giving visual feedback on attacks/effects...) that you need to separate from the game logic:

  • The internal game state should only step forward based on individual players' actions and in-game chained effects;
  • Animations should purely and simply animate stuff.

This means that the animation component should never tell the game logic to step forward so that it can display the next animation. It's the other way round: the game logic tells the animation component that something happened and that a certain animation must be played; once the animation has finished, the logic can move forward and update the game state once again, triggering a new animation, and so on.

How to do that? Unity features Coroutines, which you can treat like functions that give you control over their execution time rather than returning right after completion. Therefore, you can tell a coroutine to do something and wait for a certain amount of seconds before moving on. And, you can also nest coroutines:

  • Coroutine1 starts;
  • Coroutine1 calls Coroutine2 and pauses execution;
    • Coroutine2 starts;
    • Coroutine2 does something;
    • Coroutine2 returns;
  • Coroutine1 resumes execution.

You can now replace Coroutine1 with your game logic and Coroutine2 with any routine animating cards and effects in your current game. All animations will end before moving on with the game, BUT it's the game logic that waits for animation completion and not the animation itself to step the game state forward.

\$\endgroup\$
2
  • \$\begingroup\$ Thank you @liggiorgio, it was very informative! \$\endgroup\$ Apr 28, 2021 at 9:01
  • \$\begingroup\$ I edited my answer to include something more about how to deal with the game state and animations in your game since that's what would probably come next after you make your cards work. \$\endgroup\$
    – liggiorgio
    Apr 28, 2021 at 9:49

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .