Engine design issues, need tips

Started by
12 comments, last by jbizzler 16 years, 11 months ago
Hi I've been working on a side-scrolling 2d shooter now for quite some time using OpenGL and C++. After many many re-designs (just trying out different ideas and such) I've decided to sit down and design a solid 2d game-engine before continuing with the actual game. My idea was to make a "virtual console" as object-oriented as possible (no bashers please) which would sport a System-class containing instances of graphic, input and sound objects. The point of this was to get a feel of every class being a hardware component (graphicscard, motherboard, soundcard etc..). One would then load it with a game after which the game would start. The engine could be initiated something like this:

System machine;
machine.boot(settings);
machine.load(new Game("Mygame"));


Anyway, this design almost completely forbids the use of statics and globals and this is where my problems arise. First I tried realizing my design with GLFW but the nasty callbacks forced me to use statics (is there really no way to use instanciated methods withing the same class as callback functions?). Later on I switched to SDL which works pretty well, however, the dynamic loading and the fact that I'm almost only gonna use the framework for input makes it too bloated for my taste (not to mention the license). This leaves me confused, why should I even bother with my OOP design? It seemed nice at first but if I'm forced to use statics and global it makes no use. Another idea would be to make a hybrid solution with a few static methods in the System class allowing it to use them for callbacks and have instances for the "hardware components". However, the latter idea makes me wonder why I should use instanciated classes at all since I would only be able to run a single engine at the same time anyway. On the other side, it sounds awful to have just globals/statics, I want to be able to use constructors/destructors plus access control. I really have no idea what approach to take. I would appreciate any tips I can get :-) Thanks in advance /Mekanikles [Edited by - Mekanikles on May 2, 2007 5:02:24 PM]
Advertisement
This sounds like a pretty neat idea. I never thought of approaching design from a platform standpoint. It would make your engine behave both like a framework and virtual machine, like a super-charged AGEIA PhysX run-time. However, this would only be useful if you intend to release quite a few games utilizing the same engine in basically the same manner. You have to be absolutely certain not to break existing games when updating the engine itself, unless you plan to release a new copy of the engine separate with each game, which defeats the initial purpose.

I do think it is a neat idea. I just don't think it's practical in your case. If you plan to release a series of downloadable games that utilize the same engine, like a Popcap series, then it might be practical to install the engine once and provide only the necessary content for each individual game. For one shot deals, you probably want to roll everything into one, in which case the platform architecture will only hurt you.

OOP design however is always a good idea from design and management standpoints. If you can modularize the design, you can break it down into manageable parts and develop each component separately (divide-and-conquer). This has two benefits: it's easier to build, and it's easier to fix when something breaks. I personally feel this is the strongest aspect of OOP.

However, I don't understand what your concern is with globals and statics, inasmuch as I don't see why you'd need to use globals and statics at all. This is simply my own ignorance of what your design looks like. The best modular design contains each component of the engine such that internal data does not need to be shared between them at all, and only external structures (not appropriate terminology, but bear with me) should be passed between modules. For example, the audio, video, and input components do not care a whit for what data the others use. The machine running your game, however, must know what kind of data each component requires to operate: sprites for the video, sounds for the audio, and an input buffer for the input. For example.

Which brings to mind a final point: I have discovered through my own trials and tribulations that engine design should focus on interfaces, in other words, how one module interfaces with another. This is most certainly a "duh" concept for any experienced OO-Programmers out there, but I come from a pseudo-OOP background of VB5/6, so it took me a while to grasp the idea. Here is my basic engine design after years of iterations (I believe I finally got it right):

Virtual Machine sits at the top of the engine and manages all game-specific data. VM is responsible for storing abstract game entities and executing external scripts, which control the game's behavior. Directly beneath the VM is the LEVEL class, which represents the physical game world. Game entities in the LEVEL class have only visual/aural properties; logic is handled by the VM. LEVEL is also responsible for triggering interaction events when entities are engaging one another (e.g., sight, hearing, touching, attacking, etc.).

LEVEL has a fairly huge responsibility, in that it basically is the game, because it detects and reports all interactions between objects. It is also responsible for culling duplicate or irrelevant interactions based on how object interaction is defined (some objects interact in only certain ways) and in what order it occurs (only one interaction between two objects should be triggered when the objects meet, except in special cases like sight). LEVEL does not need to know how VM works, but it does need to know how it should report interaction events to the VM so they can be processed. This takes the form of an "event" stack that is updated and processed every X game cycles (events are sorted by priority, which is X).

LEVEL stands between the VM and the engine component modules: in my case, video and audio. LEVEL tells video/audio what to present, which means LEVEL must understand the video and audio interfaces, whereas VM does not care. VM simply tells LEVEL what objects are supposed to look like, based on their definitions in the scripts. Most communication in my engine goes down, not up. I don't promise this is the best possible design, as I've already said I'm not a real OO-programmer, but it seems to work well enough.

(Input is tied directly to LEVEL as part of the visible user interface; remember, all things pertaining to the game world's physical representation are part of LEVEL. Network is tied directly to the VM; it sits between the server VM and all client VM's. There's also the File System class, which is static and global and ties to all modules that need to load/save anything, from scripts to game states to textures and sounds. I don't like that part of the design, and I'm looking for a non-static/non-global alternative.)

Ugh. That's sort of a big "tip," but I hope it helps. Oh, and if you want some really good pointers on engine design, check out the Unreal docs that have been published by the engine's original design team (Tim Sweeney, et al). The Unreal Networking Architecture in particular has really helped me out, as it describes more than just network aspects of the engine.

GDNet+. It's only $5 a month. You know you want it.

Thanx alot for the reply Tom, I've read it through and have a few thoughts.

First of all, thanks for the encouragement, I do like it when things are overly OOP, and I'd rather not give up on my design just yet. However you talk of updating the engine without breaking the game. While I do intend to use my engine for "alot" of games it won't work as a true VM (as in interpreting code at runtime), it will just be a library for 2d-games (you will need to compile it with your game everytime). This, of course, conflicts with my design (I would love to have a machine and "rom"-format games though im not skilled enough to pull it off) but I thought it nice, from a OOP perspective, to imagine a virtual machine when designing the game. This would give the game initial constraints and make for much easier development.

As for the design structure you mentioned, I'm gunning for something similar, I'll try to explain:

The structure would basically be
Game->Specific Game Engine->Virtual Machine Engine

Where the VM would be the most "hardware close" handling drawing methods, wrapping GL, sound and input. Its interface would be the System-class.

The GameEngine would contain a bit more specific funtions like perhaps Scene-stacks, tile-engines, controllers and such. It uses the System interface only.

On top of it all would be the actual Game utilizing the GameEngine Interface.


The problem I get from using statics are obvious, I'm breaking the whole OOP structure, and if I want to use something slimmer than SDL (GLFW) I'm forced to use certain callbacks that must be statics or globals (for Window resizing among others)

I did not quite understand your LEVEL class but i think it might be something similar to my GameEngine layer.

Thanks again for the reply
I feel I have to clarify what im trying to do here :-)

I want to be able to handle events as they occur. Keypresses, mousemoves, resizes and so on. This would be handled in my System class which would then dispatch the events to the proper modules. SDL solves this by letting me poll the Events each frame and then handle them at one place. GLFW gives me the callback-system, which I think is very neat, but I cannot use instanciated methods as callbacks. My dream scenario would be to have Handler-methods in my System class as GLFWcallbacks without having them statics.

Toodles /Mekanikles

[Edited by - Mekanikles on May 2, 2007 6:49:42 PM]
I'm not sure exactly what you're looking for in terms of architecture, but it sounds like your approach is similar to what I'm currently attempting to build for my 2D engine. While I'm not aiming for a virtual machine, I'm trying to base the core of my architecture on a message passing paradigm. Under this approach, modules communicate with each other not via hard linked function calls but by signing up for messages via a message handler to a message dispatcher.

I'm only at the first stage of writing the mechanisms for tying the modules together at the moment, and I'm learning this as I go so my approach may not be the best. However I'm presently aiming to have my modules communicate through signal classes, which allow one module to send information to many others. From the calling modules perspective a signal acts a lot like a function or function pointer, but instead of being linked to one recipient it can be linked to as many as you like - even zero. The calling module shouldn't really care how many recipients it goes to; it's just letting them know there's some information that interested parties might want to learn.

For something like input, I'd have an input signal wrapped up in an interface class, which interprets the SDL input events and sends off signals representing each key press or mouse movement. Then I'd have a second input converter class that listens in on this input information and has its own set of events for game related controls; action, movement, etc., which the game modules itself can sign up to.

You'll have to forgive me if I've completely misunderstood what you're after though; I've so deep into my own engine construction right now I'm starting to see everything through that lens. I'm also not that qualified yet to speak on this approach's proper use as I'm learning what works by experimentation.

If you're interested about what I'm doing I've been rambling on about my current attempt to write a signal/slot system in my journal (link to this months entries; look back last month too for more). I've got a rough concept of the architecture posted here in JPEG form.

Useful implementations of signals are Boost.signal and sigslot. Also useful if you're wanting to play around with the guts of a signal or messaging system is a way to bind together a variety of function pointers - member functions and static (regular) functions are tricky to deal with in a unified way. For that Boost.bind and Boost.function may be useful. I'm using Don Clugston's Fast Delegate code to do the same thing, and so far it seems to be working (although I've only been using it the last couple of days).

Hope some of that is useful. Let us know how the engine goes!
Quote:Original post by Trapper Zoid
I'm using Don Clugston's Fast Delegate code to do the same thing, and so far it seems to be working (although I've only been using it the last couple of days).


Just a side note on this. I am using that Fast Delegate code in some code I'm working on with friends. We have been using it a while now, and have matched C# style event/delegate syntax for message passing with minimal effort (and minimal overhead). So far it is working great, and doesn't come with any of the dependency chains that are inherent in Boost libs. At the time I made the decision, I also looked at Boost::function, but Fast Delegate just seemed cleaner to me. Definitely worth checking out.

Hmm, it would seem that delegates is the exact thing I'm looking for. Up 'til now I've shunned C++ function pointers due to the awful syntax but I'm reading into it as I write this. A few questions linger though: How much slower would delegates be? Is it viable to do as input callback without loosing too much speed? (around 30 times/sec) and does it require compiler-specific code? (trying to code patform-independent)

Thanx alot /Mekanikles
Quote:Original post by Mekanikles A few questions linger though: How much slower would delegates be? Is it viable to do as input callback without loosing too much speed? (around 30 times/sec) and does it require compiler-specific code? (trying to code patform-independent)


That is really the beauty of the Fast Delegate implementation. It does use compiler-specific code, but it has this code implemented on pretty much any compiler you throw it at. Currently the project I am part of is compiling with no problems on both VC8 and g++. As far as speed goes, if I remember from the article explaining the implementation, a call to a FastDelegate generates possibly 1 more assembly instruction than a direct function call does. What this boils down to is a very fast implementation of delegates. I believe it outperforms boost::function, but don't quote me on that.

You should really read the article, as it goes into great detail on how the code is actually implemented, and how it was developed. It is an interesting read.
I'm not sure exactly how delegates work, but if you wanted to remove the function pointer callbacks, you could use virtual classes. I.e:

class InputHandler{public:    InputHandler (void);    virtual ~InputHandler (void);    // return whether the event was used or not    virtual bool onMousePress (...) { return false; }    virtual bool onMouseRelease (...) { return false; }    virtual bool onMouseMove (...) { return false; }    virtual bool onKeyboardPress (...) { return false; }    virtual bool onKeyboardRelease (...) { return false; }    virtual bool onJoystick... (...) { return false; }    // etc etc};class Game{public:    Game (void);    virtual ~Game (void); // system would destroy it?    // load the 'cartridge' (initialisation)    virtual bool load (void);    // run the 'cartridge'    virtual bool run (void);    // unload the 'cartridge'    virtual bool unload (void);};


Then if you really wanted to do the whole cartridge being loaded by a separate exe of the engine, you could have the game build into a dll with a callback or two which would then be loaded by the engine exe..

So in the cartridge loading app you could have:

typedef Game * (* CreateGameObjectCallback)(SystemSettings *);int main (int argc, char *argv[]){    Game * game = 0;    HMODULE hGame = 0;    SystemSettings settings = 0;    if (argc > 1) {        // load 2nd argument as the game dll        hGame = LoadLibrary (argv[1]);        if (!hGame) {            printf ("Loading cartridge '%s' failed. Exiting...", argv[1]);            return 1;        }        CreateGameObjectCallback createGameObject = (CreateGameObjectCallback) GetProcAddress (hGame, "createGameObject");        if (!cb) {            printf ("Loading cartridge '%s' failed. Exiting...", argv[1]);            FreeLibrary (hGame);            return 1;        }                // request to setup settings structure and create game object        game = createGameObject (&settings);        if (!game) {            printf ("Loading cartridge '%s' failed. Exiting...", argv[1]);            FreeLibrary (hGame);            return 1;        }    }    System system;    system.boot (settings);    system.load (game);    FreeLibrary (hGame);    return 0;   }


That code is for Win32 DLLs. I'm not sure about cross-platform stuff.

Just some ideas to throw around...
thre3dee: I didn't quite understand what you meant by the first code snippet there, but I think you might mean something else than what I'm after but the DLL-Cartridge thing was a great idea! I'll definetly look into that in a later design stage.

Dranith: Thanx, I've read it through now, and while I've gotten the hang of it some more, I'm not quite sure it solved my problem still :-(

You see, even if I can successfully send a member function pointer to my GLFW-class I still need to convert it to something the GLFW callbacks can handle (GLFWfun). GLFWfuns seems to be just void* with correct parameters since I can easily just cast an ordinary void funtion pointer to it. However when i try to send a member function pointer, it complains about different calling conventions (__thiscall). Is there a way to extract the function pointer from a delegate and use it as if it were a non-member function?

I realize this might be impossible and am therefore currently looking into modifying the GLFW source to incorporate a SDLish Event queue.


Thanx again for the responses /Mekanikles

This topic is closed to new replies.

Advertisement