views:

169

answers:

4

I am going to be re-creating a game engine I created awhile back. This time I want to do it right, so I did some research into design patterns and tried to put pieces together to accomplish my goal. The idea of the game engine is simplicity, but at the same time I don't want to sacrifice usability.

Here is a blueprint of what I am thinking, let me know if you can see any downfalls especially in expandability:

class Object
{
    public:
        string name;
}

class Object3D : public Object
{
    public:
        int x;
        int y;
        int z;
}

class Object2D : public Object
{
    public:
        int x;
        int y;
}

class cube : public Object3D
{
    cube() : x(0), y(0), z(0), name("cube") {}
}

class square
{
    ...
}

int main()
{
    SGL Engine(paramters);

    c = cube();
    s = square();

    Engine->Lib3D->AddCube(&c, "cube");
    Engine->Lib2D->AddSquare(&s, "square");
    Engine->Input->keyboard(&kbevent);
    while(Engine->running())
    {
        if (x)
            Engine->Draw("cube");
        else
            Engine->Draw("square");
    }
}

void kbevent(event-paramteres)
{
    if (key.up)
        engineptr->objects["cube"]->move(x,y);
}

The target language is C++.

+1  A: 

Unless you are using hash mapping, string lookup is slow. Besides once you have added the objects to the engine you should be able to do something like:

Engine->Draw();

Which will go through all objects added to the engine and draw them.

The Object should have a virtual Draw function and Object2d and Object3d should override that function. Then the Engine would just loop through all the objects and call draw on them.

The other thing is that you shouldn't need an add function for the different types. You should be able to do something like:

Engine->AddObject(&c);
Engine->AddObject(&s);

And it will figure out what to do with it. The way you have it now you are requiring the user of the engine to know what they want to do, whereas it should be encapsulated enough that you don't need to, but open enough that it doesn't get in your way if you want to do something not supported by the Engine you can, so I would keep the Lib2d and Lib3d accessors, though purists would say the encapsulation was broken.

DominicMcDonnell
I was planning on having a "DrawAll" function, but in my mind at least there are times when you don't want to draw all objects at a given time. I would certainty use mapping like the STL class map. Actually I do like just having the single AddObject function, but how would I be able to determine the type of object the user created? ie if it was a 2D or 3D object?
Nathan Adams
You could have the AddObject overloaded to the Object2d and Object3d classes, rather than having it take an Object class, or have a get type function in object. As for not drawing objects, I would have a visible flag for each object.Another thing is that you are exposing the storage to the user. What if you decided you really need to use a linked list for some reason, you would have to change all of the instances of engineptr->objects["cube"]. I'd rather have a GetObject, or a MoveObject function.
DominicMcDonnell
-1. This type of design will totally thrash your cache. Re-coding such a system into a more cache-friendly design will require an entire rewrite of your engine. Design with the data in mind.
Simon
@Nathan Adams: "but in my mind at least there are times when you don't want to draw all objects at a given time." Normally you want to draw all *visible* objects. Map is not suitable storing such information. "but how would I be able to determine the type of object the user created?" If you ask questions like this, it means you should think a bit about final product. It is possible to make code in such way that you won't need to determine whether object is 2d or 3d.
SigTerm
@Simon Cache? I don't understand, can you please expand on what you mean by "cache"?
Nathan Adams
In this case I meant the L2 cache more specifically: http://en.wikipedia.org/wiki/L2_cache. It's used to reduce memory-latency. It is a smaller memory that store cache lines of data. If a cache line with the data that you want to use is not in the cache, it costs a lot of instructions to fetch the data from main memory. Placing data linearly in memory is important for the L2 Cache. See my post for more information regarding design.
Simon
@Simon You have to walk before you can run. If the whole point of this was performance, yes you are correct, but data centric engines require you to know what you want before you create it. For exploration and learning about object oriented systems a simpler model is much better.
DominicMcDonnell
@DominicMcDonnell: It's not only about performance, it's about the data. A data centric engine doesn't really require what you say it does. It's just another way of designing the systems and how they hang together. I disagree that the way that you explains is a good way to learn object oriented systems, since they are poorly designed object oriented systems. I really don't think it's easier either because you are going to suffer in the long run.
Simon
@Simon: The question I answered was how to improve the design presented, not how I would design a system. The improvements I suggested do not actually limit Nathan from turning to a data centric approach later. Since it is hiding the details from the user of the engine. In fact I would suggest that any data centric engine would have a similar interface to what I laid out. It would be a simple thing to change AddObject to copy the object to an array of it's type and Draw to loop through those arrays.
DominicMcDonnell
And it is hard, from your link: 'Writing a good engine means first and foremost, understanding the data.' And understanding the data requires you to try. If you've never written a game engine before, how are you to know what you need? How are you going to store objects? Where is the mesh data going to live? What about textures, mip maps, what sort of transforms do you need, what about animation? So much easier to do in a simple naive engine you can append to an object without having to go back to the drawing board every time you discover that you need something new.
DominicMcDonnell
@Simon: But it's good that you are pointing out that there is another more advanced approach.
DominicMcDonnell
@DominicMcDonnell: As soon as you write a class, or a struct, you define the data. As soon as you code something other than "hello world", you are most likely going to need to define either a class or a struct. Once you do, you know something about your data. This doesn't require that you have 5 years of game development experience. It goes for any system. The approach is really not more advanced, it's different. Once you use it, it's simpler and leads to much better solutions that are easier to understand and easier to maintain.
Simon
+2  A: 

First, read this.

The only way to do it "right" is to turn the process upside down. Make the engine after you've used it in a game.

Second. C++ isn't Java. C++ doesn't have a base Object class, and it doesn't need one either. Don't try to fit everything into an inheritance hierarchy. If every object is polymorphic you're crippling yourself (everything has to be heap-allocated and passed by reference or pointer to avoid slicing, for example, and it doesn't even make your code any clearer)

An object in the world has a position. It is not one. So don't inherit x, y, z coordinates. Put them in a coordinate class, which can be added as a member in the classes that need it.

String lookups are slow, and you need to handle the case when strings are not unique. (What if I accidentally add two objects with name "cube".

Why not rely on plain references or pointers as much as possible?

Why can't I simply do Engine->Draw(c) to tell the engine to draw the cube?

But really, the most important piece of advice is, don't try to write a shrink-wrapped game engine for later use. You're going to end up exactly where you are now: needing to "rewrite it, and this time do it right". If you want to get something out of it that works, you need to start with its use case. Write the game first. When you've done that, you can start refactoring the code to separate the engine out. That way, you end up with an engine that works.

jalf
The reason why I don't use Engine->Draw(c) is because how else could I reference the object in the keyboard event function? The idea here isn't to write a shrink wrap-for anything engine, but I want to develop a small engine with the specific features for game, but I want to use a design pattern that will allow me to expand upon it later, not redevelop.
Nathan Adams
@Nathan Adams: Try having a list of objects to render in the renderer. Try to store those renderable objects in another system. Once you want to operate on those objects, call the other system while the renderer is not working on those objects. You should be able to add and remove objects from rendering.
Simon
A: 

First of all, try not to fit a design pattern onto a problem. Read http://realtimecollisiondetection.net/blog/?p=44 and http://realtimecollisiondetection.net/blog/?p=81 regarding that kind of solutions.

Second, It's important that you look at what kind of game(s) you will be doing when writing an engine. Therefor it is of the absolute interest to lay out what kind of data you will process and how.

Third, when it comes to game-engine development I would recommend reading into the Bitsquid blog http://bitsquid.blogspot.com/, which are currently writing some interesting topics regarding their new engine. I would also recommend watching/reading as much as possible from Mike Acton who is the Lead Engine Architecture at Insomniac Games. He frequently recommends that you look at the data first and develop your engine around what kind of data that it will process. This one: http://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-189-multicore-programming-primer-january-iap-2007/lecture-notes-and-video/embed17/ is an interesting ground-base runthrough that Mike Acton does regarding their engine development.

The link to Mike Acton's general blog is http://www.insomniacgames.com/blogcast/blog/mike_acton. I would also suggest reading through a lot of their topics since they're very good and interesting.

Regarding your code I see no reason as to why you need your inheritance structure. Why is a Cube a 3d-object? Why does Cube inherit from anything at all? A Cube or a Box is simply 3 floats that defines the extents (or half extents) of it. I would suggest that you have an Object that then uses a HAS-A relationship to cubes or boxes or squares. There are some topics at Insomniac regarding component-based game entities, I suggest you read it: http://www.insomniacgames.com/assets/filesadynamiccomponentarchitectureforhighperformancegameplay.pdf (I think that's the correct link).

The three big lies of software development: http://www.insomniacgames.com/blogcast/blog/all_categories/1500756

Simon
+1  A: 
  1. I'm pretty sure you should use floats instead of ints for object coordinates. With floats you can run the game at any framerate. With ints there will be problems.
  2. Ideally, class names should start with capital letters (class Cube) unless you use prefixes to indicate types (class CCube).
  3. (most important one) Before making object hierarchy, you should know what exactly you want to create. Imagine game level, think what objects are present, categorize them, and then try to make a hierarchy. You don't need to make a class for everything (and maybe you don't even need a complex hierarchy, maybe you can write entire thing without ever using inheritance), but you should be able to imagine final product while you're writing code. Don't introduce new concepts unless you need them. Right it looks like you have no idea of what you want to make, and now there are few classes that may be completely unsuitable for final product.
SigTerm
+1, specifically for suggesting not to use inheritance. People over-use inheritance in general. Another +1 for a good comment in general if I could.
Simon