🔒 Locked
Designing my new engine
Advertisement
Original Post
Hello, I have a little design question. I'm currently designing my new game engine after my old engine became really hard to adapt to all the new technology. My question is: I'm using a class to represent the engine as a component itself. I want to use this class to communicate between all the different parts that take care of the graphics / sound / gameplay / physics, without having to use global variables, functions etc... How would be the best way to implement this? Could I use this for instance: class Engine { // ... Engine functions + interfaces to other parts like graphics / sound classes }; class Sound : Engine { // ... Sound functions }; The problem with this though is that although it solves the communication problem, not all the functions from the engine should be available to it's child classes (like Engine::Shutdown(), a part shouldn't take the whole engine down), but these functions must be publicly available to use them in main(). Thanx for any help, Jeroen
First of all, why start over (I also recommend you read my article there about writing games, and not engines, it's linked under the "Advice" heading)? Second of all, this is a poor design.
Inheritence is unwarranted here -- it doesn't make sense to say "a Sound is an Engine." Furthermore, you're only enabling communication lines you don't need; as you noted, this gives your Sound and other subsystems direct access to engine internal details they don't require.
You're overthinking. You don't need an "engine" object at all. Each unrelated subsystem (sound, graphics, AI, whatever) can exist on its own, there's no need for a single class to sit around and simply aggregate instances of each. That doesn't solve any problems, it just adds a layer of obfuscation and conditions you to think in a centralized mindset that encourages the development of unneccessary dependancies.
Sound, graphics, et cetera don't need to communicate with eachother, there's no reason to store them with eachother, unless the game being implemented with them requires some form of cross-system communication (as many games often do) -- however, in that case, it should be the game that makes this distinction, not your engine code.
Inheritence is unwarranted here -- it doesn't make sense to say "a Sound is an Engine." Furthermore, you're only enabling communication lines you don't need; as you noted, this gives your Sound and other subsystems direct access to engine internal details they don't require.
You're overthinking. You don't need an "engine" object at all. Each unrelated subsystem (sound, graphics, AI, whatever) can exist on its own, there's no need for a single class to sit around and simply aggregate instances of each. That doesn't solve any problems, it just adds a layer of obfuscation and conditions you to think in a centralized mindset that encourages the development of unneccessary dependancies.
Sound, graphics, et cetera don't need to communicate with eachother, there's no reason to store them with eachother, unless the game being implemented with them requires some form of cross-system communication (as many games often do) -- however, in that case, it should be the game that makes this distinction, not your engine code.
Thanks for the quick reply!
I would certainly agree with the article you recommended, if it weren't for the fact that I started writing the previous engine back in 2002, when I was exactly 12 years old and had no experience at all. A rewrite from zero is definitely the best option here. I've learned a lot since then: I've written quite a few 2D games and apps and I wrote a 3D walktrough game of my school. (from which I graduated now) On top of that I've also written the demo for Parallel Split Shadowmaps in OpenGL. So I suppose I'm not a complete noob anymore. I've also switched from Windows to Linux now and I'd like my engine to be cross-platform. (I use Qt and OpenGL)
I can see the problem with my design that you mention in your post. Indeed, as you so said, a sound is not an engine. But let's look at an example to clarify my problem further: suppose I wanted to implement shadowmapping in my game. At some point, I would need to position the camera to render from the light source. In my old engine that meant I would have to make my camera class available to my light class (extern class CCamera g_Camera;) This is an ugly solution to my knowledge and I would like to avoid this situation in my new engine. (and it proves that some parts of the engine have to communicate with eachother)
Any ideas?
Jeroen
I would certainly agree with the article you recommended, if it weren't for the fact that I started writing the previous engine back in 2002, when I was exactly 12 years old and had no experience at all. A rewrite from zero is definitely the best option here. I've learned a lot since then: I've written quite a few 2D games and apps and I wrote a 3D walktrough game of my school. (from which I graduated now) On top of that I've also written the demo for Parallel Split Shadowmaps in OpenGL. So I suppose I'm not a complete noob anymore. I've also switched from Windows to Linux now and I'd like my engine to be cross-platform. (I use Qt and OpenGL)
I can see the problem with my design that you mention in your post. Indeed, as you so said, a sound is not an engine. But let's look at an example to clarify my problem further: suppose I wanted to implement shadowmapping in my game. At some point, I would need to position the camera to render from the light source. In my old engine that meant I would have to make my camera class available to my light class (extern class CCamera g_Camera;) This is an ugly solution to my knowledge and I would like to avoid this situation in my new engine. (and it proves that some parts of the engine have to communicate with eachother)
Any ideas?
Jeroen
Quote:
(and it proves that some parts of the engine have to communicate with eachother)
Intra-system communication is fine, inter-system communication needs to be handled by a higher-level subsystem. Since inter-system communication on the scale of the communication between sound, rendering, physics, game logic is domain-specific (specific to the particular needs of the particular game), that communication should be done by the game code, and not the engine.
Quote:
At some point, I would need to position the camera to render from the light source. In my old engine that meant I would have to make my camera class available to my light class (extern class CCamera g_Camera;) This is an ugly solution to my knowledge
It's only ugly if (a) globals are involved, (b) the light object accepts and directly manipulates or reads a camera, or (c) the camera object accepts and directly manipulates or reads a light. But none of that is required. Rendering from the perspective of the light only means that a higher-level system (the portion of the render pipe responsible for pre-render actions related to shadowmapping) reads from the light being used for the shadow map, passes the position/orientation information to the current camera (saving the old information, or perhaps simply creating a new camera) and renders the scene.
This means all you really need is the ability to render the scene from an arbitrary camera -- pass the camera to use to the appropriate function at the appropriate point.
So you're saying a game engine is best not implemented with a class hierarchy, because it introduces unnecessary dependencies and it's just another clumsy layer on top of it. How is this done in the Unreal engine for example? I suppose C++ must have some advantages that engine design can make use off? Does a good engine avoid object orientated design then?
Jeroen
Jeroen
Not at all -- other than the initial comment about Engine and Sound not needing to be in a hierarchy, I said nothing about inheritance or, specifically, about object-oriented design.
Inheritence, used properly, is a good tool. Object oriented design, when used properly, is a good tool. Niether of these are the issue here, the issue here is avoiding communication between unrelated subsystems (sound, graphics, "the core engine") and avoiding dependancy on explicit singular state (global camera versus passing the camera to render with).
Inheritence, used properly, is a good tool. Object oriented design, when used properly, is a good tool. Niether of these are the issue here, the issue here is avoiding communication between unrelated subsystems (sound, graphics, "the core engine") and avoiding dependancy on explicit singular state (global camera versus passing the camera to render with).
Is an advice to use inheritance only where it comes with certain advantages
e.g.: reusing code, a sound engine and a particle engine doesn t have that much in common that a common base class would bring any benefit.
A usefull scenarion for inheritance would be the implementation of different camera modes
basecam: responsible for setting the projection and transformation matrices
perspectivecam: responsible for setting up perspective transformations
orthocam: orthographic cam
followcam: picks coordinate of an object to follow, derived from perspectivecam
now you can switch between cam modes by switching the address of the cam pointer
just as an example.
As for your shadow mapping example,
I would implement it like this
shadowmapmanager:
- responsible for triggering updates
shadowmaprenderer:
- sets up the camera and renders the scene
shadowmapobject:
- just holds the textureid of the map and can be queried by objects
e.g.:
objectA:
- query "global shadowmap"
objectB:(is indoor, in lightfrustrum of spotlightX)
- query "spotlightX shadowmap"
the query is triggered by the renderer that sets up the materials & textures before rendering
Your lightsources should invalidate the shadowmap if they detect changes,
this can be implemented in the shadowmapmanager as well to avoid pollution of your light source implementations.
But somewhere you need to access the scene information, a simple callback function that returns true if the shadowmap is invalid should do the job
shadowmapmanager mn;
mn.setinvalidationcallback(isinvalid);
bool isinvalid(int shadowmapid)
{
...
access scene information
...
}
The invalid check is usually code that introduces lots of dependecies, thus a callback helps you to reduce these.
e.g.: reusing code, a sound engine and a particle engine doesn t have that much in common that a common base class would bring any benefit.
A usefull scenarion for inheritance would be the implementation of different camera modes
basecam: responsible for setting the projection and transformation matrices
perspectivecam: responsible for setting up perspective transformations
orthocam: orthographic cam
followcam: picks coordinate of an object to follow, derived from perspectivecam
now you can switch between cam modes by switching the address of the cam pointer
just as an example.
As for your shadow mapping example,
I would implement it like this
shadowmapmanager:
- responsible for triggering updates
shadowmaprenderer:
- sets up the camera and renders the scene
shadowmapobject:
- just holds the textureid of the map and can be queried by objects
e.g.:
objectA:
- query "global shadowmap"
objectB:(is indoor, in lightfrustrum of spotlightX)
- query "spotlightX shadowmap"
the query is triggered by the renderer that sets up the materials & textures before rendering
Your lightsources should invalidate the shadowmap if they detect changes,
this can be implemented in the shadowmapmanager as well to avoid pollution of your light source implementations.
But somewhere you need to access the scene information, a simple callback function that returns true if the shadowmap is invalid should do the job
shadowmapmanager mn;
mn.setinvalidationcallback(isinvalid);
bool isinvalid(int shadowmapid)
{
...
access scene information
...
}
The invalid check is usually code that introduces lots of dependecies, thus a callback helps you to reduce these.
We're not talking about inheritance, we're talking about data flow. Besides...
Inheritance is not a mechanism for code reuse. You should use inheritence first and foremost when it makes sense within the context of the object model you've chosen, and if it saves you typing some code, great. But code reuse is a beneficial side-effect of inheritence, and should rarely be the gating factor in electing to employ it or not. No matter how much code it saves you, if the the relationship cannot be expressed via "is-a" then you probably should not be using inheritence.
Although this is largely irrelevant to the topic at hand, inheritence is not what allows you to "switch modes by switching a pointer" here; you could achieve the same thing by implementing camera control in terms of a camera object and a camera controller manipulating a specific camera -- simply swap the controller pointer. This also has the benefit of seperating the responsibility of "camera" and "behavior of the camera," increasing maintainability and the general strength of the design.
Quote:
Is an advice to use inheritance only where it comes with certain advantages
e.g.: reusing code,
Inheritance is not a mechanism for code reuse. You should use inheritence first and foremost when it makes sense within the context of the object model you've chosen, and if it saves you typing some code, great. But code reuse is a beneficial side-effect of inheritence, and should rarely be the gating factor in electing to employ it or not. No matter how much code it saves you, if the the relationship cannot be expressed via "is-a" then you probably should not be using inheritence.
Quote:
A usefull scenarion for inheritance would be the implementation of different camera modes
basecam: responsible for setting the projection and transformation matrices
perspectivecam: responsible for setting up perspective transformations
orthocam: orthographic cam
followcam: picks coordinate of an object to follow, derived from perspectivecam
now you can switch between cam modes by switching the address of the cam pointer
Although this is largely irrelevant to the topic at hand, inheritence is not what allows you to "switch modes by switching a pointer" here; you could achieve the same thing by implementing camera control in terms of a camera object and a camera controller manipulating a specific camera -- simply swap the controller pointer. This also has the benefit of seperating the responsibility of "camera" and "behavior of the camera," increasing maintainability and the general strength of the design.
Quote:
Hmm, while that article has a point, I resent the implication that all games programmers are female. That kind of political-over-correctness thing makes me quiver in rage. It's not hard to write "he or she", for God's sake.
No, but it's ugly and doesn't read as nice. It's not hard to pick "he" or "she" and use it for the duration of an example, as I did, and any interpretations of political correctness exist only in your mind. I use a male example as often as I use a female example, I enjoy the variety.
It is often best to focus on the features you want to have in and what kind of design is the simplest to suit those needs. Lots of game engines do have global subsystems like
Often that kind of layout works a lot like old C-coding, and the 'class' name could often be replaced with a 'namespace'. There's nothing wrong with doing that if it suits your requirements, it's flat, simple, no dependencies and very quick to set up.
Lots of engines do something a bit like you first mentioned:
This approach should only be done if you have thought some nice features all(or most, if you're coding pragmatically) EngineComponents can/must supply. In this case, the engine usually has a list of currently running Components which are iteratively pumped forward.
Not saying either of those are without problems or the best design, or that one would work better than the other. But they're both a design.
The point is that there's no sense to start creating a deep hierarchy where you have something like Object, EngineComponent, etc. if you can't justify their existence. If you don't have a real use for each of the levels of abstraction you're creating, don't do it like in the second design. Also, if you'd like to do something C++-ish with your objects, like dynamic lifetime management, RAII, etc. the first design probably doesn't apply.
The good thing with hobby projects is that neither YAGNI nor reinventing-the-wheel arguments apply. You should use the fact that you don't have a production codebase burdening your design, so you can refactor at will if the design doesn't feel right. Think about what you want to achieve and only then try to create a design that fits.
Now that you've created an engine, I think you shouldn't just throw it away. Start listing all the weak points in its design, and figure out what makes it bad and how could a new design fix it. Old code often looks like crap, but it's a real treasure trove of crap if you look at it the right way. :)
class SoundSystem{};SoundSystem g_soundSystem;class Renderer{};Renderer g_renderer;...Often that kind of layout works a lot like old C-coding, and the 'class' name could often be replaced with a 'namespace'. There's nothing wrong with doing that if it suits your requirements, it's flat, simple, no dependencies and very quick to set up.
Lots of engines do something a bit like you first mentioned:
class EngineComponent{public: // might have something like: void Tick(float dt); void Stop();};class SoundSystem : public EngineComponent{};class Renderer : public EngineComponent{};class NetworkClient : public EngineComponent{};This approach should only be done if you have thought some nice features all(or most, if you're coding pragmatically) EngineComponents can/must supply. In this case, the engine usually has a list of currently running Components which are iteratively pumped forward.
Not saying either of those are without problems or the best design, or that one would work better than the other. But they're both a design.
The point is that there's no sense to start creating a deep hierarchy where you have something like Object, EngineComponent, etc. if you can't justify their existence. If you don't have a real use for each of the levels of abstraction you're creating, don't do it like in the second design. Also, if you'd like to do something C++-ish with your objects, like dynamic lifetime management, RAII, etc. the first design probably doesn't apply.
The good thing with hobby projects is that neither YAGNI nor reinventing-the-wheel arguments apply. You should use the fact that you don't have a production codebase burdening your design, so you can refactor at will if the design doesn't feel right. Think about what you want to achieve and only then try to create a design that fits.
Now that you've created an engine, I think you shouldn't just throw it away. Start listing all the weak points in its design, and figure out what makes it bad and how could a new design fix it. Old code often looks like crap, but it's a real treasure trove of crap if you look at it the right way. :)
Quote:No, he didn't say anything like that (or really anything about inheritance at all).
Original post by godmodder
So you're saying a game engine is best not implemented with a class hierarchy, because it introduces unnecessary dependencies and it's just another clumsy layer on top of it.
I don't see why your camera and light have to know about each other at all; sure, you could do something like Camera.toLight or Light.toCamera but it's just as simple to figure out what data you actually need from the light and only provide that (e.g. position and orientation => new Camera(light.Position, light.Orientation)).
This way you increase the flexibility of your code (now you can make cameras from anything with a position and orientation, such as game actors and arbitrary data, as opposed to just the specific elements hardcoded from the engine) and you also reduce the number of dependencies you have going. Less is more.
I'd recommend you sit down and draw a data flow diagram showing what components you need where as opposed to what large-granularity objects you need. Just because it's object-oriented doesn't mean you need all of the object.
Hello guys,
thanks for all of the information. I guess I'll go for a simple design with only independent classes for things that actually do stuff. (SoundSystem / Renderer / AI)
You're right that I'm only a hobby programmer so I'll just try out a design and see where it goes. Anyway, I'm not entirely abandoning my old engine, I'll take the good parts out and reuse them, but there's also some really cluttered stuff that uses register combiners and all sorts of old stuff. Also, my last engine was designed with the majority of levels being outdoors, while my new game is going to be almost 95% indoors. That's another reason why I want to start from zero this time.
Just one more question: suppose that now I have a two classes called Camera and Renderer. How would be the cleanest way to pass a camera position to render from? Would it be like this:
void Renderer::RenderShadowMap(Camera *pCamera) { /* ... */ }
void Renderer::Render()
{
RenderShadowMap(g_Camera);
}
Where would I get g_Camera from? A global variable isn't the way to go right?
Jeroen
thanks for all of the information. I guess I'll go for a simple design with only independent classes for things that actually do stuff. (SoundSystem / Renderer / AI)
You're right that I'm only a hobby programmer so I'll just try out a design and see where it goes. Anyway, I'm not entirely abandoning my old engine, I'll take the good parts out and reuse them, but there's also some really cluttered stuff that uses register combiners and all sorts of old stuff. Also, my last engine was designed with the majority of levels being outdoors, while my new game is going to be almost 95% indoors. That's another reason why I want to start from zero this time.
Just one more question: suppose that now I have a two classes called Camera and Renderer. How would be the cleanest way to pass a camera position to render from? Would it be like this:
void Renderer::RenderShadowMap(Camera *pCamera) { /* ... */ }
void Renderer::Render()
{
RenderShadowMap(g_Camera);
}
Where would I get g_Camera from? A global variable isn't the way to go right?
Jeroen
Quote:
How would be the cleanest way to pass a camera position to render from?
Ravuya pretty much gave you a verbatim example of that. As for where to get the camera, it comes from the light (same position/orientation, remember?) and the light comes from a higher-level process. Presumably the shadow mapping pass is a subset of a larger rendering operation which has already acquired all the lights in the scene to do regular lighting with them. Use those.
(Those lights, in turn, came from the level or scene, which, in turn were user-specified either programmatically or from a data file.)
Ok, you're comments have been very usefull so far and I've got a rudimentary design up and running. I'm quite happy with it actually. Everything feels naturally in it's use. Now that I've set up an application and a window for rendering to, I want to use my Renderer class to do the actual rendering now. As there should be only one instance of a renderer in my game at a time and because it really only needs to be explicitly invoked in 3 places (the overridden events from Qt: initializeGL(), paintGL() and resizeGL()), would it be smart to access the renderer from a static member variable to itself? Then I would perform the rendering like this: Renderer::GetRenderer()->Render(); Remember: I only have to do this 3 times.
Is this professional or does it suck big time?
Let me know,
Jeroen
Is this professional or does it suck big time?
Let me know,
Jeroen
Quote:The way my engine does things (just another method, not the bible), is that I maintain two key data structures, a scene graph and a render graph. The visitor pattern is invoked over the scene graph, which is then translated into a render graph. This render graph is a more linear, useful and "final" list of the objects to be rendered. Part of the visitation is registration of cameras, lights, meshes and storage of their final 4x4 transformation matrices in a linked list. When registration occurs, the engine builds relationships such as assignment of view ports (which in my engine describe size, projection and other properties such as Z range) to lights for shadow mapping passes. When building this render graph, opportunity for cache coherency and sorting/batching can be obtained as well as hardware assisted rejection (occlusion queries).
Original post by godmodder
void Renderer::RenderShadowMap(Camera *pCamera) { /* ... */ }
void Renderer::Render()
{
RenderShadowMap(g_Camera);
}
Where would I get g_Camera from? A global variable isn't the way to go right?
Jeroen
With the state of modern DDR and memory controllers, generation cost of this render graph usually less than render time. One key benefit and justification for this design is the system lends well to a multi-core environment, as you simply instantiate two render graphs, build one while the other is being rendered, and simply exchanged pointers using an atomic OS function like InterlockExchange
All in all, you shouldnt be worrying about inheritence, there are much higher level design aspects that go into making a flexible and _easy_to_use_ engine, if you're having trouble with knowing when to apply inheritance, have a look at some good quality OSS projects like Irrlitch, Ogre3D and particularly NebulaDevice2. These engines show sound OO practice and have their sources and clear, concise documentation available to view (and learn from) for free.
Hope that helps.
-Danu
Ok, so a singleton isn't the way to go. Would it be ok to have a few global functions in my main.cpp like InitGame(), EndGame(),...?
From there I could call all of my systems independently and pass on needed objects as function arguments. For example:
main.cpp
--------
How about this?
Jeroen
From there I could call all of my systems independently and pass on needed objects as function arguments. For example:
main.cpp
--------
#include <QApplication>#include "qtopengl.h"#include "renderer.h"int main(int argc, char *argv[]){ QApplication App(argc, argv); InitGame(); return App.exec(); // start the message loop}void InitGame(){ QtOpenGL *pWindow = new QtOpenGL(0, QString("Engine Test")); Renderer *pRenderer = new Renderer(pWindow); // Create a renderer and pass a // window to render to // Do other init stuff here}How about this?
Jeroen
Topic Locked
This topic has been locked by a moderator. New replies are not allowed.
Sign in to reply to this topic.
Advertisement