DeckStacker v1.0
A card manager plugin 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 cards move?

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

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

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

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

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 DSCardMoveHelper.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

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"

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?

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

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

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?

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

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?

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

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

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

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