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
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.
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:
The intended purpose of this card is for when a pawn has reached the other side of the board and becomes a Queen.
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.
Let's start with how to move a card without leaving its Stack.
There are a few different ways of doing this:
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...
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.
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.
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:
Here is your order of operations:
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.
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:
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:
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.
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.
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.
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).
A DSAction is provided for moving a card by a specific Vector3.
DSActionQueue.AddAction(new DSMoveCardInDirAction(DSCard, Vector3 for direction and distance));
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));
This section describes different ways you can use the DSAction system to move cards between Stacks.
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:
There are a lot of different DSActions that initiate this kind of card exchange.
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.
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...
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.
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.
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.
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:
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.
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 purpose of selecting a card is twofold:
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:
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.
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
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.
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.
"Movement" is admittedly used a little loosely, here...
The Card Movement Curves are used in the following events:
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.
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:
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.
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.