Jump to content
  • Advertisement
Sign in to follow this  
hallgeir

A little help on designing my game code

This topic is 4172 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi. I was just wondering - what would you think is the "best" way to store all objects in the game (like enemies, players, other things)? I have been working on a 2D game engine, which should be up and running by now, and it's time to start thinking of making the actual game. An example of a problem I have encountered in previous experiences is: I had one "Engine" class. That class had for instance an array where it could store all objects in the game (an array of a class called CObject, which had basic elements like position, sprite, speed vector etc.). The problem with that approach was that if I for instance wanted an object (let's say a space ship) to shoot a projectile somewhere: I made a member function called void CShip::Shoot(). In that function I would make a pointer to a new instance of a CObject (or actually a CProjectile, which was derived from CObject), set its parameters like damage and speed. Then I would want to add it to the array in the Engine class, the class that simply renders the whole array of CObjects. But to get there I would need to make the instance of Engine a global variable, and then call TheEngine.AddObject(pProjectile); for instance. Making the instance of the engine class a global variable and call it like I did there doesn't seem like a very good objective oriented approach to me, so I'm asking if you got any suggestions on how I could store all objects in the game, AND allow CObjects to be able to "create" new objects (like in my shooting example). I hope I explained my problem clearly enough.. any tips are very welcome, even if it means that I have to rethink the whole "Engine class" approach. If there's something I haven't explained good enough, please let me know and I'll try to clear it up as soon as possible. Thanks in advance. :)

Share this post


Link to post
Share on other sites
Advertisement
I sorta had a similar problem like that. I solved with virtual function.

example:
virtual void update(float deltaTime);

I had this in my base class. Every other class that inherented the base class had it's own code in the update function.

All I had to do is loop through all the objects in the object array (I used a vector) and call update() for each one.

If this won't work for you, I can only say that using event handlers would do the job.

Not sure if this is what you're looking for. hope it helps.

Share this post


Link to post
Share on other sites
Quote:
Original post by Farraj
I sorta had a similar problem like that. I solved with virtual function.

example:
virtual void update(float deltaTime);

I had this in my base class. Every other class that inherented the base class had it's own code in the update function.

All I had to do is loop through all the objects in the object array (I used a vector) and call update() for each one.

If this won't work for you, I can only say that using event handlers would do the job.

Not sure if this is what you're looking for. hope it helps.


This is exactly what I did in my previous little project. :) I made a virtual void OnRun() function in the CObject class, which I could use to make specialized code for each derived version of CObject which is supposed to run every frame.
The problem however is not this. My problem is that my old way of letting objects create other objects (like when I want one object to shoot a projectile somewhere) seemed like a horrible solution, since it involved making the instance of the Engine class a global variable. I was thinking - there MUST be a better way of handling this?

Thanks anyhow for your answer. Further tips are more than welcome. :)

Share this post


Link to post
Share on other sites
Have a Factory class, that lets you create objects given their initial properties.

It's basically delegating the part of the engine that create things to another class, so you don;t have to expose the whole engine class, just one of its module. Upon creation, the objects store the pointer to their factory (most likely a single instance for the whole game), and use that to instanciate objects. The factory does the dirty job of exposing the new object to the engine (render list, update list, register some callbacks for him...).

Share this post


Link to post
Share on other sites
*repeats above*

Factory classes are the future, as well as allowing you to determine exactly what object you return, and allowing you to hide the irrelevant implementation from the host, they make it possible to manage and monitor your engine's memory usage.

Share this post


Link to post
Share on other sites
The idea about having a factory class sounds like a good plan! I'll try to find something to read about it so I get it right. :)

Thanks a lot all who answered - think I got what I was looking for!

Share this post


Link to post
Share on other sites
Quote:
Original post by ziggwarth
The idea about having a factory class sounds like a good plan! I'll try to find something to read about it so I get it right. :)

Thanks a lot all who answered - think I got what I was looking for!


Here's how my engine is structured (perhaps this will help you):

== Objects ==

There is an Object class from which all important objects derive (Map, Sprite, Screen). The unimportant objects are things like AnimationFrame (owned by Animation which is derived from Resource which is derived from Object), or SpriteSheetCell (owned by SpriteSheet which is derived from Resource, which is derived from Object), or Tile (not derived from anything). Those unimportant objects are typically extremely simple, just one step above a struct, and do not contain virtual functions. The important objects are very complex, some have event handlers and support dynamic creation, etc.

The Object class the following members: a reference to the engine set on construction and the object type enum. The reference to the engine is passed to the constructor by whoever creates a class derived from Object. Only the Engine class and another class derived from Object are capable of creating an Object, so no problem here (an exception is made for certain unimportant objects that are containers, which must contain an explicit reference to the engine in order to create important objects). Object also contains two Serialize functions: one for loading from a path string, and one for loading from a Stream. The function that loads from path simply creates a stream, opens it for a file, and calls the second Serialize that loads from stream. Object class also has a Name member for storing the name of the object (all classes that are important and therefore derive from Object require having a name, so Name is placed in Object). The last memeber is Flags, which is a DWORD. All important objects have flags, with actual values varying depending on the type of object. Object also has a Clear() function (nonvirtual) to indicate that this is the way to clean up Objects without destroying them. Last but not least, Object has a virtual destructor = 0, which makes Object a base class only.

== Resources ==

Resources (all derived from Object) are loaded and managed by ResourceMap template class. Engine class contains an instance of ResourceMap for each resource type, with interface functions like GetTextures(), GetSounds(), etc. So you can load a texture from inside an Object-derived class like so: pMyTexture = m_rEngine.GetTextures().Load(L"c:\\mytexture.bmp"); Of course, ResourceMap also makes sure only one resource with a specific file path is loaded at the same time, to prevent duplication (uses hashing for fast search). Note that ResourceMap is not derived from Object because it's not complicated and large enough, and therefore considered unimportant. It does contain a reference to the Engine, because it must be able to create Resources and pass a reference to the Engine to their constructors.

The following classes are derived from Resource: Texture, TextureSheet, TextureFont, Animation, Model (I choose to call AnimationSet a Model because it sounds cooler), Sound, Music, RegionFile (contains collision data).

TextureSheet owns: TextureCell(s)
Animation owns: AnimationFrame(s)
Model owns: Animation(s)
RegionFile owns: Region(s)

== Maps ==

While Maps should be Resources->Objects, they aren't - they're just Objects. That's because Maps support dynamic creation from a class name. This is implemented to allow a basic form of scripting, where scripts are compiled C++ code. The entire client application (the game that uses the engine) is basically a collection of those scripts, in which the handlers are called by the Engine components, or by each other. To create your own handlers for a Map, for example a handler that runs each time the Map is updated, you would derive a class from Map, such as MapTownSquare. Then you can override all virtual functions and insert your own code for functions like OnCameraMove(), OnSerialize(), Render(), Update(), etc. Moreover, the class name can contain a filename of a DLL file and the name of an exported function to call to create the class. This makes external scripting modules possible, so you don't have to recompile the main game executable just because you want to mod an extra weapon with special scripting. Dynamic creation is handled by the engine, through CreateFromClassKey() function. It searches through all registered class names, and when it finds one, it makes a call to the function pointer associated with a class names. That function pointer would point to a global function in client code that creates a class based on class name and passes the pointer back.

== Sprites ==

Map owns Sprite(s). Sprite is a base-only class derived from Object, and also supports dynamic creation in the manner described above. Sprite contains virtuals such as Render(), Update(), OnSerialize(), etc. It also contains code that helps to place sprites into world database, in order to run world traces for collision detection, visibility detection, and Z-order sorting.

Brush and Actor are derived from Sprite. The reason for the separation is load-time performance. Brushes are part of Map, stuff like trees, walls, world detail objects. Actors are dynamic things, like player and npcs. So when the map is serialized, Brushes write 32-bit indexes into Map's resource tables (and Tiles do too). Actors write names of resources. Obviously, 32-bit indices take less space - but there is a penalty of having to add, for example, a texturesheet to the map's resource table before a Brush can use it. The reason an index is needed is because maps can have more than one texture sheet for tiles and sprites - most other 2d engine only allow one "tileset" for a map. Actors can be created more easily, but take more space and more time to load.

== Helper Objects ==

Certain helper objects not derived from Object, as was mentioned, need to have a reference to the Engine in order to create Objects. Those helper objects are mostly simple containers, like ResourceMap or SpriteList.

== GUI ==

GUI in my engine works through the use of Screen class, derived from Object. Engine contains a ScreenList container helper object, which manages Screens, exposed through Engine.GetScreens(). Engine also contains interfaces for screen management, such as Get/SetActiveScreen(), Get/SetFocusScreen(), Get/SetCaptureScreen(), LoadScreen(), etc. Screen takes care of Z-ordering itself, so on each frame the screenlist is traversed to render all loaded GUI. Engine also has its own windowproc, through which is generates events for Screens and for Maps. Screens of course support dynamic creation and are loaded from XML-like text files.

== External Scripting ==

My engine supports two ways of scripting - the very fast, compiled C++ code called through virtual functions as was mentioned, and the slow, processed-in-realtime text scripts, used for once-only stuff like GUI button-click scripts, startup scripts, config scripts, etc. Those text scripts can set the value of Variables (a VariableMap container helper class is present in both Map class, for Variables local to the Map (like gravity, or checkpoint_passed), and for the Engine class, for global Variables (like game_difficulty, etc). Variables have functions to read them from a string (dynamically determining type), or to read them directly from a binary stream in which case type is already specified.

Text scripts can also call Commands, which are pointers to global functions in the client code. Commands have to be registered, just like dynamic creation functions, and can be called from external DLLs given DLL name and function name, just like dynamic creation functions. Commands can have up to 32 params (all of them Strings), and a reference to the engine is passed as the first one. CommandMap helper class contains registered commands, and has functions for Executing statements. A Statement is either a command call or a variable get/set. I support goto's for jumping around to labels, and return's for immediately stopping the execution of a script. Expressions, decisions, and loops are not supported.

== Engine ==

Each time Tick() is called:

1. Calculate frame time using floating-point timing in seconds, taken from QueryPerformanceCounter.

2. Calculate current FPS.

3. Update engine time (time since session start) by adding frame time to it

4. Iterate through all registered Timers, and execute the ones that need to be executed. Timers send events only to Screens (GUI), just for GUI stuff like fading in, etc.

5. Iterate through all currently playing SoundInstances (played with DirectSound), and check if any of them have stopped playing and need to be deallocated.

6. Update game time. If game is paused, then don't go past step 5.

7. Iterate through all loaded Maps, and call Update() for each one.

Each Map then calls Update() for all Brushes and all Actors, and for all visible Tiles. Map's client code, if injected, is called.

Update() code for Brushes and Actors first calls Sprite Update() to update the animation, if they are animated. Then client code, if injected, processes AI and physics when called.

Update() code for Tiles updates their animation, if animated, and exits. Tiles don't have handlers.

----

Each time Render() is called:

1. Render the current map. Even though multiple maps can be loaded, only one can be current. Map renders every visible tile, cached into a buffer when camera last moved. Map renders every visible sprite (includes Brushes and Actors), that are added to a linked list and sorted by Z when camera moves.

2. Render all GUI.

3. Render custom cursor, if custom cursor is enabled.

----

So that's it. All the important design stuff.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!