If I got a dollar every time I stated some idea about the “definitive best way to tackle a problem” only to recant shortly thereafter I could probably afford a nice car (but not the gasoline to put in it, I am in Europe). Nevertheless, I keep going. So here is my current attempt at finding the one code design principle to rule them all.
My background lies in writing from mid-sized to very small games based on mostly static content, with programming teams ranging from five people to just me and time frames of two years to a few months. The approach I will discuss is completely unsuitable for large content-driven games, so if you are working at Media Molecule get ready for some heavy facepalming.
In a perfect world, once pre-production is done you would know exactly what will make it into the game so you could come up with a nice design and naming scheme for everything and stick to it. But in the real world of small scale development, game content and design are a moving target, and we all learned to think flexible so changes can be made quickly.
So everything that gets thrown at the game has to be integrated with little modifications, and there are basically two ways of doing it: hard-code each case or data-drive the whole thing. I guess we all started programming at some point with the first approach (hopefully not in a commercial environment) and quickly realized its shortcomings. Falling for it is still a very important stepping stone in the process of becoming a programmer, but dealing with a massive amount of ad-hoc code for every element of the game is quite hard to manage in the long run.
Being data-driven is usually considered a good thing, the ability to change the content without even recompiling is a huge win. But things quickly get complicated when the content lacks uniformity. Way too often I found myself dealing with large code bases where everything was spewed from a generic factory creating container components full of references and ids to other components, and where everything is named “object” or “process”. And when going from a mesh to its texture or from an AI controller to its visual manifestation on screen is an excruciating journey into debug hell, you might regret your early attempts at programming where the player mesh was named “PlayerMesh”.
So what is my current approach? Screw run-time flexibility, I use #define everywhere.
1 2 3 4 5 6 7 8 9
#define GAME_ASSET( _ASSET_ ) \ GameAssets._ASSET_.mesh = LoadMesh( "data/meshes/" #_ASSET_ ".obj" ); \ GameAssets._ASSET_.texture = LoadTexture( "data/textures/" #_ASSET_ "_diffuse.dds" ); GAME_ASSET( player_car ) GAME_ASSET( airplane ) GAME_ASSET( mighty_rabbit ) #undef GAME_ASSET
And I just use the same pattern every time I need to repeat some action on a list of gameplay elements. So in a lot of places where I would have used an array with arbitrary indices requiring a lookup to figure them out, I now have named variables that immediately make sense in the debugger. I also use it a lot for dealing with enums, array indices and scripting interfaces.
That implies that most variables in code have the same name as resources, but that’s fine, it forces cohesive naming. That also implies that new data cannot be added without modifying the code, and this can be a huge impediment depending on the kind of project you are working on, but on my current projects that compile in the snap of a finger and where artists and coders work in close cooperation it was never a bottleneck. Finally, it requires you to go through a few places in code to propagate the addition or modification of a resource, but so far it never was more than a matter of minutes, and the comfort added to debugging saved me much more.
For now the drawbacks I see are the compiled code getting bigger that it would with a proper data-driven approach, but it depends a lot on the amount of items you are dealing with. And there is also the risk of having to debug inside the macros, which feels like poking in the dark, but this can be avoided by delegating dangerous stuff outside the defines. So I think I’ll keep going in that direction for a while, until my new shiny silver bullet disintegrates mid-air.