A couple of truism: Simple choices can have a wide, cascading effect. The design of your game’s root classes can effect every other piece of code in game.
I’m porting my python dungeon crawler over to java, and am trying to get it up and running one small piece at a time. For the first time I’m trying out true unit tests; trying to get each piece debugged before setting the whole thing going as one.
I’m currently faced with a deceptively simple choice: should a “Room” class contain a list of pointers to “Actor” objects?
If you feel the answer is: yes, of course Rooms should contain Actors, you probably consider yourself a McCoy programmer: a programmer who favors simple, easy to understand code, that executes quickly.
If you say: good god no, you probably consider yourself a good engineer, a person who creates robust code for the long haul, a purely Hatfield programmer.
Behind their back McCoy programmers get called hackers, spaghetti programmers, baby killers; Hatfield programmers get called language lawyers, code obfuscators, Bush administration supporters. Even today the Hatfields and the McCoys don’t get along.
If you are unlucky: you will have both ardent Hatfields and ardent McCoys on your team, both sides hell bound to have their way. Debates over simple choices will rage for weeks, no code will actually get written, your game will get cancelled, and you’ll be forced to live out your days penniless.
If you’re really unlucky you’ll have someone who professes to be a Hatfield but codes like a McCoy. This person will know just what to do, will spend two weeks coding something “abstract” and “decoupled” that’s 6000 lines long, distributed across eight object classes, and uses string compares to look up actors. Your game will ship and become wildly successful. They will get a promotion, while you will have to spend the rest of your career maintaining something so fragile that it breaks every time a stray neutrino passes near.
Fundamentally, there is no right answer to the question I’ve posed. Software is pliable, and it allows you to make whatever choices you want. When “mu” choices come up I generally just take my best most educated guess and move on, correcting bad choices later on.
I justify this with a personal tenet that programmers need to get code into customers’ hands as quickly as reasonably possible. ( The quality level of “reasonable” and the speed of “quick” being up to endless debate on a case by case basis ).
My python code therefore is currently McCoy like – it has bi-directional pointers, Rooms hold Actors, Actors track their parent room. Unfortunately, both my Actor and my Room classes have become top heavy: if I port one, I have to port the other.
I am at a cross-roads then, I can:
- port both together
- create abstract base classes for each to lighten the load, then port each in turn.
- separate the two from each other entirely.
The land of databases sheds some light: If I were to construct these as tables, I never would have been able to have had one complex class contain lists of another complex class – I’d have to an Actor table, a Room table, and a third, ActorsInRooms, table.
Taking that metaphor forward: it’s easily possible to create the ActorsInRooms table up front as small and as flat as the list of the Rooms itself can be. For a static per level set of rooms: a simple level load time allocated array – indexed by room id – will be just as quick (I’m throwing cache to the wind here) and just as small as a direct in Room list of actors.
Later, if the Rooms need to get Items, Lights, Fixtures, etc. We have a nice simple model that allows for fast. compact, lookup when needed without bogging down the Room object model.
The downside may be that, if Rooms become dynamic, that list may have to become a hash at some point – but, down that route, the Rooms themselves will need to go in a hash, and so perhaps, it will be possible to create a shared central lookup.
At any rate: here’s a rule of thumb I advocate that isn’t repeated enough in code design books, classes, discussions, etc. Keep your classes SMALL. Try to keep per class responsibilities down. If classes start to get big, side step that increase by introducing simple relationship objects that can help express connections between objects.
Most importantly, however, revise and streamline your class design as you go. Don’t worry about abstract choices too much. Instead, focus on getting reasonable code in your customers’ hands quickly. Later, as you learn about the issues your code needs to handle, take the time to make your code better.