Duel Masters

Personal Project

Platform: PC

Engine: Unity

Team Size: 1

Role: Lead Programmer

Roles and Responsibilities

  • Established a modular prefab structure to enhance runtime efficiency, seamlessly integrating with an extensible ScriptableObject framework. Implemented Custom Editors to simplify data editing, testing, and storage by designers.
  • Designed project architecture adhering to SOLID principles, emphasizing various design patterns such as Command, Observer, Singleton, and State for a clean and optimized system.
  • Implemented Editor scripting to optimize the authoring pipeline for encoding card data, enhancing efficiency and workflow automation.

Overview

The Duel Masters Trading Card Game is a two-player or two vs. two team collectible card game (CCG) jointly developed by Wizards of the Coast and Takara Tomy (itself an affiliate of Hasbro, which owns WotC). The card game is part of the Duel Masters franchise.

For me, the significance of this game transcends mere entertainment. It served as the catalyst for forging enduring bonds with my closest circle of friends during our formative school years, fostering friendships that have withstood the test of time. Thus, when contemplating my inaugural personal project, I felt compelled to pay homage to this cherished aspect of my past.

Much akin to the digital adaptation of Magic: The Gathering through MTG: Arena, my endeavor aims to translate the essence of Duel Masters into the digital realm. Through this undertaking, I seek to capture the essence and nostalgia of the original card game while introducing it to a new generation of enthusiasts, ensuring its legacy endures in the digital era.

Assets Used

Card Prefab Structure

The building blocks of a TCG are its cards, and a TCG is only as good as the cards which make it. Consequently, it was only fitting that this is where I initiated the implementation of my project.

Before diving into the card structure, I got the art for all the Duelmaster cards from Pranjal Bisht. Next, in Krita, I picked cards I liked, mainly considering their resolution. I edited them to make frames for the cards, making it easy to overlay them onto the actual card art in my program.

Card Prefab Structure 1
Card Prefab Structure 2

I aimed for the card art only, excluding any text, to create a modular system for setting up each card. This involves utilizing ScriptableObjects with the relevant data embedded within them, upon which I expand further below.

Card Prefab Structure 3 Card Prefab Structure 4 Card Prefab Structure 5

The card functions as a canvas in the world space, positioned above a scaled-down cube. I wanted a 2.5D design to add some depth to my project aesthetic, influencing my choice of this approach. The hierarchy begins with the 'Artwork Image,' representing the card image sourced from the database. Above it is the corresponding 'Frame Image' based on the card type. Additionally, there are UI Text objects displaying essential card information. The Creature Layout includes extra text specific to creature cards.

Card Prefab Structure 6 Card Prefab Structure 7

The Mana and Battle Cards serve as simplified renditions of the Card Layouts, specifically tailored for the Mana and Battle Zones, respectively.

Card Prefab Structure 8 Card Prefab Structure 9 Card Prefab Structure 10

The final card representations seamlessly integrate all distinct card layouts into a cohesive whole. This setup allows for effortless editing of individual layouts as needed, eliminating concerns about unintentionally disrupting other functionalities. The incorporation of prefab variants serves to underscore and enhance this design choice. Hence, this method establishes a decoupled prefab configuration following the component pattern, fostering an optimized modular structure and a more streamlined workflow.

Card Prefab Structure 11 Card Prefab Structure 12 Card Prefab Structure 13 Card Prefab Structure 14

The keyword prefabs further emphasise the modular nested prefab structure for the card representations.

Serializing Card Data

My original implementation of this section of the project was much more straightforward, essentially comprising a few monolithic classes with large sets of member variables to store every type of information that may be required, ever.

However, after setting that up, I found myself unsatisfied with the architecture of my project. As a self-taught solo developer, I struggled for a decent amount of time, wondering how I could make the entire system and the processes within it cleaner.

That is when I came across, in my research, a YouTube video titled "Clean Code - Uncle Bob." I watched the lecture in one sitting, and although by the end my brain was swimming with all the information, my heart felt reinvigorated. Robert Cecil Martin had introduced me to the world of the SOLID principles and the beauty of clean code architectures.

And off I went into a huge refactoring of my entire Card Data pipeline, which broke a bunch of code in my actual gameplay processing systems that I have not yet gotten around to cleaning. Someday I hope to get back to this project in earnest, but until then, the tools I picked up through this refactoring attempt will keep helping me in making systems better, and dare I say, more beautiful.

Serializing Card Data 1

The 'CardData' is a ScriptableObject and the basis of the entire card serialization pipeline. Apart from the members that store the information regarding the card itself, the most important element of this class is the list of 'EffectData' objects, which store the data specific to each effect that a card may have.

Serializing Card Data 2

The EffectData itself is a simple class with two Booleans that further define the way an effect might work. The core functionality is described within the 'EffectCondition' and 'EffectFunctionality' classes.

Serializing Card Data 3

The 'EffectCondition' class is responsible for serializing any conditions that need to be validated before the relevant effect takes place.

  • The 'EffectConditionType' enum stores the different types of conditions available.
  • The 'EffectConditionParameter' is an abstract class that describes any additional parameters that may need to be checked (such as whether a given creature is “tapped”). Making it an abstract class allows the addition of any new types of conditions that may be required in the future thus allowing for easy extensibility.
  • The 'EffectTargetingCriterion' class holds data regarding the targeting criteria, such as the numeric description of the selection (e.g., at least two, equal to the number of fire cards in the graveyard, etc.).
  • Serializing Card Data 4
  • The 'EffectTargetingCondition' class stores data pertinent to the condition, such as whether to check for a specific card or civilization.
Serializing Card Data 5

The 'EffectFunctionality' class is responsible for the serializing of the pertinent data regarding the working of the effect itself.

  • The 'EffectFunctionalityType' enum stores the different types of effects available.
  • The 'EffectFunctionalityParameter' is an abstract class similar to the EffectConditionParameter class shown above. A myriad number of parameters can then be created by simply extending the class and adding the relevant type to the EffectFunctionalityType enum. For example,
    • The DestroyFuncParam class describes the parameters associated to an effect that destroys a particular card on the field.
    • Serializing Card Data 6
    • The PowerFuncParam class describes the parameters associated to adding or subtracting the power of a Creature card on the field. It inherits from MultipliableFuncParam, which is an abstract class inheriting from EffectFunctionalityParameter that adds information for any functionality that may be required to be multiplied according to some other value, such as Bolshack Dragon(link).
    • Serializing Card Data 7
  • The 'CardTargetType' is an enum that describes whether the effect targets the card itself, or another card (or cards) instead.
  • The 'PlayerTargetsHolder' simply describes which player is the one choosing the targets and which player the chosen targets must belong to.

Then for both of these classes, the 'ConnectorType' enum denotes how the effect connects (either "and" or "or") to any sub-conditions or sub-functionalities the effect may have.

The '_subCondition' and '_subFunctionality' variables, along with the connector described above, allow for a nested effect structure designed to enable the structuring of complex effect behaviors using the same basic building blocks.

Hence, this approach enables a flexible and highly modular setup that is easy to extend. The hierarchical setup also means that classes at the top do not need to worry about the details regarding the classes at the bottom. Adding any new Condition or Functionality to the system simply requires adding the type to the appropriate enum and establishing a new class inheriting from the relevant abstract parent. There is no need to modify any code elsewhere.

Custom Editor for Card Data

One could possess the best-designed architecture for a serialization pipeline in the world, but all of that becomes irrelevant if entering the data is cumbersome. As such, the next logical progression for me was to establish a custom editor, enabling the setup of card effects through an intuitive interface.

To showcase this, I will use the following two cards as examples: Aqua Hulcus, which is a Water Creature, and Aura Blast, a Nature Spell.

Custom Editor for Card Data 1

The interface for setting up this effect is as follows,

Custom Editor for Card Data 2

And this is how it looks once you're finished editing,

Custom Editor for Card Data 3 Custom Editor for Card Data 4

The interface for setting up this effect is as follows,

Custom Editor for Card Data 5

And this is how it looks once you're finished editing,

Custom Editor for Card Data 6

While I was proud of the serialization pipeline and the editor I had constructed at the time, I now recognize that the process of setting up effects is not as intuitive as I had hoped. If given the opportunity to implement it again, I would leverage all the knowledge I've gained from this project and my subsequent work to create a system that is not only more decoupled and extensible in scope, but also features a cleaner and more intuitive editing interface.

Gameplay Logic Systems

There are two main types of manager classes pertinent to the logical processing of the system: the Game State Managers and the Zone Managers. Additionally, there is a PlayerManager that does not fit within either of these categories.

The Game State Managers, as implied by their name, are responsible for processing the central game states.

  • The GameManager is responsible for advancing through each step of the game, managing player turns, assigning the player managers their respective decks, and starting the initialization process. It is essentially implemented as a state machine, where each game step represents a state.
  • The GameStep is an abstract class outlining the functionality that each step should possess. Following the classic state machine pattern, the GameStep includes Start, Process, and Finish functions, enabling flexible processing of tasks required during any given step.
  • The CardEffectsManager is responsible for processing the functionality of various card effects.
  • The BattleManager is responsible for handling the logic whenever a Creature attacks another creature, player, or shield.

The ZoneManagers comprise classes such as the BattleZoneManager, DeckManager, HandManager, etc., which are responsible for the behaviors pertaining to their respective zones. They assume complete ownership of the zones under their jurisdiction. For instance, if a card effect involves searching for a card in the deck and then shuffling the deck, the CardEffectsManager will retrieve the card from the DeckManager's list of managed cards, prompt the relevant PlayerManager to transfer the card to its corresponding HandManager for processing its addition to the hand, and subsequently, the shuffling logic will be executed in the DeckManager.

The PlayerManager holds references to each of the ZoneManagers associated with its respective player. It is also responsible for delegating the movements of cards to and from each of the zones.

© Copyright Jasfiq Rahman 2024

Icons by Icons8 and Flaticon.