Every thing you mention is something that can be specified in data. Why are you loading
aspecificmap ? Because the game configuration says that is first level when a player starts a new game, or because that's the name of the current save point in the player's save file they just loaded, etc.
How do you find
aspecificmap ? Because it's in a data file that lists map ids and their on-disk resources.
There need only be a particularly small set of "core" resources which are legitimately hard or impossible to avoid hard-coding. With a bit of work, this can be limited to a single hard-coded default asset name like
main.wad or the like. This file can potentially be changed at runtime by passing a command-line argument to the game, aka
game.exe -wad mymain.wad.
Writing data-driven code relies on a few other principles. For instance, one can avoid having systems or modules ask for a particular resource and instead invert those dependencies. That is, don't make
DebugDrawer load up
debug.font in its initialization code; instead, have
DebugDrawer take a resource handle in its initialization code. That handle might be loaded from the main game configuration file.
As a concrete examples from our codebase, we have a "global data" object that is loaded from the resource database (which itself is by default the
./resources folder but can be overloaded with a command line argument). The resource databse ID of this global data is the only necessary hard-coded resource name in the codebase (we have others because sometimes programmers get lazy, but we generally end up fixing/removing those eventually). This global data object is full of components whose sole purpose is to provide configuration data. One of the component is the UI Global Data component which contains resource handles to all the main UI resources (fonts, Flash files, icons, localization data, etc.) among a number of other configuration items. When a UI developer decides to rename the main UI asset from
/ui/lobby.swf they just update that global data reference; no engine code needs to change at all.
We use this global data for everything. All the playable characters, all the levels, UI, audio, core assets, network configuration, everything. (well, not everything, but those other things are bugs to be fixed.)
This approach has a lot of other advantages. For one, it makes resource packing and bundling integral to the whole process. Hard-coding paths in the engine also tends to be mean that those same paths have to be hard-coded in whatever scripts or tools package up game assets, and those paths can then get out of sync. Relying instead on a single core asset and reference chains from there, we can build an asset bundle with a single command like
bundle.exe -root config.data -out main.wad and know that it will include all the assets we require. Further, since the bundler would just be following resource references, we know that it will include only the assets that we require and skip all the left-over fluff that inevitably accumulates over the life of a project (plus we can automatically generate lists of that fluff for pruning).
A tricky corner case of this whole thing is in scripts. Making the engine data-driven is easy conceptually, but I've seen so very many projects (hobby to AAA) where scripts are considered data and hence are "allowed" to just use resource paths indiscriminately. Don't do that. If a Lua file needs a resource and it just calls a function like
textures.lua("/path/to/texture.png") then the asset pipeline is going to have a lot of trouble knowing that the script requires
/path/to/texture.png to operate correctly and might consider that texture to be unused and unnecessary. Scripts should be treated like any other code: any data they need including resources or tables should be specified in a configuration entry that the engine and resource pipeline can inspect for dependencies. The data that says "load script
foo.lua" instead should say "load
foo.lua and give it these parameters" where the parameters include any resources necessary. If a script randomly spawns enemies for example, pass the list of possible enemies into the script from that configuration file. The engine can then pre-load the enemies with the level (since it knows the complete list of possible spawns) and the resource pipeline knows to bundle all the enemies with the game (since they're definitively referenced by configuration data). If the scripts generates strings of path names and just calls a
load function then neither the engine nor resource pipeline have any way of knowing specifically which assets the script might try to load.