DeckStacker v1.0.4
A card game engine for Unity games
Loading...
Searching...
No Matches
How does it work?

This page does a slightly deeper dive into how DeckStacker works. I'm trying to be illuminating, here, but not dive toooo deep into everything. You should be able to find more detail into any of these subjects in the API documentation pages.

How do you spawn cards?

API Links
DeckStacker.DSCardPool

Card creation is managed through Card Pools (DeckStacker.DSCardPool GameObjects).

Card Pools are provided in all the sample scenes (including the TemplateScene), and are the default way of creating card objects in the scene, cutting down on the amount of instantiation and destruction of card GameObjects.

Let's go through a brief review of the DSCardPool component that you'll need to hook up in your scene.

DSCardPool

API Links
DeckStacker.DSCardArt
DeckStacker.DSCardPlacementInDeck
DeckStacker.DSCardPool
DeckStacker.DSStack

DSCardPool

Let's review each of the exposed properties, one by one:

  • Card Prefab: The card prefab that the pool instantiates and manages copies of
    • Notice that the card prefab will need a DSCard component attached.
    • The DeckStacker Card prefab is the recommended place to start making your own cards. Duplicate the prefab and modify as you need.
    • If you need a breakdown of how DeckStacker card prefabs are designed, please refer to the Card Prefab Setup page.
    • If you have several card prefabs to manage, then make a different pool per card prefab.
  • Pool Parent: A UGUI GameObject that will be the home of any card pool objects
    • Cards in the pool will be deactivated and hidden when not in use, so it doesn't really matter where this Pool Parent object is.
  • Pool Card Sprite Atlas: Used by the DSCardArt component on card prefabs in this pool
    • This is how the DSCardArt component of each card has access to the card's intended SpriteAtlas for updating its card art.
  • Card Pool List: A C# List of card objects currently in this card pool, ready for use
    • If this list ever runs out (ie When you are trying to pull a card from the pool and there are insufficient cards in there), new cards will be created and added to the pool to satisfy new requests for cards.
  • Build Pool Number At Start: When this object calls its Start method, it will automatically create this many card copies for the pool

Code-wise, do the following:

  • Get cards from the pool, using the DeckStacker.DSCardPool.SpawnCard method(s)
    • Important note: This method is preferred over the GetCardFromPool method, since it will do some extra work for you.
    • There are 3 overloads for SpawnCard, depending on where in a DSStack you would like to spawn the card.
  • Return cards to the pool, using the DeckStacker.DSCardPool.ReturnCardToPool method
    • To make things easier, you can use the DSCard's ReturnCardToPool() method to make the card find its way back home, rather than calling on the DSCardPool for it.

How a card is grabbed from the pool is an important moment to consider in your game code. DeckStacker manages the card pool, but grabbing a card from the pool with just DeckStacker code will result in a simple copy of the card prefab. Obviously, we want to do so much more, and a card's state will need to be updated as the card is grabbed from the pool. This is where we get into what a card is to your game, and what data needs to be updated as the card is leaving the pool.

Let's examine card pool management from the standpoint of "traditional" playing cards. DeckStacker provides some example code for these kinds of cards.

How DSTraditionalCardSpawner uses DSCardPools

API Links
DeckStacker.DSCardPool
DeckStacker.DSTraditionalCardSpawner

The basic idea here is pretty straightforward:

  1. We need a card pool to efficiently create and dismiss cards.
  2. Cards being grabbed from the pool need to reflect card data and visuals.

Number 1 on the list is handled by the DSCardPool script, attached to a GameObject in the scene. It does the following:

  • Manages a pool of card prefabs
  • These prefabs can be generic or specific
  • If you have several card types in your game, there can be several Card Pools to manage the different types
DeckStacker Cards

So, in the case of traditional cards, the DeckStacker Card prefab in the Prefabs folder is setup to use traditional card art and data. So we've got a card prefab setup for our needs, here. For a breakdown of the card prefab please refer to the Card Prefab Setup.

Not only does DeckStacker provide traditional card prefabs (complete with bare-bones traditional card art), but DSTraditionalCardSpawner is a static script designed to do the data setup for a traditional card, as well.

Let's take a closer look at the DeckStacker.DSTraditionalCardSpawner script and pick out some interesting methods to help us make some cards.

MakeTraditionalCard and MakeTraditionalJoker methods

Here is the code for DeckStacker.DSTraditionalCardSpawner.MakeTraditionalCard:

// Returns a traditional card, in the DeckStacker system, grabbed from the specified card pool.
public static DSCard MakeTraditionalCard(DSCardPool cardPool_, DSStack targetStack_, int rank_, DSSuit suit_, int iterationNum_ = 0)
{
DSCard newCard_ = cardPool_.SpawnCard(targetStack_);
newCard_.cardData.suit = suit_;
newCard_.cardData.rank = rank_;
// cardID serves to id a card's info or type, whereas the object name of the card will serve to be the unique identifier for the card.
newCard_.cardID = GetTraditionalRankName(newCard_.cardData.rank) + "_of_" + newCard_.cardData.suit.ToString().ToLower();
newCard_.name = newCard_.cardID + "_" + iterationNum_.ToString();
if (newCard_.cardArt != null)
{
newCard_.cardArt.UpdateMarkings();
}
return newCard_;
}

In more plain language, this method does the following:

  1. Spawns a card from a given DSCardPool, using the DeckStacker.DSCardPool.SpawnCard method
  2. Updates the card's rank and suit data to the given parameters
  3. Updates card IDs to help differentiate the card from other cards in the scene (including cards with same data values as this one)
  4. Triggers an update on card art
  5. The method returns a reference to the card for you to capture and track, if desired

The MakeTraditionalJoker method is very similar to MakeTraditionalCard, but with slightly different data handling of the card's values to account for the fact that Jokers are usually the odd-ball of a card deck, and can mean very different things between games. All the other cards have a standardized value progression.

MakeTraditionalDeck method

That's all well and good, but we typically think of traditional card decks as having an established set of cards.

  • 4 suits
  • 10 number cards and 4 face cards per suit
  • Optional Jokers (typically 2 of them)

So, DeckStacker.DSTraditionalCardSpawner provides a method that sets up a typical card deck with this method:

public static List<DSCard> MakeTraditionalDeck(DSCardPool cardPool_, DSStack targetStack_, bool jokers_, int deckID_ = 0)

This method returns a List<DSCard> of a traditional deck of cards, given the following:

  1. cardPool: The DSCardPool that is managing the card prefabs (must contain card prefabs setup for this style of card)
  2. targetStack: A DSStack that serves as the "spawn point" for the cards
  3. jokers: A boolean for whether Jokers should be in the deck (you'll get 2 if true)
  4. deckID: Optional. This will be a numeric suffix added to the GameObject names of all the cards in order to append a unique identifier on each card
    • This is in the event that you want to make multiple decks. Each card should have a unique name to help find them, later.

Summary

API Links
DeckStacker.DSCard
DeckStacker.DSCardPool
DeckStacker.DSTraditionalCardSpawner

That's a lot of information, so let's break it down to the key takeaways:

  1. Card spawning is managed by DSCardPools.
  2. Get a card from the DSCardPool, using the SpawnCard method.
  3. When done with a card, you can return it to the DSCardPool, using the DSCard's ReturnCardToPool() method.
  4. Card data is managed separately.
    • After a card has been grabbed from the pool, you'll need code to update its state to reflect what you want the card to be.
  5. If you just want some traditional cards in your game, DeckStacker provides some default setup (prefabs and code) to get you started.

Hopefully this clears up an essential part of DeckStacker for you. As always, study the documentation on each of these classes, carefully.


How do cards move?

API Links
DeckStacker.DSCardMover

Card movement is managed, using the DSCardMover static class. In that script, there are several lists that are being monitored by DSCardMover's update loops, RunUpdate() and RunLate().

These lists are:

  • cardsWaitingToBeDealt
  • cardsInMotion
  • cardsOffset
  • cardsRotating
  • cardsScaling
  • cardsGrabbed

The types of animation that these lists manage can be categorized as:

  • Cards passing between different stacks
  • Cards being moved to specific positions, such as when a "restack" arranges a stack's cards
  • Cards visually offset with position and/or scale
  • Cards being rotated
  • Cards being scaled
  • Cards being "grabbed"

Let's briefly summarize how each of these styles of animation work, and why you would want to use them.

Cards passing between different stacks

API Links
DeckStacker.DSCardMover

This is one of the most common ways a card will move.

When a group of cards has been marked for a "deal" (aka moving from one stack to another), the cards are added to the cardsWaitingToBeDealt list, in DSCardMover.
While in the cardsWaitingToBeDealt list, each card is evaluated by the following criteria:

  1. Cards are evaluated in a queue. Is this the next card in the queue?
  2. If yes, does this card have a "deal delay"?
  3. If yes, decrement its deal delay clock
  4. If the deal delay is depleted, prep the card for a stack transfer, and add it to the cardsInMotion list (more on cardsInMotion, in the next section)

Once the card is out of the cardsWaitingToBeDealt list, and in the cardsInMotion list, the card will be officially added to its destination stack, and the Restack operation will take hold of it, moving it to its proper place in the new stack.

Cards being moved to specific positions, such as when a "restack" arranges a stack's cards

API Links
DeckStacker.DSAction
DeckStacker.DSCard
DeckStacker.DSCardMovementHelper
DeckStacker.DSMoveCardInDirAction
DeckStacker.DSMoveCardToPositionAction

When a card has been added to the cardsInMotion list, the DSCardMover script will lerp the card's position between its starting location (DSCardMovementHelper.startPos) and the new target position (DSCardMovementHelper.endPos).

The most common way this will happen is during a Restack operation for a stack. Restacks are when the stack arranges all of its cards according to its stack type.

DeckStacker also provides DSActions that utilize this behavior, such as the DSMoveCardInDirAction and the DSMoveCardToPositionAction.

If you want more continuous control of a card's position, I would recommend having the card belong to an Undefined stack, or else Restack operations are going to constantly try and move your card(s) to where the stack thinks the card(s) should be.

You can use the DSCardMovementHelper.MoveCard() method to utilize this system, outside of the core DSActions provided in DeckStacker.

Note: This movement is applied to the base Transform of the card (the Transform attached to the same object that has the DSCard component). See Card Prefab Setup for more info.

Cards visually offset with position and/or scale

API Links
DeckStacker.DSCardMovementHelper
DeckStacker.DSCardMover

A card "offset" is when the card maintains its position, but the card visuals are moved or scaled.

When making card games, I often find myself wanting to make small position and/or scale adjustments to a card to add visual emphasis to the card, like when a card is selected by the player. You can use the DSCardMovementHelper.OffsetCard() method to offset a card, which will then add the card to the DSCardMover.cardsOffset list. The DSCardMover script will then iterate through that list, every Update, and move / scale any cards that haven't finished their offset animations.

Note: This movement is applied to the DSCardMovementHelper.offsetParent object in the card prefab. See Card Prefab Setup for more info.

Cards being rotated

API Links
DeckStacker.DSCard
DeckStacker.DSCardMovementHelper
DeckStacker.DSCardMover
DeckStacker.DSRotateCardAction

Similar to the previous sections, describing moving a card object: Cards are rotated by adding them to the DSCardMover.cardsRotating list, and then the DSCardMover script will iterate through the list and lerp the rotations of the DSCard objects that haven't finished rotating to thier endRotation values.

This bit is primarily used for angling cards for Fan stacks, and/or correcting the rotation of cards being passed between stacks of different rotations.
This can also be used to "tap" cards in a Magic The Gathering style action.

If you want to lock the rotation of a card, and prevent the rest of these systems from rotating it (like when a restack happens), you can utlized the DSCardMovementHelper.lockRotation flag, disabling future card rotations until that flag has been reset.

You can use the DSCardMovementHelper.RotateCard() method to utilize this system, or use the DSRotateCardAction.

Note: This rotation is applied to the base Transform of the card object (DSCard object). See Card Prefab Setup for more info.

Cards being scaled

API Links
DeckStacker.DSCard
DeckStacker.DSCardMovementHelper
DeckStacker.DSCardMover
DeckStacker.DSScaleCardAction

Similar to the previous sections, describing moving a card object: Cards are scaled by adding them to the DSCardMover.cardsScaling list, and then the DSCardMover script will iterate through the list and lerp the scales of the DSCard objects that haven't finished scaling to thier endScale values.

You can use the DSCardMovementHelper.ScaleCard() method to utilize this system, or use the DSScaleCardAction.

Note: This scale animation is applied to the base Transform of the card object (DSCard object). See Card Prefab Setup for more info.

Cards being "grabbed"

API Links
DeckStacker.DSCardGrabber
DeckStacker.DSCardMover

This style of movement works slightly different than the other types of movement.

A "grab" is intended to be a player-controlled, dynamic input that will move the card visuals.

Similar to a card "offset", a card grab does not move the base position of a card, but moves a DSCardGrabber.grabParent, instead.

It still uses a list in DSCardMover (cardsGrabbed) where cards that are marked as "grabbed" are then added to this list, and DSCardMover iterates through the list to update positions.

The ideal use cases for card grabs are when a player clicks and drags on a card to signal some kind of intention or action the player wants to take.

Example: A player grabs a card from their hand, dragging it to the center of the screen which then signals that they want to activate the card's abilities.

  • These kinds of card interactions are used in Magic the Gathering: Arena and Slay the Spire.

Note: This grab movement is applied to the DSCardGrabber.grabParent object. See Card Prefab Setup for more info.


How do cards flip?

API Links
DeckStacker.DSCardFlipper

Simply put: Card flipping is a fundamental need for a card game. If you're not flipping cards, then what are you doing with your life? jk

Each card has a DSCardFlipper attached to it that drives the card flipping animation for its card.
Let's examine some of the essential parts of this script to understand how flip animations work...

DSCardFlipper.facing

API Links
DeckStacker.DSCardFacing
DeckStacker.DSCardFlipper

In DSCardFlipper, there is a DSCardFacing enum saved that tells the card which side it should be showing.

When the DSCardFacing value doesn't match the object's state (ie When the card is showing it's back face, when the enum is DSCardFacing.Up), the appropriate card flip animation starts playing, bringing the card's state and the current DSCardFacing enum into alignment.

DSCardFlipper.rotationKey

API Links
DeckStacker.DSCard
DeckStacker.DSCardFlipper
DeckStacker.DSCardMovementSettingsSO
DeckStacker.DSCardPool

This string is used as a key for searching what rotation lerp-curves the flip animation should use.
These curves are saved in the DSCardMovementSettingsSO Scriptable Object that you'll need for your project.

Why AnimationCurves instead of Animator Controllers for each card?

  • That would be a LOT of AnimatorControllers in the scene! Not great for optimization...
  • Also, there is a very large hazard of the flip animation being interrupted by other events, or the card getting flipped back the other direction in the middle of a flip.
  • Dynamic animation management is way better to avoid the issues present in the above points.

Default lerp-curves are provided in the DeckStacker Movement Settings asset:

DeckStacker/DSCore/Resources/GlobalData/DeckStacker Movement Settings.asset

This key is saved on a prefab-by-prefab basis, and is accessed through the DSCard.cardPool property for consistency. (See DSCardPool for more info)

Why 3 Curves?

API Links
DeckStacker.DSCardFlipper

The Default RotationCurves Setting

Simply rotating the Y axis to achieve a card flip is pretty boring, so why not put a little more PAZAZZ on it!?

In the above image, you can see that the flip animation is broken up into individual axes (X, Y, Z), and that the X and Z curves are somewhat symetrical while the Y starts at 0 and ends at 1. This is because...

  • The Y axis controls which side of the card is displayed
  • The X and Z axis are here to just make the animation look a little more natural
    • This is especially important if you are using an orthographic camera, since you will not have any perspective skewing the card.

Obviously, your mileage may vary, and you may want a different rotation setup for X and Z axis curves to fit your aesthetic.
Just be mindful that the X and Z curves are aesthetic, while the Y curve has a function.

More info on card flipping can be found on the DSCardFlipper page.


How do cards manage their art?

Default Card Art Setup

Images

API Links
DeckStacker.DSCardArt
DeckStacker.DSCardPool

DeckStacker provides a DSCardArt script that updates card art, based on what a traditional western-style card needs:

  • 1 central image
  • 2 corner images

By default, card art is updated after the card is released from its corresponding DSCardPool.

Obviously, unless you are making a card game consisting of traditional western-style cards, you'll want to remake this script to support the needs of your game.

More info on DSCardArt can be found on its page.

Tint

Transparency




What are "stacks"?

Stacks Demo Scene

Conceptually, any place that gathers and arranges cards, as a group, is what DeckStacker calls a "stack".

Stacks represent a group of cards in a place.
A stack can be a pile (deck), a row, a column, or a fan of cards.
An "undefined" stack does not arrange cards, and is ideal for when cards are not gathered into a group, and generally "float" around the table.

How do stacks arrange cards?

API Links
DeckStacker.DSCardMover
DeckStacker.DSRestacker
DeckStacker.DSRestackHelper
DeckStacker.DSStack

When a stack moves its cards into a particular layout, it is called a restack.

  • Every time a card (or cards) is added to a stack, a restack is triggered.
    • This is done by adding the DSStack to the restackList in DSRestacker
  • During a restack, the DSRestacker static class will read information stored in the stack's DSRestackHelper (stack layout settings) to determine where to place the stack's cards.
  • Cards are assigned their positions (in localspace), and then added to the DSCardMover.cardsInMotion List, triggering any needed position updates.

Layout Direction By Stack type

API Links
DeckStacker.DSRestackHelper

Each stack type has a default direction that it will position its cards.

  • Pile : Up
  • Row : Right
  • Column : Down
  • Fan : Right

Note that the DSRestackHelper of a stack has a "Reverse Direction" flag. This will reverse the direction of the stack.

Card spacing

API Links
DeckStacker.DSCard
DeckStacker.DSCardSpacing
DeckStacker.DSCardPool
DeckStacker.DSRestackHelper

Card spacing determines how far the cards are spaced apart in a restack.

The edge of the card that is being measured from is different, between stack types.

  • Pile : bottom edge
  • Row
    • Tight : left edge
    • Loose : right edge
  • Column
    • Tight : top edge
    • Loose : bottom edge
  • Fan : left edge

Note: If Reverse Direction is enabled in DSRestackHelper, the above edge directions are flipped, as well.

Card spacing settings can be found in the Scriptable Object:

DeckStacker/DSCore/Resources/GlobalData/DeckStacker Card Spacing.asset

How does a stack know what DSCardSpacing to use?:

  • A Card prefab selects its preferred DSCardSpacing data with its saved DSCard.cardMoveHelper.cardSpacingKey
  • Any DSCardPool that uses that Card prefab will then be able to retrieve that spacing data
  • Any stack that contains cards from that Card prefab then gets the spacing data from the Card's DSCardPool

That sounds complicated, but just make sure your Card prefab's cardSpacingKey matches with a setting in "DeckStacker Card Spacing" asset.

A stack can override card spacing data

API Links
DeckStacker.DSCardSpacingOverrideSettings
DeckStacker.DSRestackHelper

In a stack's DSRestackHelper, there are card spacing overrides in the form of DSCardSpacingOverrideSettings.
This will override the spacing settings of any card that is put into it, universally.


How is the Action Queue managed?

This subject deserves its own page. See DSAction Guidelines and Use