Sign in to follow this  

Game Engine Dilemma

This topic is 3783 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

Hello everyone, this is my first post :D. I am having a dilemma about a game engine I am building. Its my first attempt at a full engine, its written in c++ and uses DirectX. I keep changing the structure back and forth, but I have to choose one way and stick to it. Okay here it is: My engine is divided into devices and objects. For example: Devices: Graphics, Input, SoundManager etc. Objects: Images, Sprite, Sound etc. Now, here is the dilemma A) Should I have functionality in the actual objects or the devices? Ex. When I create an image or a sprite, should that object have functions for loading and drawing? Or should the Graphics device hold those functions? If the functionality is in the image object, it would look like this. Image.Initialize( GraphicsDevice, filepath, x, y); etc. Image.Draw( x, y); If its done like this than any object that needs a device to perform its operation will have to hold a pointer to the device as a member of the class, or it could be passed into parameters like this: Image.Draw( GraphicsDevice, x, y); ...because the image needs access to the device to draw. B) OR.......Should I percolate functionality upwards into the actual device? Ex. The device controls the operations of the object which need the device.. Image image = Graphics.CreateImage( filepath, useMask) .....etc. That eliminates the need to have a pointer to the device in every single object that needs it, or passing it in as a parameter all the time. Also.... Graphics.Draw( Image imgName); ** no need to pass in the device However, functions that do not require the device like: Image.GetHeight(); ....should maybe stay within the object itself? ********************** This is the dilemma. Keep functionality with the objects? Or move it up into the devices? The goal of this engine is simplicity. The user should have easy access to functions, thats why I like to keep them in devices. Currently, I have functionality in the objects, but I am tempted to move it into the devices. I am leaning towards the device functionality because it makes it "easier" I think to access the Graphics object for everything related to graphics. Please let me know your opinions, I have never made a full engine before. Oh...and one more thing. Should my functions return 0 if they fail and let the user check for errors in their code? Or should there be an error message box in the actual engine function? Ex: int Draw( Image, x, y) { if ( Graphics->Draw( Image) == 0) return 0; return 1; } and in the users code: int temp = Draw( image, x, y) if ( temp == 0) MessageBox( NULL, "Could not draw image", "Error!", MB_ICONEXCLAMATION | MB_OK); OR **************************** void Draw( Image, x, y) { if ( Graphics->Draw( Image) == 0) MessageBox( NULL, "Could not draw image", "Error!", MB_ICONEXCLAMATION | MB_OK); } Thanks for all your help, XSlinger

Share this post


Link to post
Share on other sites
The semantics of "Device" sort of imply that there could be multiple devices, and if that's the case then you may want your "objects" to be reusable.

Why not something along the lines of:

image.Initialize(filepath, x, y);
GraphicsDevice.Draw(image);
OtherGraphicsDevice.Draw(image);

Actually, that's how it works in the Java and .NET drawing libraries, now that I think about it.

If there is any state information that is specific to a particular device, then that device can cache any data it needs internally... I'm thinking vertex buffers, etc.

Share this post


Link to post
Share on other sites
Hmm....

But even the initialize requires use of a graphics device. Therefore it might have to be specific to a single device.

So it would have to be:

Image.Initialize( Graphics, x, y)

or

Image = Graphics->Initialize( x, y);


I think...

Share this post


Link to post
Share on other sites
I agree with smitty, the objects (like "Image") should be reusable. It seems to me like they are both somewhat dependant of each other. Why does initialization of an Image Object require a Graphics Device?

Share this post


Link to post
Share on other sites
@popsoftheyear

The image initialization needs a Graphics device because the graphics device holds the IDirect3DDevice9* object and the function to load a Direct3D texture takes in this device as a parameter.

**EDIT in the above post I just put Image.Initialize( Graphics, x, y)
I meant to put a filepath in there. (Graphics, filePath, x, y)

The image object hoolds the Direct3D texture and Initialize uses the filePath to load the texture.

Share this post


Link to post
Share on other sites
I'm only vaguely familiar with DirectX...but it seems to me that your image object will only be useable by the graphics device that it is initialized with? If this is OK, I would probably stick with the "Image = Graphics->Initialize(path, x, y);" version, or even "Image = Graphics->Load(path)"... (why do you need x and y in the initialize parameter if you're loading anyway??)

Unless someone knows of some way that XSlinger doesn't have to depend on the direct3d interface to load his 3d textures...

Cheers
-Scott

Share this post


Link to post
Share on other sites
The way I see it:
Sprites use the device, the device doesn't use the sprites;
So the sprites should know the device, not other way.

(I may be wrong because I don't know exactly the features and responsibilities of your classes)

You can know which device you are using hard-coded (possibly after inheriting from more abstract class, or with a template), or by global pointer (singleton?) or by holding a pointer member (and there are more ways...).
The most flexible way is holding a pointer member, but since device is such a central part of the game engine (ie. many objects and classes will need it) and you put simplicity as a top goal, it may be acceptable to have some global pointer to the device. Same way if you want to have a log file, it may be acceptable to have a global variable of a Logger class.

Just be careful not to make a habit of using globals... in the example of the logger if you later decide to have a separate log file for the red sprites you may find it difficult when you are using just one global pointer across your classes.

Share this post


Link to post
Share on other sites
I stand by my original opinion. It shouldn't matter that the Device has the IDirect3DDevice9*, because the Image object shouldn't be creating texture objects and should not even know what an IDirect3DDevice9 is. Those are in the API/device realm. An Image should only hold pixel information read from a file and other imformation that the device needs to create a texture.

When you draw the image using...

myDevice.Draw(myImage)

...then the Device should internally create a texture from the supplied image.

There is no reason whatsoever for the image to have knowledge of the Device. Then, later, you could even reimplement the "Device" to use OpenGL or something else, if you want to. The "objects" should be completely uninterested in the implementation details of the "Device".

In my opinion, of course.

Share this post


Link to post
Share on other sites
I completely agree with smitty still...forcing something that should be flexible like your Image object to be dependant on an very specific API is bad design. Granted sometimes there is nothing you can do about it...but for all intents and purposes this shouldn't be one of those times.

Ideally, I would think that it should be something more along the lines of

Image->Load(Path); // Assume X and Y are described in the file
Graphics->LoadTexture(Image);

On the other hand, I did a quick google search and came up with this function, D3DXCreateTextureFromFile. I assume you're using something like this to easily load images and not have to worry about that file formats etc yourself? If you are using a directx dependant function to load images, I'm not sure how many other choices you really have!

And to answer your other question...I would probably just have a return value. There may very well be cases where if image drawing fails, it is NOT necessary for the end-user to know...ya know?

Share this post


Link to post
Share on other sites
Thanks for all your help and the speedy replies. Now I understand that objects should be independent of the device.

I will do something like the following:

Image = Graphics->CreateImage( filePath);
Graphics->DrawImage( Image);

XSlinger

Share this post


Link to post
Share on other sites
IF the "device" is creating the object, then they aren't independent of one another. Why must the interface to the video card create an object which is loaded from a file on disk? That doesn't make any sense to me. What does an image on disk have to do with the video hardware?

Share this post


Link to post
Share on other sites
I look at "Image" as a part of the game engine, not the game.

In my engine, objects provide "descriptions" for what needs to be drawn, not the actual drawing data.

I provide an interface, IDrawable, which classes that need to be drawn can inherit from. IDrawable contains things like Texture (string), Position, Rotation, Scale. I pass a list of IDrawables to the renderer for drawing.

Share this post


Link to post
Share on other sites
To chime in on your other question: Where do I handle my errors?

For starters, I wouldn't use messageboxes. They tend to block things like mad.

Both options of error handling are valid. I personally return an error value as exit parameter, when things will not go critically boom.
However if your error generating code needs to clean stuff up, or release a lot of resources, it might be nicer to catch the exception in the exception handling code.
I'm not that fond of the c++ way of exception handling, but in some cases, for instance FileIO its very nice to have a robust way of handling errors.

A personal tip when working with mixed return values/exceptions:
Use code/xml documentation to indicate what your code does when it encounters an error.

Good luck.



Share this post


Link to post
Share on other sites
Just wanted to add a few bits of friendly advice!

#1) A well designed API thinks of 'what goes where' but also 'how will this be used'. A general rule of thumb I try to stick to is 'make it just work' which means try to make it so as little can go wrong as possible.

#2) Think of objects in the problem domain as close to real terms as possible. You must imagine 'There is only one device, and there can only ever be one device' but 'there may be one or more sprites'. A good way to think of the problem is 'A sprite must own a texture, the one device creates that texture, only that device can destroy that texture, and only that device can display that texture'. Also 'The sprite may change as functionality is added to the sprite, but the way the device may draw the texture will not change'. These facts help you to organise your API, but there is no strict design rule except 'How can I make this as simple as possible to use'.

#3) Don't use C++ exception handling in your main loop. Exceptions are expensive, and the tried and true method is to make every function that could possibly fail return an integer which is very cheap to compute. If the function failed then it will return an error code, if it didn't fail then it will return 0.

I hope these tips help you out, and good luck!

Share this post


Link to post
Share on other sites
to help separate the device and object, i would do something like this:


class image {
int x, y;
texture tex;
image( texture _tex, int _x, int _y )
: tex( _tex ), x( _x ), y( _y ) {
}
texture getTexture( ) {
return tex;
}
int getX( ) {
return x;
}
int getY( ) {
return y;
}
};

...

image img ( device.createTexture( path ), startingX, startingY );

device.renderTexture( img.getTexture( ), img.getX( ), img.getY( ) );


this way, device and image have to know about texture, not about each other. What I don't like about this method is that I prefer to have class functions that perform an operation (like a draw() method ) rather than have a bunch of getters. But this way devices and images don't have to know about each other..

Share this post


Link to post
Share on other sites
Quote:
Original post by XSlinger
Q: Should I have functionality in the actual objects or the devices?

When it comes to adding new types of objects, do you prefer to modify the device class or do you prefer to add another object to the hierarchy? But see the next answer before you jump into programming.

Quote:
Original post by XSlinger
Q: OR.......Should I percolate functionality upwards into the actual device?

I don't like this solution very much (which is not a criterion for you) - it has to be chosen only if you are sure that you won't extend the functionalities of the devices later.

Quote:

However, functions that do not require the device like:

Image.GetHeight();

....should maybe stay within the object itself?

Yep, of course.

On a side note: if an image is responsible for holding its mathematical representation AND for drawing itself then the single responsibility principle is not respected.

Quote:
Should my functions return 0 if they fail and let the user check for errors in their code? Or should there be an error message box in the actual engine function?

No message box - let the user choose what he wants to do - maybe he's going to use its own message box system.

My DX engine is using a different paradigm - the "device" is not doing the rendering (they are two separate objects; of course, the renderer is created by the device). Resource creation is done by passing the device to the resource constructor; sending resources to the device is done using their bind() method. The renderer is responsible for holding its state and to send drawing orders.

In the end, code looks like:

// --------------- creation
grx::texture model_texture("image_path.png");
grx::vertex_buffer model_vbuffer(vertex_decl, vertex_count);
grx::index_buffer16 model_ibuffer(index_count);
grx::vertex_shader model_vshader(...);
grx::pixel_shader model_pshader(...):

try
{
model_texture.create(device);
model_vbuffer.create(device);
model_ibuffer.create(device);
model_vshader.create_and_compile(device);
model_pshader.create_and_compile(device);
}
catch (grx::initialization_exception& e)
{
grx::log(e.what());
}

// ---------------- use
// bind everything
model_texture.bind(0);
vbuffer.bind();
ibuffer.bind();
vshader.bind();
pshader.bind();

// setup the renderer state
renderer.set_raster_state(raster_state);
renderer.set_depth_stentil_state(ds_state);
// and finally: render!
renderer.render_indexed_triangle_list();


Of course, it's a bit more complex than just that. But that should give you the idea.

Share this post


Link to post
Share on other sites
Quote:
Original post by Fase
To chime in on your other question: Where do I handle my errors?


#1 rule in a game-engine: Whatever you do: Don't crash

- Check for null-pointers
- Use asserts in debug
- Add logging (make sure you can filter on importance (from boring to critical)
also make sure you can output logging in release
- In a game engine be smart about handling errors, for example
- image not found? take the "invalid" image (black background with red cross),
- model not found? Either ignore the draw, or render default model (big cross).

- Use error checking over return values (success/failed/...)

- I use exceptions for *exceptional* situations - an error which is so critical
that it's absolutely impossible to continue.

Again, this is just my 2ct - depending on what platform and such, your
needs might differt (and there are many opinions on using 'exceptions :)

Share this post


Link to post
Share on other sites

This topic is 3783 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.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this