This post is just for me to write down some ideas bout how to architect the Unity card game I wrote about once before. The ideas here are the ideas I came up with once I knew what I needed to make the card game work… so far. The needs are likely to change but right now, this first set seem reasonable to work from.
The game is a 2D card game where there is a set of common cards, decks, and discard piles, and also a lot of player-specific cards. The screen will only ever show the play area and common are for one player at a time, with the exception of a list of players and a few meaningful stats about them. The game presents some interesting challenges and possible solutions that I present below.
Variable Size/Ratio Screen
I intend to have this game run on a PC in a window. Therefore, the game will need to handle screen size changes. The ratio of width to height can be changed as part of a resize operation so the code to handle the placement of content needs to be very flexible. If the code handles the screen or window size properly, there will be nothing special to do on any of the various devices that Unity supports.
The solution to the screen size problem is to have a screen layout singleton class that converts position “codes” into coordinates and scale values. This singleton class will be a child class to the Unity MonoBehaviour class and it will have an Update() function that can detect screen size changes and create a set of coordinates for all in-game objects (cards, decks, etc.). Whenever a object like a card needs to be played on the screen, it will get the exact coordinates from the layout manager object.
The only thing I am unsure about using this solution is weather or not each on-screen object should check and reposition itself on every call to its Update() function. It seems like it might be more efficient to have the layout manager class detect the screen size change and then notify the objects of the change so they don’t constantly update their positions to the same exact spot 60 times per second. then again, if there is no change, that can be detected and nothing would happen. it’s only a few lines of code for each object that would run at the frame rate most of the time.
Whenever a player or the game needs to move a card from one place on the screen to another, like when the player indicates that they want to play a card from their hand down onto the table, the card should slide smoothly into the new position and handle any rotation or scaling as it goes. This sliding effect makes card games playable since the player gets good feedback about what is happening in the game.
The solution to moving the cards around is the card mover class. The card mover class is a MonoBehaviour child class and therefore can be added as a component to a game object. On every Update() call for a game object, the object code will call the Update() function for the card mover component. If the player moves a card, the game code will somehow, through the game object, call a function in the card mover to set a start and end to the move, including rotation and scaling information. This info will not be set as coordinates but rather as “codes” for screen locations known to the layout manager. the card mover class will simply get the start and end coordinates from the layout manager for each update and will determine the proper intermediate location based on the speed and current distance traveled by the game object. The reason for using the layout manager is so a screen size change during a move won’t make the game behave unexpectedly.
My concern with this idea is that there will be lots of Update() function calls. That is always a concern. With a maximum of 20 to 50 game objects on the screen, this should not be a problem.
Cards and Sprites
There will always be cards displayed on the screen either face up or face down, and with some scale and rotation. Pre-creating a sprite for every card in the deck will be unmanageable because a two player game requires two theme decks and 114 cards.
The solution to the sprite problem is to just create the sprites on-the-fly. I think that I can create a single card prefab object in Unity and then create an instance of the prefab at runtime when a card is needed. My code would create the sprite and then add properties to it by passing in some type of property objects of my own making. I would also pick an image for the card from some large texture image. The properties I might give to the card are related to the score of the card, the side effects of the card, the multiple different actions of the card if it is an event, etc.. Even the card being an event card vs. a park card, or blueprint card, would be a property. The basic sprite object would only have built-in code to handle things that are common for all cards.
For elements of the game that are not cards, I would create prefabs for them individually. There are three different coin values and it would be no trouble to just create three different prefabs. Note that in Unity, I can add components to game objects and one of those components can be a coin value component with a preset variable that is the coin value. Then the interface in the coin game object will be a call to a function in the coin value component.
I sort of like how Unity lets me add components to game objects. I read a book a long time ago about doing something similar in C++. The chapter on this subject was all about how to not use inheritance to create complex objects, but rather to use property classes to assign property to give objects their complex set of behaviors. it was rather interesting and even went as far as having an object get created with a memory allocator property so that the object could create itself in memory in some interesting and variable way. of course, to handle the memory allocator property, the property needs to be set using templates before the object is actually instantiated. it was all very interesting. Unity game objects are sort of like that but at a bit higher level.
I don’t know enough about Unity, sprites, and textures, to know how well the card sprite ideas will work. I think they will be easy and that determining the architecture now is most of the work.
The actual flow of the game from one step to the next and from one turn to the next is an interesting challenge. It’s not a coding challenge though; it’s a challenge for me to figure out how other people manage this part of their games and to do it right in the first place.
A game manager singleton object seems appropriate for this. It will somehow return state information about the game that can be used by all of the other objects to know how they should behave. An event card cannot be played during the park step of the game and a park card cannot be played during the event step. Do the card objects even need to handle their own state management? No they should not. When a player taps on a card, the tap even and the target of the event should be passed to the game manager. The game manager should have a reference to all cards on the table and would determine how to act on the tap event. If the card should simply move somewhere, the game manager would tell it to move. If more info is needed, the game manager would create the game object that is the popup window and then act on the buttons tapped or clicked in that popup. Yep, there need to be a user interface that includes popup elements like requests for more info, notifications, etc.
the trouble with a game manager is that too much of the game becomes functional and linear and not object oriented. The objects suddenly have properties but no functionality (other than accepting input and moving themselves). It seems on the surface as if this is wrong and that the objects should know how they behave and handle it. But a playing card never knows about the game in real life. It only knows its value. The “hand” of cards might know that is has some cards that have values but the hand also knows nothing about the game. it is just a collection of cards. In the end, it is the player who knows about the game and it is a shared “experience” between the players because they all know the state of the game. So a game manager is actually very much like the real life structure of a card game. The state and meaningfulness of the hands, the cards, the cards on the table, etc., is all only understood by the game manager. But maybe this doesn’t seem wrong at all. Why would a card know how to take action and not just know what action it provides to the game manager?
So I’ll have a game manager singleton object.
There will be a game manager to track the state of the game and to handle interactions between players, table, etc.. And there will be a layout manager that knows about where things go but nothing about why. There is no need to discuss player objects because it is obvious that the cards for a player, both in hand and on the table, need to be kept in some sort of list structure. And there are cards. Oh, and other things that are more obvious in their architecture like the user interface elements, coins, tokens, markers, or whatever else the game needs. It was really on the sprite and card management and game and layout management that were ever a problem to solve.
So now on to doing some coding. The first step will be to create the layout manager and make sure that every possible location of content is given a special code value (an enum value if you are a programmer). To test that, I will then need to implement the sprite-on-the-fly code to create cards with the proper properties and visual state. That will be the first real challenge since it is not just me writing C# code; it is me researching how to create sprites and give them textures at runtime in Unity. A bit of testing will get done to place cards in all of the card locations on the screen. Maybe a list of what I need to do is a better way to describe the plan:
- Finish layout manager.
- Create basic sprite-on-the-fly code for cards.
- Test layout manager and card visuals, including moving cards from one location to another and flipping and rotating them as needed. They will need to flip from face down to face up and vice versa sometimes.
- Design and implement player class to track player information (to support more than a single player).
- Design and implement game manager to track all players as well as all things that are common to all players (market, bank, card decks, etc.). Maybe the “table” with its decks, market, etc., needs to be an object that is within the game manager object? It doesn’t seem useful except to move some code out of the game manager class and having it in it’s own class to be easier to maintain. I’ll decide that later.
- Implement various game manager features one at a time starting with just a basic turn manager that relies on the “end of turn” button. Then the various steps, actions, etc, can be added one at a time until the game works.
- Add properties to the cards to support a minimal game with simple cards before implementing an exact replica of the actual commercial game. Note that I have no permission to distribute a game that is a copy of the board game. The purpose of this project is to learn how to use unity while implementing what is an interesting and challenging table top game.
That’s as far as I can think ahead. Now on to writing code.