An oo-ish version of my typical game architecture

posted in Gamedev info
Published January 26, 2015
Advertisement
An oo-ish version of my typical game architecture


all one code block, i was too lazy to split it up for posting ! .

non-oo: void main{init_prog run_proguninit_prog}globals: boolean: quit_progetc. oo: class program{globals such as quitproginit becodes constrctoruninit becomes destructorvoid run_prog method}; non-oo: runprog{while ! quitprog { switch main_menu() { case CONTINUE: load last saved game rungame break; case TUTOR: init tutorial game rungame break; case NEW: init new game rungame break; case LOAD: load saved game rungame break; case TOOLS: run tools menu break; case QUIT: quitprog=1; break; }} oo-ish: program.run{boolean quitprogwhile ! quitprog { switch main_menu() { case CONTINUE: load last saved game game.run break; case TUTOR: init tutorial game game.run break; case NEW: init new game game.run break; case LOAD: load saved game game.run break; case TOOLS: run tools menu break; case QUIT: quitprog=TRUE; break; }} non oo: init_blank_gameload_last | init tutor | init new | load savedrungameuninit_game oo-ish: class game{boolean quitgamemethods:init_blank // private - possibly can go in constructorload_lastinit_tutorinit_newload_savedrun // IE rungameuninit - desctructor} non-oo: void rungame{ while ! quitgame { render_all process_input update_all limit_framerate // i prefer the simplicty of framerate // limiters. f-y-t is another viable option. }} oo-ish: game.run{boolean quitgamequitgame = FALSEwhile ! quitgame { game_specific_renderer.render_all input.process entity_list.update_all world.update_all }} non-oo: render_all (typical){clearscreenbegin_scenedraw_skybox clear_render_queuedraw_all_entitiesdraw_terraindraw_render_queueend_scenepresent} oo: game_specific_renderer.render_all{generic_graphics_engine.clearscreengeneric_graphics_engine.begin_scenegeneric_graphics_engine.clear_render_queueworld.render_all // includes skyboxenitity_list.render_allgeneric_graphics_engine.draw_render_queuegeneric_graphics_engine.end_scenegeneric_graphics_engine.present} non-oo: void process_input{// run windows message pump. captures mouse input.// i use get async keystate for keyboard, but windows// messages can also be used for capturing keyboard input.process_windows_messages();getmouse(&x,&y,&b);mouse_aim_camera(x,y);if (b==1) { // left mouse button pressed, do something. }else if (b==2) { // right mouse button pressed, do something. }if keypressed(VK_somecode) { // some key is pressed, do something. }if keypressed(VK_a_different_code) { // some key toher is pressed, do something else. }if keypressed(VK_and_so_on) { // i think you get the idea! . }} oo-ish: input.process{int x,y,b;generic_game_library.process_windows_messages()generic_game_library.getmouse(&x,&y,&b)camera.mouse_aim(x,y)if (b==1) { // left mouse button pressed, do something. }else if (b==2) { // right mouse button pressed, do something. }if generic_game_library.keypressed(VK_somecode) { // some key is pressed, do something. } etc. } once might also have a mouse object: mouse.get(&x,&y,&b)mouse.set(x,y)mouse.reset()etc. i'm discovering in this "port to oo" that generally, stand alone fuctions ought to go in some sort of object.i've been using the same basic architechture for years, so the basic datastructures and functions are already pretty well known at least in generalterms. so its mostly just a matter of grouping related data structures and fuctions into appropriate classes. but things that pretty much every game needs probably ought to be in a single module, as opposed to a bunch of little modules for each thing like mouse, camera, timers, etc. thats why i use the generic_game_library.getmouse syntax in the example above. you might think of generic_game_library as the top level API for all the generic game modules used such as the genric graphics engine, asset pools, generic audio system,timers, etc. really the way to do it in layers:1. have a module for each thing like timers, camera, etc.2. have higher level modules for similar things like a graphics module and an audio module.3. have a top level API (gamelib API) that uses the lower level modules. that way you can include just what you want, or the whole shebang. non-oo: void update_all{for each entity { update_entity(i) }update_world_stuff()} oo-ish:entity_list.update_all{for each entity { call entity_type[entity.type].update_function }} i use a "relational database" pattern for storing entity info and entity type info. entity variables such as location and damage are stored in entity structs or objects. these are entries in the entities_list. data which is the same for all entities of a given type is stored in entity_type structs or objects. these are entries in the entity_types list. each entity has a type - the index of its entry in the entity_types list. examples of this type of data would includedrawing info, max speeds, max turn rates, base hit points, base armor class, base chance to hit, etc. IE stuff that doesn't change from one Orc to the next for example. world.update_all{// update all world objects as needed.// things like remove dead bodies, model weather, // periodically reset spawn points, etc.} ----------------------------------------------------- so this gives us the following game specific classes:note that where i specify just one instance required, i mean for typical applications. some games may want more than one instance of some things. i personallytend to use two entity lists, one for targets, and one for missiles. entity (multiple instances) entity_type (multiple instances) entity_list (one instance)entity_types_list (one instance)program_object (one instance)game_object (one instance)game_specific_renderer (one instance)game_specific_input_handler (one instance)world_object (one instance)mission_object or level_object - if mission / level based game (one instance) and then we have the generic_game_library class (one instance).it includes:generic_graphics_engine (one instance)generic_audio_system (one instance)timers, random number gens, etc. the graphics engine includes: mesh, texture, model, and animation asset pool objects. the audio system include a wav asset pool object. program.run runs the main menu (new game, load game, etc) game.run is the main loop. in a mission/level based game, game.run is the "between missions" menu, with one or more menu options that call mission.run, which is the main game loop. some exmple class methods required:--------------------------------------------------------- world object:init renderupdate audio system:inituninitplaywavloopwavstopwavetc entity_list:render_allupdate_all entity_types_list:init asset object:loadunloadset texture etc. asset pool:initload_allunload_alluninitetc note: init and uninit methods might be placed in constuctors and destructors,or they might be explicit methods to allow for object reuse without having todestroy and recreate the object. ownership and scope issues-------------------------------------------------------------- i typically try to load all assets and allocate and initialize all resources one time at program start in init_prog, and shut everything down in reverse order in uninit_prog. but this means the program object will own the game_library,entity_list, and world object (if the world is the same every game), and will have to pass them to game.run() to avoid globals. i could simply new and dispose them each time i start a game, and let the game object own them, but that's not efficient due to unncessary memory allocations and deallocations. so in general to avoid globals while going oo, i'll need to passwhat used to be globals around as parameters. this could lead to long praramter lists. the plan is to borrow apage from directx, and pass long parameter lists in structs passedby reference. this way i can have object ownership at a higher level where reuse is possible, and not have to deal with an ugly API when calling lower level methods that i must pass high level objects (IE global data structures and api's) to. example: struct game_params{generic_game_library *gamelib;game_specific_renderer *renderer;input_handler *input;entity_list *elist;world_object *world; // if world is the same every game}; program.run{/*new() all program level objects:generic_game_librarygame_specific_rendererinput_handlerentity_listworld_object // if world is the same every game*/// set params for call to game.run....game_params gp;gp.gamelib=program.generic_game_librarygp.renderer=program.game_specific_renderergp.input=program.input_handlergp.elist=program.entity_listgp.world=program.world // if world is the same every game// call game.rungame.run(&gp)} delving deeper:---------------------------------------------- entity.render{if !camera.cansee(entity.location) return;add_to_render_queue(entity_type[entity.type].drawinfo, entity.location);} entity_type.update:{if the entity type is not player, do AI.// player input takes care of AI for their entityapply update method.} data structures:-----------------------------------------------------the entities_list, entity_types_list, and asset pools would most likelyuse std:vectors. member variables of entity and entity type objects would vary depending on the game in question, as would entity_type update methods. entity type info----------------------------------------------------------- traditionally i've used a relational database type design,with an entities list, and an entity_types list. it seems that this is no longer necessary. back in the day it was unthinkable to replicate entity type data in each instance of an entity.now there RAM to spare, and cache misses from having type info elsewhere is the big issue these days. this calls for an extremly minor change to the standard architechture. the enitity_typesdatabase stays, but now each enity object also contains all the member variables in an entity_type. when an entity is created, instead of setting its type, you copy its type info into it.when assessing type info, you use tgt.whatever, not tgttype[tgt.type].whatever. summary:--------------------------------------------------------use of a generic game library reduces the coding requiredto the following: 1. program object - init game lib etc, run main menu, shutdown.2. game object - init a game, run a game (main game loop)3. game_specific_renderer - draw a scene4. input - process mouse and keys5. entity_list - update code (including AI) and drawing info. entity type data should be data driven for non-trivial projects. a generic game library can also help with these tasks: 1. it includes a generic menu routine that takes background and mouse pointer textures as paramaters. this can be used for the main menu and any in game menus.2. it includes file i/o methods for loading and saving games. it includes timers for use in framerate limiting the main loop (if desired), as well as for other uses in the game.3. it includes a generic skybox drawing routine.4. a generic keyboard mapper could be added to the game library for help with input.5. the library includes AI routines for a built-in entities_list which could be made generic for use with any entity. due to the game specific nature of entity lists, i suspect generic entity lists are not the best way to go. case in point: after finishing my generic entity list system, complete with AI, i discovered that my next game would require an entirely different update method, due to storing world coordinates differently due to the large game world size. Since then, i've started yet another game with an even bigger game world with yet another method of storing world cooordinates.
0 likes 0 comments

Comments

Nobody has left a comment. You can be the first!
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement