DeckStacker v1.0
A card game engine for Unity games
 
Loading...
Searching...
No Matches
Basic How-To's

This page has several mini-tutorials to help shepard you into DeckStacker concepts. This is a series of "how-to's" to demonstrate some of DeckStacker's basic functionality.

Some notes on this section

  • This page will focus on how to accomplish all these things by DSAction, rather than the raw method calls.

  • If you are looking for how to accomplish these outside of the DSAction system, then I would suggest using the DSActions suggested in each of these as code examples.

  • To make this section more readable, the code examples are a mix of actual code and pseudo-code. Be sure to consult the doc pages for any given script for full notes and parameters.

  • Several of the DSAction examples are written for "single card" use, but many of them have overloads to support a List of cards, instead. Be sure to check the doc pages of a particular script to check for that option, if desired.


How to queue up several DSActions during another action's Execute call

I've already written about this in a couple different places, but this is important enough to be repeated.

There will be times when you are queuing up an action where it will need to "know" the board state at execution, but the board will change its state between the time the action is queued, and when it is executed.

To resolve this lack of information, you will need to make sure the new actions (generated by the first one queued) take the place of the original.

To do this, you'll need to create a Custom DSAction that will act as a stand-in for the actions you wanted to queue up at that queue index, but didn't have the information needed at the time.

List<DSAction> tempActionList = new List<DSAction>();

tempActionList.Add(new <replacement action 1 here>);
tempActionList.Add(new <replacement action 2 here>);
tempActionList.Add(new <replacement action 3 here>);

DSActionQueue.InsertActionList(tempActionList, 0);

With this, the actions you wanted to execute with the desired board state will be able to execute with accurate information.

Just like the regular action queue, these actions will be executed in the order they are added.


An example from JumbleChess

In one of the example projects, JumbleChess, there is a script called "JCConvertPieceAction". This script does something similar to the concept described above, but its purpose is slightly different:

This script acts as a common grouping of DSActions, to cut down on redundant code.

private JCCard _convertingPiece = null;

private JCPieceType _newPieceType = JCPieceType.Pawn;

public JCConvertPieceAction(JCCard convertingPiece_, JCPieceType newPieceType_)
{
    _type = this.GetType();

    _newPieceType = newPieceType_;

    _convertingPiece = convertingPiece_;
}

public override void Execute()
{
    LogAction(System.String.Format("Action: {0}", _type.ToString()));

    List<DSAction> tempActionList_ = new List<DSAction>();

    tempActionList_.Add(new DSFlipCardAction(_convertingPiece.cardDS));

    tempActionList_.Add(new DSDelayAction(0.3f));

    tempActionList_.Add(new JCAssignCardDataAction(_convertingPiece, _newPieceType));

    tempActionList_.Add(new DSFlipCardAction(_convertingPiece.cardDS));

    DSActionQueue.InsertActionList(tempActionList_, 0);

    Resolve();
}

When the JCConvertPieceAction action is executed, it then replaces itself with the following actions:

  • DSFlipCardAction
  • DSDelayAction
  • JCAssignCardDataAction
  • DSFlipCardAction

The intended purpose of this card is for when a pawn has reached the other side of the board and becomes a Queen.

  1. The card is already face up, so it is flipped to face down to cover the visual update.
  2. There is a delay so that the visual update doesn't pop when the face up is still visible.
  3. Card data is changed to the Queen, and the card art is updated at this time.
  4. The card is still face down, so it is flipped back to face up.


How to move a card...

This example doesn't need a whole lot of introduction. You're making a card game. You will most likely want to move cards around the screen.

The question becomes: In what way do you want to move the card?

Note: "Moving a card" in this context is defined as a change in the card's position, and will not refer to other animations.


Intra-Stack Movement

Let's start with how to move a card without leaving its Stack.

There are a few different ways of doing this:

  • Change card order
  • Offset the card
  • Grab the card
  • Shuffle the Stack
  • Other intra-stack movement


Change Card Order

Reminder: The index order of the DSStack.cards list will be used when a restack is triggered to determine sibling order and card position.

We can manipulate this card order in a few different ways...


Shift a card up or down the Stack order

When you want to simply move a card up or down the index order of a Stack's card list, you can use the DSShiftCardOrderAction.

The following line will add the DSAction to the queue:

DSActionQueue.AddAction(new DSShiftCardOrderAction(DSCard, (int)X positions));

Positive howManyPositions_ numbers will move the card toward the end of the Stack's card order, while negative numbers will move it toward the beginning.


Exchange 2 cards in the stack

There could be times when all you need to do is switch the positions of 2 cards in a Stack.

For these instances, use this code:

DSActionQueue.AddAction(DSExchangeCardsAction(DSCard 1, DSCard 2));

From here, DSCardMover will switch the placement of the 2 cards, and trigger a restack.

Note that this DSAction is also capable of switching 2 cards that are in different Stacks.


Completely reworking the card order of a Stack

The time may come where you just want to brute-force a Stack's card order, and the scope of the previous DSActions jusst isn't enough.

A DSAction is not provided for this scenerio due to 2 factors:

  1. This will be highly context-sensitive, and a one-size-fits-all solution isn't very useful.
  2. This is a relatively small bit of code you'll need to write in a Custom DSAction.

Here is your order of operations:

  1. Save your new card order in a List<DSCard>
  2. Apply that list to DSStack.cards
  3. Trigger a restack

In this example, "stackDS" is the Stack that contains the cards.

List<DSCard> newCardOrder = new List<DSCard>();

<code that reorders stackDS.cards>

stackDS.cards = newCardOrder;

stackDS.AddToRestackList();

From there, the DSRestacker will do the work of rearranging your cards.


Offset a card

An "offset" is defined as "changing the visual position and / or scale of a card without effecting its relative position / scale in its Stack".

This is useful for when all you want to do is slightly change the position and / or scale of a card for visual emphasis without changing its transform properties with respect to other systems, like the card's position in the Stack.

In even fewer words: An offset card's DSCard parent object will not be moved. The "offset parent", which is a child of the DSCard, will be moved, instead.

This can be done in 2 ways...

Queue a DSAction:

DSActionQueue.AddAction(new OffsetCardAction(DSCard, position offset, scale offset));

Directly call the DSCard's offset method:

DSCard.cardMoveHelper.OffsetCard(position offset, scale offset);

It will be useful to know the default Vector3's of the offset values:

  • Default position when not offset: 0f, 0f, 0f
  • Default scale wen not offset: 1f, 1f, 1f


Grab a card

Card grabs are very similar to a card being offset: They both are designed to keep their respective DSCard transform at the same position while moving a chile object in the card prefab to offset the visuals.

The key difference between the 2: Card grabs are meant for user controlled motion, while offsets are procedural.

Because grabs are intended to be user controlled, there isn't a DSAction provided. This style of movement wouldn't work well in the DSAction queue.

There are 2 ways of initiating and updating a card grab:


Mouse Driven Input

When the player is using a mouse, use these methods for controlling a grab:

Initiating a grab: DSCard.cardGrabber.StartGrabWithMouse()

Updating a grab: DSCard.cardGrabber.OnGrabWithMouse()

Be sure to visit the "Object: Card Button" section of the Card Prefab Setup for implementation recommendations for mouse input.


Code Driven Input

When the player is using some other input device (such as keyboard or controller), use these methods for controlling a grab:

Initiating a grab: DSCard.cardGrabber.StartGrabWithCode(position you want the card to move to, initially)

Updating a grab: DSCard.cardGrabber.OnGrab()

Keep in mind that the OnGrabWithMouse() method will get the mouse position and update the grab position with it.
That affordance is not possible with grabbing a card with code, and you'll need to set the DSCardGrabber.grabPos manually in your script.


Shuffle a Stack

Shuffling a stack is pretty straightforward.

Add this line to add a shuffle action to the queue:

DSActionQueue.AddAction(new DSShuffleAction(DSStack));

From here, the input DSStack will have its card order rearranged, several of the cards will be moved to the sides to visually animate the shuffle, and a restack will be triggered for it, pulling the cards into the new order.


Other intra-stack movement

There are a couple other ways of moving a card within a Stack.

These DSActions are best used for Undefined Stack types, but are useable in other Stack types if desired.
For the other Stack types, the card that has been moved will be pulled back into its normal card position the next time a restack is triggered (which will happen frequently).


Moving a card in a specific direction

A DSAction is provided for moving a card by a specific Vector3.

DSActionQueue.AddAction(new DSMoveCardInDirAction(DSCard, Vector3 for direction and distance));


Moving a card to a specific position (either world or local space)

A DSAction is provided for moving a card to a specific Vector3 position in either world or localspace:

DSActionQueue.AddAction(new DSMoveCardToPositionAction(DSCard, new position, bool for worldspace));


Inter-Stack Movement

This section describes different ways you can use the DSAction system to move cards between Stacks.

  • Card Dealing
  • Automatic Card Distribution Between Multiple Stacks
  • Combine Several Stacks Into 1
  • Exchange 2 Specific Cards


Card Dealing

A "deal" is a term used loosely for moving a card, or list of cards, between Stacks.
Basically, think of a traditional card deal to a player:

  • The Stack doing the dealing is the dealer's deck
  • The Stack recieving the deal is the player's hand

There are a lot of different DSActions that initiate this kind of card exchange.

  • DSActions that deal from the top of the deck
    • DSDealCardsAction
    • DSDealCardsToStackListAction
  • DSActions that move a specific card or list of cards
    • DSDealCardsListAction
    • DSDealSingleCardAction

Detailing each of these DSActions is beyond the scope of this page, so be sure to check the doc pages for these scripts for more information on how their overloads work.


Automatic Card Distribution Between Multiple Stacks

There may be times when you have a bunch of cards, either in a Stack or a List<DSCard>, and you want to evenly distrubute those cards between multiple Stacks. The script DSItemDistrubutor was written for this purpose.

To easily utilize the DSItemDistrubutor, you can use the DSDistributeCardsBetweenStackListAction class.

This DSAction can be used to distribute a Stack's entire cards list, or you can predefine a List<DSCard> and input that, instead.

Additionally, there are overloads that allow you some control over item distribution and weighting that distribution between the start and end of the Stack list. Be sure to check the docs for more details.

The original conception of this system was a DSMultiStack class that managed cards between several DSStack objects, but this ultimately proved to be a very volatile implementation that degraded the quality of several parts of DeckStacker.
Instead, I pulled back and separated out some of the code for a more generic DSItemDistrubutor system, and a DSAction to leverage it.

In this way, less is automatically handled, but this implementation is far less buggy and gives the developer more responsibility for managing the distribution in the way they see fit. I think this is the right way to go, all things considered...


Combine Several Stacks Into 1

The inverse of the previous secition is DSCombineStacksAction. This DSAction takes several Stacks and combines all their cards into a target Stack.

DSActionQueue.AddAction(new DSCombineStacksAction(List<DSStack>, target Stack));

This DSAction leverages the DSDealCardsListAction.

Note that the cards will be added to the target Stack in the order that their original Stacks appear in the List<DSStack>, but any cards that are already in the target Stack will stay put, and not appear in the order defined by the Stack list.

If you want the cards mentioned to appear in a different order, you will need to define that order in a separate way, or just not use this action and do it manually.


Exchange 2 Specific Cards

If you read a previous section about exchanging card positions, this is the version that switches 2 cards that are in different Stacks.

DSActionQueue.AddAction(DSExchangeCardsAction(DSCard 1, DSCard 2));

These cards will inherit their counterpart's card position and index and trigger restacks in their respective stacks, moving the cards into their new positions.


How to tint a card...

DeckStacker comes with card tinting behavior. This system is particularly good for game prototyping, but can be used in the final product if your art is compatible with it.

In the default card prefab, it works by tinting a list of semi-transparent Image objects, which in turn applies a color to the card. (see "Objects: Card Selection Tinter Front and Card Selection Tinter Back" section of the Card Prefab Setup page)

The colors for tinting are saved in the following ScriptableObject:

DeckStacker/DSCore/Resources/GlobalData/DeckStacker Card Tint Settings.asset

These colors can then be accessed via a key assigned to them.

DeckStacker Card Tint Settings

In code, a card can be tinted fairly easily, once these are setup.

DSCard.cardTintHelper.TintCard(Tint Key, Tint Animation Speed Multiplier);

In the event that you don't want to setup a color in the ScriptableObject ahead of time, you can feed a Color value directly into the tint helper method:

DSCard.cardTintHelper.TintCard(Color, Tint Animation Speed Multiplier);

Note: The "Tint Animation Speed Multiplier" is a multiplier on the Time.deltaTime value, so a larger number will give a faster tint speed.

If you don't want to ease between the current color and the target color, and would prefer an instant tint, you can use the TintCardInstant methods in DSCardTintHelper, instead. As the name implies, this will instantly tint the card.

You may have noticed that there are 3 tints that are already provided:

  • selected
    • Default key used for card selection feedback
    • Cards can use a different color for selection by changing the Default Selected Tint Key of the card's DSCardSelector
  • highlighted
    • Out of the box, this is only used in the Tic Tac Toe demo game.
  • shuffle_chess_highlight
    • Out of the box, this is only used in the JumbleChess demo game.

Additionally, you can find a "Default Tint Curve" stored in the "DeckStacker Card Tint Settings", which is used in the tint animation. This is exposed to provide the option to change this easing behavior if you'd like. The default curve works pretty well.


How to select a card...

Selecting a card is easy:

DSCard.cardSelector.ToggleSelect();

From there, the card is recorded in the DSRegistry.selectedCards list, and is considered "selected". What that means, will have to be determined by your game code.

The more interesting quesiton is "What does the card selection feedback look like?"

The purpose of selecting a card is twofold:

  1. The player wants to tell the app that they want to do something with a card.
  2. The app should tell the player that their input is successful (aka feedback).

So it all comes down to visuals. DeckStacker will record a card as "selected" by adding it to the DSRegistry.selectedCards list, and then trigger any desired scaling, position offset, and color tint animations.

By default, you can adjust the visual feedback on scaling, position, and tint in the following properties:

  • Selected Card Scale Offset
    • A per-Stack Vector3 property
      • This is because different stack types will need different treatments
    • You can find this property in DSStack of whatever contians the card
  • Position offset
    • A per-Stack Vector3 property
      • This is because different stack types will need different treatments
    • You can find this property in DSStack of whatever contians the card
  • Color tint
    • A per-Card string key that should match a desired color stored in the "DeckStacker Card Tint Settings" (see "How to tint a card...", above)
      • Unlike the previous 2 properties, different cards will require a different color for tinting
    • You can find this property in the DSCardSelector component attached to the card

Note: These properties can be adjusted in the respective prefab or prefab instance's Inspector.

By toggling select on a card, the position offset, scale, and tint animations should automatically trigger.


How to customize movement and rotation easing...

DeckStacker provides default rotation and movement easing curves for their respective animations. These easing curves do the trick, but you or your art team may want to adjust them to give your game a slightly more unique feel.

The various card movement and rotation curves can be found in this ScriptableObject in the project:

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

Card Movement Curves and Card Rotation Curves are 2 lists that store key/value pairs, where the curves are stored. These are accessed by the DSCardMoveHelper and DSCardFlipper attached to a card via keys stored in the Inspector of those components.

Where to find curve keys

I wanted to avoid a one-size-fits-all approach, in case there was a need for a different movement aesthetic for a different card type, or changing the size/shape of a card changed the way these animations feel. In the event that you want different easing curves for different cards, you can add additional keys to the "DeckStacker Movement Settings" and give your card prefabs different keys.


What is a "movement curve"?

"Movement" is admittedly used a little loosely, here...

The Card Movement Curves are used in the following events:

  • Any time the base DSCard object is being moved by DSCardMover
    • This is also referred to as "Basic Movement" in DSCardMover
    • The most common way this movement is activated is when a "restack" is triggered in the card's Stack
    • This can be seen as moving the root of the card object
  • When a card is "offset"
    • This should be seen as a temporary visual offset for the card
  • Card rotation (Non-flipping and non-movement-tilting rotation)
    • This can be seen as rotating the root of the card object
    • This is triggered by calling DSCardMovementHelper.RotateCard()
    • Cards are also rotated in this way if they are being moved between different stacks that have different rotation values
  • Non-offset scaling of the card
    • This can be seen as scaling the root of the card object
    • This is triggered by calling DSCardMovementHelper.ScaleCard()
    • Cards are also scaled in this way if they are being moved between different stacks that have different scale values

Note: This is NOT used when the card is "grabbed" by the DSCardGrabber. That easing is handled by a simple lerp, due to the dynamic nature of card grabs.


Why are there so many rotation curves?

There are so many different ways a card can rotate!

Note: Rotation curves are saved using the DSVector3Curve class. Not all of the properties visible in the Card Rotation Curves list are configurable.

Here are the properties you should focus on, and how they are used:

  • X Curve
    • Effects the "XZ Rotator"
    • Makes card flipping animations feel less flat
    • Purely aesthetic
    • Requirement: This curve should start and end at Y=0
  • Y Curve
    • Effects the "Y Rotator"
    • This is the curve that is used for flipping the card between face up, and face down
    • This curve is used, in both directions
      • The 2 ends of the X axis represent the card being face up or face down
        • When the card is face up, the "Y Rotator" Y angle is calculated at X=0 on this curve (results in 0 degrees)
        • When the card is face down, the "Y Rotator" Y angle is calculated at X=1 on this curve (results in 180 degrees)
      • When the card is flipping, the direction of the flip determines which direction the evaluation is moving along the curve
        • Flipping from face up to down will move through the curve in the positive direction
        • Flipping from face down to up will move through the curve in the negative direction
    • Requirement: For the above reasons, this curve should start at Y=0 and end at Y=1
  • Z Curve
    • Effects the "XZ Rotator"
    • Makes the card flipping animations feel less flat
    • Used to tilt the card while moving for good game feel
    • Purely aesthetic
    • Requirement: This curve should start and end at Y=0

For info on what the "Rotator" mentions are, above, see Card Prefab Setup.

I think the default animations work quite well, but your feelings may vary. I would recommend not touching the default and making new entries in the Card Rotation Curves list if you want to experiment and change the feel of card flipping. This kind of dynamic animation work is not super intuitive, so you'll want to preserve a baseline to compare against.


How to customize card spacing...

Card spacing settings can be found on the following ScriptableObject:

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

Just like the Card Tint Settings and Movement Settings, this SO saves key/value pairs in a list. These DSCardSpacing objects save how much cards should be spaced (in localspace relative to the card's current stack) when their respective Stack is "restacking".

For more info on card spacing, see the "How do stacks arrange cards?" section of How does it work?.

Card spacing settings are assigned on a per-card-prefab basis, and stored in the DSCardPool that spawns a card type. This is because different card prefabs may need different spacing.

To assign a DSCardSpacing setting in the ScriptableObject to a card prefab, assign the corresponding key to the Card Spacing Key found in the card prefab's DSCardMovmentHelper.

Where to find a card prefab's Card Spacing Key