0
\$\begingroup\$

This game involves making buildings that can be upgraded. Sometimes a building's upgrade isn't available until another building is either created or upgraded to some specific level.

For simplicity's sake, there are two buildings, A and B. Level 1 of both buildings can be built at the start.

Building B can be upgraded freely but building A can't be upgraded to level 2 until building B is at level 4.

As far as class structure, I don't know if there should be a Building class and then BuildingA and BuildingB classes that inherit from it. Or if there should just be a Building class and individual building types are instances of that class. This latter solution seems ugly because there is also the layer of individual instances of building a building type.

In either case, I don't know how to design the class structure to control dependencies like the example I gave. Other than hard coding it which isn't very clean or conducive to extending it to more buildings and nuanced dependencies.

\$\endgroup\$
2
\$\begingroup\$

Or if there should just be a Building class and individual building types are instances of that class.

This. Do not use your language's type system to enforce your game logic's taxonomy. That's the kind of bad object-oriented programming nonsense for which Java programmers get ridiculed. ;)

This latter solution seems ugly because there is also the layer of individual instances of building a building type.

It's not ugly, at all. You're just confusing language types and instance with game archetypes and instances. They're different things.

The type system in your language is there to enforce interfaces. It's there to help you make sure that you don't accidentally try to draw a sound or animate a database record.

For game objects, think about the kinds of operations you have. For instance, you have an archetype of building called BuildingA. All BuildingA's look the same, have the same stats, or whatever it is your game requires. In other words, there's a bunch of properties that are shared by all BuildingA instances. Note now then that BuildingB has the same set of properties, just with different values. That is, both BuildingA and BuildingB have a sprite or mesh that is shared by all their instances but is of course different between the two building architectures.

This should sound familiar. All building archetypes have the same set of properties: that means that you can model building archetypes as a class! So instead of class BuildingA you instead have class BuildingArchetype. Your individual archetypes of buildings are object instances of that class.

auto city_hall = BuildingArchetype("City Hall", "/sprites/hall.png");
auto barracks = BuildingArchetype("Barracks", "/sprites/barracks.png");

This provides another benefit - your building archetypes, since they are just data values for a class rather than unique classes - can be defined in data files instead of code. You can allow designers or modders to add all new buildings without having to recompile your game.

You also have properties that are unique to each instance of a building archetype (any building), such as its location, or its construction completeness / hitpoints / whatever. Again, it doesn't matter which archetype a building instance uses - they all have these same sets of properties. Hence, a building instance is a second class.

class Building {
  BuildingArchetype* _archetype;
  Vector2f _position;

public:
  ... stuff ...
};

And hence each instance takes its archetype as data instead of using the language's type system, since the operations you can perform on instances of one building archetype are the same as another building archetype.

vector<Building> buildings;

buildings.emplace_back(city_hall, {1, 7}));
buildings.emplace_back(city_hall, {-5, 6}));
buildings.emplace_back(barracks, {4, 8}));

Note particularly here that all buildings - no matter the archetype - can now live in a single homogenous collection. This is pretty useful. You can just draw all buildings without having to care about what type they are, for example:

for (auto& building : buildings)
  draw_sprite(building.archetype().sprite(), building.position());

Of course, you might now be wondering about all the code bits that are in fact different for different kinds of buildings. That's the same application of this design, though. For instance, Barracks might produce Soldiers but City Hall produces clerk, and City Halls might also have a Take Loan option.

These don't need to be methods on a building class, though, and you don't want them to be - that would mean that you have to write differnet UI code for every building, and different AI code, and so on. Huge pain. Instead, a building archetype can contain a list of Action classes that define what sorts of things a building can do, and individual action sub-classes can define the logic of the action.

This is also composable as now you can reuse your actions on buildings, on units, on scenery, etc. You can make units that can make other units, or buildings that attack just like units do (e.g., guard towers can fire bullets just like soldiers can, without having to duplicate any code).

Note also the "composable" part. This is the crux of what leads into component-oriented design. Instead of hard-coding a complex game object into a single class in your code, allow your game object to be built out of many small components. There are plenty of resources online for you to Google if you're interested in that broader topic.

\$\endgroup\$
  • \$\begingroup\$ Much better than the way I was planning to implement things. What about the building dependency part? That's the main thing I can't figure out. To say that BuildingA needs BuildingB to be at a certain level to be able to upgrade seems like something I'd need to put ugly code in (like searching the list of current buildings for a building whose name property is a certain value and level property is a certain value) Ideally I'd like it to be something that references the classes instead of hard ugly logic like that. \$\endgroup\$ – David Bandel Oct 27 '16 at 1:32
  • \$\begingroup\$ @DavidBandel: a BuildingArchetype can contain a list of other archetypes and levels that they require. For building levels, do you just care what level an archetype is? You can just store a per-player hash table between archetype and max level. You could also use a sorted tree of some kind to find all buildings of a particular archetype sorted by level, or such. \$\endgroup\$ – Sean Middleditch Oct 27 '16 at 1:59
  • \$\begingroup\$ I could store the max level of a given archetype. But my question is more, how do I encode the information that buildingA requires that there is a buildingB at level 4 before it can level up to level 2. After I define the classes for buildingA and buildingB, how do I add some information to the class definition for buildingA that references the archetype buildingB \$\endgroup\$ – David Bandel Oct 27 '16 at 2:17
  • \$\begingroup\$ Once I do that I could check the hash table for the max level of buildingB. But it needs to know which archetype to check for. All solutions I've thought of are extremely messy and specific \$\endgroup\$ – David Bandel Oct 27 '16 at 2:18
  • \$\begingroup\$ Should the buildingarchetype class contain a pointer to a buildingarchetype (the one it depends on) as well as a number (for what level the archetype being pointed to needed to be)? \$\endgroup\$ – David Bandel Oct 27 '16 at 4:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.