I'm pretty new to gamedev and I'm starting to make more complex games, but handling the growing amount of data, classes and functions, while still trying to use best design-practices, is proving tricky!
Hypothetically, say I want to make a version of the board game "monopoly". In very pseudo-code (I'm using C++ but this probably applies to other languages), I'd try to model the data showing the current state of the game like this:
class Board {
private:
container<Square> squares; //board locations to land on. Square is a base class
//from which classes like Property, Station inherit
container<ChanceCard> chanceCards;
container<CommChestCard> commChestCards;
Bank bank; //to begin, bank owns all Deed objects, which are linked to Property squares
unsigned freeParkingMoney; //amount of money in the center of the board
//that you win by landing on free parking
//etc..
};
class Player {
private:
string name;
unsigned money;
Token token; //hat, iron, shoe ...
container<Deed> ownedDeeds;
//etc
};
class GameData {
private:
Board board;
container<Player> players;
Dice dice;
Player* activePlayer;
};
Rather than having a huge class with all the states in, GameData, I've tried to use modularity and break things up into sub-parts, using composition. (There are some missing class definitions of course, this is just a sketch.) This is all separate from the game's rendering / state stack / gameloop / input handling etc.
The problem with this comes when a player wants to take some action in the game. For example, say I wanted the current player to buy a property deed using the function buyDeed().
- How does it access all the data it needs?
I have reduced the size & responsibility of the GameData class and made everything more modular. But on the flip side, the data is spread all over the place and hard to connect together. buyDeed() would need access to GameData::board, and then Board::bank, and then Bank::deeds. Then it would hand over ownership of that deed and give it to the active player, and have the player pay for it, which requires access to GameData::activePlayer, and Player::ownedDeeds and Player::money. I'm not sure how to address this - I could make a lot of the data global/static/public, which is generally considered bad design. Or store a reference/pointer to each dependent object in each class - which tightly couples all my classes.
2. Where should the function go?
Because the function would act on a lot of data in several different class scopes, it isn't clear where to define the function. I could pass the dependencies along through member functions in each class, so each class operates on its own data, using pass-through functions - but that would quickly lead to spaghetti code I think, and it would be hard to track the program flow. Or have the logic in a separate GameEngine class that can modify GameData - that would be nice and separate, but I get back to how GameEngine accesses the data.
I've tried reading up on game design patterns, like dependency injection, or components etc. But most articles focus on decoupling the rendering / physics / input in games, not amongst the actual game data in a composition-style design. So I'd be interested in how more experienced gamedevs would go about approaching this. Or, if the design is just in general bad, how you would fix it? Thanks!
[edit: typo]