- Home /
What's the best way to couple logic and rendering in a card game?
I've started making a simple card game, I'm making a card game but I'm not sure how best to update rendering based on the state of the hands/board.
I have lists of integers for both player's hands, and the state of the board. These integers simply allow me to lookup card data from a big list of cards. Logically I can add/remove cards to the lists at any time. However I'm not sure how to go about drawing the hands or the board. My game is completely 2D at the moment, so I'm using UI objects.
I've made a prefab which I can instantiate and change the image/text on. The question is how do I know when to add/remove these objects? I can think of some methods, but I might not be considering some alternatives.
Every time the board or hand changes, tell the GUI to delete all UI objects and re-create them based on the current state. Positives are it's simple and easy, negatives are that it's adding/removing an unnecessary amount of objects every time the state changes.
Every time a card is added/remove from the hand/board, send an event to the GUI so it can remove or add that card. Positives, it's elegant and efficient. Negatives are that I have nothing linking the cards in hand with the cards being rendered, so I wouldn't know which card to remove if there's a duplicate in hand.
Actually store the UI object in the hand/board lists, so we know exactly which one to add/remove. The downside of this is I want to keep logic and rendering separate, since eventually logic will be done on the server. Possibly some sort of identifier or hash to link the two would work?
Advice? Thanks.
Big open-ended subjective questions belong in the forums. We are encouraged to reject such questions at UA, but it's polite and well written, so why not?
In my UI inventory system, I need to display dozens of buttons dynamically. Because I wanted a simple list, it seemed the most straightforward option was to refresh everything when anything changes. (option 1). Ultimately I was happy with this solution; whether it will work well for you may depend on your unvoiced expectations.
Often the best way to approach a problem is the most expedient way. Sometimes you'll make a big mess by going with the most trivial or hack-y solution, but you'll learn from your mistakes for next time. In that sense, you win either way.
Thanks, I was leaning that way as well. However, I realised that I also need to be able to tell which card a player has dragged onto the board, which means I need some sort of unique identifier to link the logical card and UI card.
How did you do this with your inventory system? When a player equips something from the inventory, how do you know what they clicked on?
The display area has a "layout group" component, and each button ("card" in your case) gets a "layout element" to automatically arrange a dynamic number of them, if necessary.
Refresh Cycle:
Delete all children of the layout group.
For each item in the inventory (card in the hand)
Instantiate your prefab UI element as a child of the layout group
and initialize it as needed.
Each button (card) has a script which implements the built-in IPointerClickHandler interface. Clicking the button invokes the method, which calls a managerial script of some kind, and passes itself (or some identifying property of itself) as an argument. That's the "which one got clicked" bit...
public class CustomButton : $$anonymous$$onoBehaviour, IPointerClickHandler {
public void OnPointerClick( PointerEventData ped ) {
$$anonymous$$anagerialScript.Instance.OnCustomButtonClicked(this);
}
}
Yeah, the 'identifying property' is the important part. For now, I ended up making the following changes:
Logical cards are now stored in a list with two elements; an index that defines what type of card it is, and a unique ID, which is automatically assigned and incremented for every new logical card.
UI Cards are likewise stored in lists with a reference to the GameObject containing the images/text/etc, and also a UID.
When a logical card is added to a list, a layout manager is notified and passed the UID. It creates the GameObject and positions/scales it, and stores it in a list along with the UID. There's now a logical and UI card with matching UID. If one of the UI cards is selected, I can pass it's UID to the logic side and find out which card it is, and likewise if I want to remove a card from the UI, I pass the UID from logic to layout.
I'm sure I'll find issues as I progress, but it seems like an O$$anonymous$$ system for now.
Answer by lizard4455 · Jun 02, 2015 at 09:17 PM
I recently made a card game with a dual system like this and this is what I ended up doing. I am by no means an expert but I hope this can at least help you out a little.
I made two lists of integers, one for the cards in play and one for the cards in the player's hand
I made a special tag for the UI cards in play, and I made a tag for the UI cards in hand
I made two display methods, one for the board and one for the hand, inside of the script that handles the logical cards. These methods handle turning logical cards into game objects.
Example (C#): This method is marked RPC to allow it to be called over a network (When a card is played, it can be called in rpc mode to all players to refresh their displays)
[RPC]
void Display(){
//First check to see if there are UI cards with the tag "hand" in the scene, then clear them all from the scene
if (GameObject.FindGameObjectsWithTag ("hand").Length > 0) {
GameObject[] dispCards = GameObject.FindGameObjectsWithTag ("hand");
foreach (GameObject go in dispCards) {
Destroy (go);
}
}
//For loop to go through each of the cards currently in hand (integer list "inHand") and instantiate a new UI card object "cardFab", change its tag so it can easily be deleted later. resize and posHand are values based on screen size I have declared earlier to give it a "liquid" layout
for (int i = 0; i < inHand.Count; i++) {
GameObject newCard = (GameObject)Instantiate (cardFab, posHand[i], Quaternion.identity);
newCard.tag = "hand";
newCard.transform.localScale = new Vector3(resize, resize, 1);
//An example of setting the text on the card based on the integer ID that is currently in spot "i" of the "inHand" list. The GetText method is not relevant to your project, so do this however you have already managed to do it
newCard.transform.GetChild(0).gameObject.GetComponent<TextMesh>().text = GetText(inPlay[i]);
//You will probably need to add a script to your card prefab. This sets an integer called "place" in the UI Card's script that points to the index of the logical card list that corresponds to that card. Useful later with selection
newCard.GetComponent<DispCard>().place = i;
}
}
Like I said it may be helpful to add a script to your UI card prefabs, not only to help detect clicks, but to store some values
Here is an example of a script that could be attached to a UI card GameObject that would work well with the above example (This one requires the card have a box collider 2D/3D)
public class DispCard : MonoBehaviour {
public int place;
void OnMouseDown(){
Camera.main.gameObject.GetComponent<Game>().selected = place;
}
}
This script simply stores the index of the corresponding card in the logical list, and will update an integer "selected" in the script where the logical cards are. In the integer list of card IDs currently in the player's hand, selected represents the index of the selected card ID.
Of course, there are probably many ways you could go about doing this kind of game... Mine may not even be the best, but I hope it was helpful to you. I also hope it wasn't too confusing, I had to change a bunch of stuff to fit your scenario better. Good luck! (Especially if you are doing multiplayer... you'll need it >.<)
That's awesome, thanks! If I understand correctly, to update the client, you delete all and re-create (using a tag so you don't have to clear board and hand at the same time). And to update the server, you store an identifier in the UI GameObject, and when the GameObject is selected, you pass that identifier to the logical side.
It sounds similar to what I'm trying now, except I'm using the identifier to talk both ways, including from the server to the client.
Yeah if you have a list of cards played down and cards in hand, you can play a card by removing it from hand and then doing an rpc call to add to everyone's played card list then rpc a display to update everyone.
Your answer
Follow this Question
Related Questions
Help with Blackjack Probability Concept 1 Answer
cpu logic in cards game 0 Answers
Rendering with the CPU in Unity 0 Answers
GrabPass and Transparent geometry 0 Answers