I understand that circular dependencies are bad, so I shouldn't have A reference B, and also have B reference A. But in the strategy game I'm making (in Unity with C#), the map is made up of Tiles that contains Cities, and the Tiles and Cities are controlled by Kingdoms. Naïvely, that means the dependency tree should look like
Kingdom -> Tiles -> Cities
Only problem is, when the graphics system needs to render the Tiles, they are supposed to render them the same colour as the controlling Kingdom, so when looping over the the Tiles, it should be able to find the Tile's Kingdom through a reference. That means the dependency tree now looks like
Kingdom <-> Tiles -> Cities
Also, when calculating the income of the Kingdom, I need to know which Cities are owned by it, and then the tree looks like
Cities <- Kingdom <-> Tiles -> Cities
It's a mess, and this is just one example. I keep running into this problem when making games like this. I mean, Tiles doesn't even depend on either Kingdom or Cities, so should they even hold a reference to either in the first place? But then how do I know what belongs to who?
How do I structure the data in the most sensible way for a strategy game?
CodePudding user response:
The tiles shouldn't be part of any kingdom object. They should just be referenced by whatever kingdom or other type of object that uses them somehow.
Your kingdoms (which seem to be the central piece here) should hold a reference to the tiles they own as well as the cities it owns. When painting your map you can either loop through them all, and check if each belongs to a kinddom (by asking the kindgdom object) or you can later loop through each kingdom and check what tiles belong to them and repaint them. Which you choose should depend on how heavy such an action would be.
When calculating income you loop through each kingdom and inside that each city they own.
CodePudding user response:
I am not an particularly familiar with Unity but have crossed similar issues. A work around is to use a "manager" for all the elements. For example:
public class Board
{
List<Kingdom> _kingdoms;
Dictionary<Tile, Kingdom> _boardTiles;
public void RenderBoard()
{
foreach(Tile tile in _boardTiles.Keys)
{
tile.RenderTile(_boardTiles[tile]);
}
}
}
public class Kingdom
{
List<Tile> _kingdomTiles { get; }
public double GetIncomes()
{
double totalIncomes = 0.0;
foreach(Tile tile in _kingdomTiles)
{
foreach (City city in tile.Cities)
{
totalIncomes = city.GetIncomes();
}
}
return totalIncomes;
}
}
public class Tile
{
public List<City> Cities { get; }
public void RenderTile(Kingdom owner) { /* Render */ }
}
public class City
{
public double GetIncomes() { /* Calculate */ }
}
CodePudding user response:
TLDR; Circular references are bad if they end up in hen and egg problems, e.g. you can't create a kingdom, because every kingdom needs a city and every city needs a kingdom. Or you have kingdoms and cities in different projects and can't build the kingdoms-project because you need to build the cities-project first and the cities-project relies on the kingdoms-project.
I suppose that you will create a kingdom first and then create cities for the kingdom afterwards, so I do not think that cirular references are the prime concern in your scenario. From that point of view, it is ok to have circular references if it enables traversing the data more efficiently.
The problem on how to depict hierarchies is a very common one. You should differentiate between the following:
- How you store the hierarchy (e.g. in a database)
- How you keep the data in memory.
As regards storage, this depends on the DBMS you use; in a relational DBMS, you would have tables for kingdoms, cities and tiles; tiles keep the ID of the city, cities keep the ID of the kingdom:
Kingdom <- City <- Tiles
In a document DB (e.g. MongoDB) there are other approaches as well; in an extreme example you could embed both cities and tiles into the kingdom document if you always read the data of the whole kingdom.
When you load the data from the database or create them in memory, you have the option to organize data in a different way. Please keep in mind that there are different perspectives that you need to respect:
- In your sample, you want to draw the tiles and need information from the kingdom; this means that the tile needs to reference the city that references the kingdom:
Kingdom <- City <- Tile
. In this case, "referencing" means that you have a property forCity
on theTile
and a propertyKingdom
on theCity
. - There are cases when you want to enumerate all cities in a kingdom and all tiles in a city. To support this, you can create a list of
Cities
on theKingdom
and a list ofTiles
on theCity
:Kingdom -> City -> Tile
As you can see, it depends on the perspective how you structure the data in memory. In your case, I`d support both perspectives by creating both a property for the parent and a list for the children. This asserts that you can traverse the structure from both directions in an efficient manner.
Be aware, that you should make sure that the information remains consistent. E.g. if you move a city to another kingdom you do not only need to move it to the list of children of the other kingdom but also update the Kingdom
property on the City
to reference the newly assigned kingdom.