Sign in to follow this  
Oberon_Command

Acceptable use of globals in this case?

Recommended Posts

For a number of years, I've had a codebase which I've been using on my projects. Basically, it's a collection of classes which provide access to graphics methods, resource management, configuration file parsing, etc... However, recently I decided that the system I currently had was not flexible enough, so I decided to restructure it. My past system was more or less a layer on top of SDL, especially for rendering. While I do enjoy programming in SDL, and it meets my needs quite nicely for most things, I do plan on upgrading my code-base to support OpenGL at some point in time. The new code-base (called BaseCode2 since I have nothing better to call it :P) implements a system where each of the API-dependent layers are abstract interface classes. The primary video subsystem, for instance, is a class called "VideoScreen", which has all virtual members. The intent behind this is that I write a "driver" for each new API that I want to add to the codebase, and the user of the codebase (usually me) uses the interface ONLY to access those functions. Initializing an instance of the "VideoScreen" class might look something like this:
VideoScreen *screen = new SDLVideoScreen();
screen->Initialize(640,480, SDL_ANYFORMAT);

Now, this system is up and running and currently works quite well; all I really need to do to switch "drivers" is write a new implementation of VideoScreen, say "OGLVideoScreen", and point the "screen" pointer to an instance of that class. This way, the rest of the program should care minimally about how "VideoScreen"s are implemented. Now, here's my problem: my last version of "BaseCode" implemented only ONE version of the VideoScreen class, and it was a singleton. This made things easier throughout the program, since I didn't have to explicitly declare a global variable and I didn't have to explicitly pass a VideoScreen pointer to every method that needed to use it. I'm not certain as to how that might work with the new system, however; actually, I can't see how it would work at all. I can use global variables for this, but I'd really rather not. My question is this: would it be more acceptable to use global variables in this case instead of trying to get a singleton working? Is it even possible to use a "VideoScreen" singleton in this case, where everything (else) about the singleton is purely virtual and is overloaded by a derived class implementation?

Share this post


Link to post
Share on other sites
Quote:
Original post by Oberon_Command
My question is this: would it be more acceptable to use global variables in this case instead of trying to get a singleton working? Is it even possible to use a "VideoScreen" singleton in this case, where everything (else) about the singleton is purely virtual and is overloaded by a derived class implementation?
Either a global variable or a singleton indicates that the VideoScreen will be visible globally, but I would have a hard time justifying this. Why does the Screen class need to be globally visible? Or in other words: why does every element of the program need access to the screen? If not every element needs access to the screen (and I can't see why, for instance, the audio system would need the screen), then it should not be globally visible, and neither global variables nor singletons are appropriate.

Share this post


Link to post
Share on other sites
Quote:
Original post by swiftcoder
Quote:
Original post by Oberon_Command
My question is this: would it be more acceptable to use global variables in this case instead of trying to get a singleton working? Is it even possible to use a "VideoScreen" singleton in this case, where everything (else) about the singleton is purely virtual and is overloaded by a derived class implementation?
Either a global variable or a singleton indicates that the VideoScreen will be visible globally, but I would have a hard time justifying this. Why does the Screen class need to be globally visible? Or in other words: why does every element of the program need access to the screen? If not every element needs access to the screen (and I can't see why, for instance, the audio system would need the screen), then it should not be globally visible, and neither global variables nor singletons are appropriate.


The idea is that it would be visible only to video output subsystems ("Renderer" objects).

Would it be acceptable to put the variable in a particular header file and only include that header file in the source files that needed it? I admit that it's not a particularly elegant way of doing things, but I'm stumped as to what else I could do without destroying the readability of all the classes that use the VideoScreen functionality. I.e.:


// I would prefer to do this
pixelRenderer->DrawAt(x,y,colour);

// as opposed to this
pixelRenderer->DrawAt(screen, x, y, colour);

// or even this
pixelRenderer->setScreen(screen);
pixelRenderer->DrawAt(x,y,colour);


Share this post


Link to post
Share on other sites
Quote:
Original post by Oberon_Command
The idea is that it would be visible only to video output subsystems ("Renderer" objects).

Would it be acceptable to put the variable in a particular header file and only include that header file in the source files that needed it? I admit that it's not a particularly elegant way of doing things, but I'm stumped as to what else I could do without destroying the readability of all the classes that use the VideoScreen functionality.
What is wrong with passing the screen to the constructor?

pixelRenderer = new PixelRenderer(screen);
pixelRenderer->DrawAt(x, y, colour);


In general, this is the preferred method to eliminate global variables/singletons - which should be avoided wherever possible.

Share this post


Link to post
Share on other sites
Quote:
Original post by swiftcoder
Quote:
Original post by Oberon_Command
The idea is that it would be visible only to video output subsystems ("Renderer" objects).

Would it be acceptable to put the variable in a particular header file and only include that header file in the source files that needed it? I admit that it's not a particularly elegant way of doing things, but I'm stumped as to what else I could do without destroying the readability of all the classes that use the VideoScreen functionality.
What is wrong with passing the screen to the constructor?

*** Source Snippet Removed ***


Mainly because in the final product the VideoScreen would get passed down through a chain of constructors that would look something like this:


main() // initialized here
-> GameApp
--> RenderManager
---> PixelRenderer


Here's how it used to be, before I turned VideoScreen into a singleton:

main() // initialized here
-> GameApp
--> GamePlayState
---> GameMap
----> Tile
-----> Image // FINALLY gets used here


The first chain is certainly better than the second... but I'd still like to avoid that sort of thing as much as possible. If I were to use this system of referencing, RenderManager would have to be passed down similar chains in place of the Screen. Actually, that was probably going to happen anyway.

If I have to do that in both cases, I will, but it doesn't strike me as stylistically "clean". Granted, neither does the use of globals... To refine my question, I guess what I'm asking is whether there's a way to make a particular instance of an object global to only a few objects instead of the whole program without trickling a reference to that object through several layers of classes.

Share this post


Link to post
Share on other sites
Quote:
Original post by Oberon_Command
Mainly because in the final product the VideoScreen would get passed down through a chain of constructors that would look something like this:


main() // initialized here
-> GameApp
--> RenderManager
---> PixelRenderer


Here's how it used to be, before I turned VideoScreen into a singleton:

main() // initialized here
-> GameApp
--> GamePlayState
---> GameMap
----> Tile
-----> Image // FINALLY gets used here


The first chain is certainly better than the second... but I'd still like to avoid that sort of thing as much as possible. If I were to use this system of referencing, RenderManager would have to be passed down similar chains in place of the Screen. Actually, that was probably going to happen anyway.

Why is such a chain a bad thing? It makes it clear when reading the code exactly where the screen comes from. And just as importantly, it means I can swap it out easily, if I need to test the Tile class, say, with a fake screen object.

Share this post


Link to post
Share on other sites
Quote:
Original post by Spoonbender
Quote:
Original post by Oberon_Command
Mainly because in the final product the VideoScreen would get passed down through a chain of constructors that would look something like this:


main() // initialized here
-> GameApp
--> RenderManager
---> PixelRenderer


Here's how it used to be, before I turned VideoScreen into a singleton:

main() // initialized here
-> GameApp
--> GamePlayState
---> GameMap
----> Tile
-----> Image // FINALLY gets used here


The first chain is certainly better than the second... but I'd still like to avoid that sort of thing as much as possible. If I were to use this system of referencing, RenderManager would have to be passed down similar chains in place of the Screen. Actually, that was probably going to happen anyway.

Why is such a chain a bad thing? It makes it clear when reading the code exactly where the screen comes from. And just as importantly, it means I can swap it out easily, if I need to test the Tile class, say, with a fake screen object.


I'm not sure. It just seems ugly to me. What I'd ideally like is something like a singleton (one instance only) that can only be accessed by classes that declare themselves able to use it. So, something perhaps like this:

class Tile uses VideoScreen
{
...


But I'm using C++, and I don't think that would be useful for anything but singletons, so I'll just use the "trickle-down" method. It does have the advantages you mention.

Thanks, all.

Share this post


Link to post
Share on other sites
Quote:
Original post by Spoonbender
Why is such a chain a bad thing? It makes it clear when reading the code exactly where the screen comes from.


These contructors could be called from many higher up constructor sites, so infact it is not clear at all where it came from.

You need to have knowledge of the entire chain (something not visible when examining the source of any of these constructors) to know whats really going on.

The only thing clear is that the object was handed to it. This does infact have some utility, but that is not what you seem to have meant.

This sort of reference spaghetti can be almost as evil as GoTo, but probably not so much in this case. Globals can also be evil, but also not so much in this case.

The global/singleton solution has value because you only ned to know one thing to understand the entire system: There can be only one.

Share this post


Link to post
Share on other sites
Quote:
Original post by Rockoon1
Quote:
Original post by Spoonbender
Why is such a chain a bad thing? It makes it clear when reading the code exactly where the screen comes from.


These contructors could be called from many higher up constructor sites, so infact it is not clear at all where it came from.

You need to have knowledge of the entire chain (something not visible when examining the source of any of these constructors) to know whats really going on.

The only thing clear is that the object was handed to it. This does infact have some utility, but that is not what you seem to have meant.

This sort of reference spaghetti can be almost as evil as GoTo, but probably not so much in this case. Globals can also be evil, but also not so much in this case.

The global/singleton solution has value because you only ned to know one thing to understand the entire system: There can be only one.


This sounds more like an argument against deep class hierarchies than reference passing -- introducing globals only hides the problem that the tree is unwieldy and opaque to understanding. You don't understand the entire system, you only understand the global -- and since anything can modify globals, you probably only think you understand it! (and what's happening to it)

Share this post


Link to post
Share on other sites
Quote:
Original post by Rockoon1
These contructors could be called from many higher up constructor sites, so infact it is not clear at all where it came from.
Yeah, the dependencies are directed from top to bottom, but that's a good thing it means that lower level components are reusable which is all part and parcel of object oriented design.

Quote:
You need to have knowledge of the entire chain (something not visible when examining the source of any of these constructors) to know whats really going on.
If you want to view the system as a whole then that's always going to be the case. If you just want to examine how a particular object works, then no, you don't need to know where instances come from, it is sufficient just to know that they are there.

Knowing that an instance is visible is made harder by globals since they won't be present in any function signatures or class definitions. Knowing when it is safe to modify a global, or when it makes sense to read from one, requires knowledge of the entire system.

Quote:
The global/singleton solution has value because you only ned to know one thing to understand the entire system: There can be only one.
I agree that it makes it easy to understand the entire system, but not that it has value. Compare these:

Global/Singleton: "There is a global instance that is visible to me, it is visible to the rest of the system too, so it is not safe for me to access or modify."
Synopsis: All system components are coupled to a global instance, and by implication to each other.

Argument Passing: "There is an instance that was given to me, it is specifically for me to access and modify."
Synopsis: All system components are loosely coupled, they are self contained systems in their own right.

Share this post


Link to post
Share on other sites
Quote:
Original post by MaulingMonkey

This sounds more like an argument against deep class hierarchies than reference passing -- introducing globals only hides the problem that the tree is unwieldy and opaque to understanding. You don't understand the entire system, you only understand the global -- and since anything can modify globals, you probably only think you understand it! (and what's happening to it)


Isn't that always the case?

One of biggest problems with singleton (as a pattern) is that it mixes implementation detail with design.

In a properly designed system, presence of singleton is not a problem. One way to test whether system depends on singleton is this:

struct Foo {
Foo(Bar * bar = BarSingleton::getInstance());
};
If the following makes sense, then system is de-coupled to a degree that storage is not mandated.

However, in case of this particular problem, VideoScreen must be Initialize()d - which is a problem, since BarSingleton cannot be auto-constructed as required above (creating a new instance of VideoScreen leaves it in undetermined state), meaning that use of globals is obstructing a bigger problem.

Quote:
-> GameApp
--> GamePlayState
---> GameMap
----> Tile
-----> Image // FINALLY gets used here


Hmmm. Warning lights flashing yet?

You've written it down yourself - you hierarchy is not suitable.

Why does image need to know who the VideoScreen is? Image is passive and doesn't do anything to it. Typically, for classes like Image you have something like:
class Image {
Image(InputStream * is) // loads from some abstract source
{
is->readThis();
is->readThat();
is->readSomeMore();
if (not_condition) throw error;
}

void draw(VideoScreen * vs, int x, int y);
};


Why does tile need screen? To know the size of screen? It doesn't need that information. A Tile is dumb.
struct Tile : public Rectangle {
// singleton is perfectly fine here in the same way
// std::allocator is fine for STL containers
// but we are free to choose something else
Tile(InputStream * is, ResourceManager * rm = ResourceManager::getSingleton()) {
image = rm->loadCached(is->readTileId());
// read more data about what is contained in this tile
}
Image * image;
std::vector<Foo*> contained;
};


GameMap doesn't need it either. Game map doesn't care about coordinates available for rendering. GameMap is determined by level designer.
struct GameMap {
GameMap(InputStream * is) // load from some abstract source
{
extents.x = 0;
extents.y = 0;
extents.w = is->readInt();
extents.h = is->readInt();
for (...) {
tiles.push_back(Tile(is, imageCache));
}
}

void render(VideoScreen * vs, const Rectangle & clip);

const Rectangle & getExtents() const {
return extents;
}

ResourceManager imageCache;
std::vector<Tile> tiles;
Rectangle extents;
};

The above example uses typical windowing paradigm, namely view offset is part of VideoScreen (canvas or similar), and render function can query. In addition, we provide clip rectangle to limit what is drawn.

So....


VideoScreen * vs = new OpenGLVS(); // or DXVS() or SDLVS() or WhateverVS()

// file, memory, internet, pak, ... whatever source
InputStream * is = getReader("levels/level01.map");
GameMap map(is);

vs->setViewOffset(50,20);

// similar to singleton
// unless we really need it, we just use default clip
// and redraw everything, but we have the choice of replacing it
// to optimize the rendering
map.render(vs, vs->getExtents());


So you end up with classes which are independent of each other, yet interoperate in a clear manner.

Note that to create a Tile or Image, you do not need to know anything about GameMap or Game or VideoScreen or anything else. That's not what Image or Tile do - they are dumb and passive and do one thing and one thing only.

Share this post


Link to post
Share on other sites
Quote:
Original post by Rockoon1
This sort of reference spaghetti can be almost as evil as GoTo, but probably not so much in this case. Globals can also be evil, but also not so much in this case.

Spaghetti? By your definition, any code that has more than one function is spaghetti code then? Because the function could be called from any number of places. What do you propose then, put everything inside main()?

By your definition, everything else becomes spaghetti code.

Share this post


Link to post
Share on other sites
Global variables are not evil in them selves. What is evil is allowing everyone to change a global.

The reason globals are frowned upon is not because everything can see them, it is because everyone can change them. This makes for a debug nightmare. If anyone can change the global, then if an error pops up you have no idea where it came from.

There are two ways to make globals not have that issue. The first is to initialize the global on start up and not allow it to be changed. This makes sure that if there is a problem with the global that it has to be in the constructor or the code calling the constructor. The other way is to pass an instance to the global and that is the only thing that is allowed to change the global. That way all changes go through a single class and instantiated object.

So remember, globals are not bad, only allowing anyone to change them is.

theTroll

Share this post


Link to post
Share on other sites
In my game code, I've found that in every instance, the cleanest solution I could come up with involved no globals/singletons whatsoever. Globals just take bad design and make it require less syntax. Try removing every global variable in your program and replacing it with arguments. If that makes your code "ugly" or "complicated", all you've really done is taken the lipstick off the pig. Good designs will have small numbers of arguments to methods/constructors and no globals.

When I eventually realized that, I ended up spending a lot more time thinking about code than I used to, but the end result was much cleaner. Look at everything in terms of what it logically should "need" to function, even if, at the current time, that doesn't "work" in your current code base. For instance, looking at your code example:


// I would prefer to do this
pixelRenderer->DrawAt(x,y,colour);

// as opposed to this
pixelRenderer->DrawAt(screen, x, y, colour);

// or even this
pixelRenderer->setScreen(screen);
pixelRenderer->DrawAt(x,y,colour);


Like I said earlier, global variables just reduce the amount of syntax. Looking at it logically, the first DrawAt in essence already receives the screen as an argument (as does every other function in your program). The downside is that there is no way for the caller to change the "argument" to refer to a different screen. The second version of DrawAt is exactly the same, only you are able to pass a specific screen to it. In this case, the second DrawAt is far more flexible, and at the same time gets rid of globals. See? Better (in this case, more flexible) design doesn't have globals in it.

The third design is really not much better than globals. Just like global variables, class variables are kind of implied arguments to all member functions. This can be a good thing, as in a lot of cases you have sets of functions that do all take similar arguments (and this should be the only motivation for a class, IMO). In this case however, does every single function in pixelRenderer need the screen? Probably not. But even if they did, looking at it from another direction: Does a pixelRenderer need a screen to exist? (which, IMO, should be the only motivation for a class variable) The answer should also be no.

As for the readability, yes its a bit more typing. But thats the amount of typing it takes to express your design in C++. Global variables just hide syntax and pollute the design with unnecessary arguments. It boils down to the same thing as goto and control structures. One is shorter, one expresses your design using constructs of the language properly.

Share this post


Link to post
Share on other sites
Quote:
Original post by Antheus
Why does tile need screen? To know the size of screen?


With the hierarchy mentioned, the tile needs the screen so that its image will know what the screen is.

Quote:
GameMap doesn't need it either. Game map doesn't care about coordinates available for rendering.


But images, and therefore tiles do, and maps are composed of tiles therefore GameMap needs to know what the VideoScreen is. Only the images care about the "extents", they care about the drawing methods (i.e. DrawImage()) that are provided by the VideoScreen interface.

Quote:
struct GameMap {
GameMap(InputStream * is) // load from some abstract source
{
extents.x = 0;
extents.y = 0;
extents.w = is->readInt();
extents.h = is->readInt();
for (...) {
tiles.push_back(Tile(is, imageCache));
}
}

void render(VideoScreen * vs, const Rectangle & clip);

const Rectangle & getExtents() const {
return extents;
}

ResourceManager imageCache;
std::vector<Tile> tiles;
Rectangle extents;
};

The above example uses typical windowing paradigm, namely view offset is part of VideoScreen (canvas or similar), and render function can query. In addition, we provide clip rectangle to limit what is drawn.

So....


VideoScreen * vs = new OpenGLVS(); // or DXVS() or SDLVS() or WhateverVS()

// file, memory, internet, pak, ... whatever source
InputStream * is = getReader("levels/level01.map");
GameMap map(is);

vs->setViewOffset(50,20);

// similar to singleton
// unless we really need it, we just use default clip
// and redraw everything, but we have the choice of replacing it
// to optimize the rendering
map.render(vs, vs->getExtents());


So you end up with classes which are independent of each other, yet interoperate in a clear manner.


I'm not sure I understand this. I don't think it's very clear at all. Where is the rendering code in this? I can't see where the Image might actually be drawn. Can you clarify on this, please?

Quote:

Note that to create a Tile or Image, you do not need to know anything about GameMap or Game or VideoScreen or anything else. That's not what Image or Tile do - they are dumb and passive and do one thing and one thing only.


But how does the Image know what to draw on?

I've "shallowed out" my class hierarchy in my current project. The design I think I'll go with has a single GameApp object that has a stack of game state classes and a "RenderManager" class. Each state class will have access to Renderer classes, and upon initialization a state will be passed a reference to the RenderManager, at which point it will add the appropriate Renderers to the Rendering manager. Thus, the game state itself doesn't have to care about rendering at all; it only changes the game world and interprets input. This "game world" object is then passed to the RenderManager's rendering function. So, the class hierarchy would look something like this:


GameApp
-> RenderManager
---> TileRenderer
---> UnitRenderer
---> etc...
-> GameState
---> GameWorld (gets passed to each of the renderers when they are added to the RenderManager)


So, the only objects that would need to know about the VideoScreen would be the GameApp (the VS would be a member variable), the RenderManager, and the rendering calls of each of the Renderers.

Is a desirable design, or should I attempt to flatten out my hierarchy even further?

[Edited by - Oberon_Command on October 12, 2008 2:17:06 PM]

Share this post


Link to post
Share on other sites
Quote:
Original post by Oberon_Command
Quote:
Original post by Antheus
Why does tile need screen? To know the size of screen?


With the hierarchy mentioned, the tile needs the screen so that its image will know what the screen is.

Quote:
GameMap doesn't need it either. Game map doesn't care about coordinates available for rendering.


But images, and therefore tiles do, and maps are composed of tiles therefore GameMap needs to know what the VideoScreen is. Only the images care about the "extents", they care about the drawing methods (i.e. DrawImage()) that are provided by the VideoScreen interface.

Quote:
struct GameMap {
GameMap(InputStream * is) // load from some abstract source
{
extents.x = 0;
extents.y = 0;
extents.w = is->readInt();
extents.h = is->readInt();
for (...) {
tiles.push_back(Tile(is, imageCache));
}
}

void render(VideoScreen * vs, const Rectangle & clip);

const Rectangle & getExtents() const {
return extents;
}

ResourceManager imageCache;
std::vector<Tile> tiles;
Rectangle extents;
};

The above example uses typical windowing paradigm, namely view offset is part of VideoScreen (canvas or similar), and render function can query. In addition, we provide clip rectangle to limit what is drawn.

So....


VideoScreen * vs = new OpenGLVS(); // or DXVS() or SDLVS() or WhateverVS()

// file, memory, internet, pak, ... whatever source
InputStream * is = getReader("levels/level01.map");
GameMap map(is);

vs->setViewOffset(50,20);

// similar to singleton
// unless we really need it, we just use default clip
// and redraw everything, but we have the choice of replacing it
// to optimize the rendering
map.render(vs, vs->getExtents());


So you end up with classes which are independent of each other, yet interoperate in a clear manner.


I'm not sure I understand this. I don't think it's very clear at all. Where is the rendering code in this? I can't see where the Image might actually be drawn. Can you clarify on this, please?

Quote:

Note that to create a Tile or Image, you do not need to know anything about GameMap or Game or VideoScreen or anything else. That's not what Image or Tile do - they are dumb and passive and do one thing and one thing only.


But how does the Image know what to draw on?
Assuming I understand Antheus' example correctly, the rendering is done in Image::draw(), and the image knows what to draw on because it receives a pointer to a VideoScreen object as an argument to draw().

Does that answer your questions at all?

[Oops, looks like you edited your post since I posted this.]

Share this post


Link to post
Share on other sites
Quote:
Original post by jykAssuming I understand Antheus' example correctly, the rendering is done in Image::draw(), and the image knows what to draw on because it receives a pointer to a VideoScreen object as an argument to draw().

Does that answer your questions at all?


Well, that much I guessed, since it's stated somewhere above. What I can't quite see is where Image::draw() is actually getting the pointer to the VideoScreen instance from.

Share this post


Link to post
Share on other sites
Quote:
original post by Oberon_Command
// I would prefer to do this
pixelRenderer->DrawAt(x,y,colour);

// as opposed to this
pixelRenderer->DrawAt(screen, x, y, colour);


Think about that for a second. What if you want to draw a pixel on a different surface? Say you want to do some of your rendering on a separate surface, to be blitted onto the screen later. But if pixel renderer is hardwired to the screen, you're screwed. Drawing pixels is fundamentally dependent on individual surfaces. Your argument list may as well admit it.

The same goes for most other reference passing problems.

Quote:
Here's how it used to be, before I turned VideoScreen into a singleton:


main() // initialized here
-> GameApp
--> GamePlayState
---> GameMap
----> Tile
-----> Image // FINALLY gets used here


One idea, which may well be stupid, is to have a Surface* or something as a static member in Image. This can be set to the screen somewhere early in the video initialization, where it fits in naturally. You can set it to any surface you want. It still doesn't cleanly solve multiple display surfaces, but that doesn't really seem to be a major concern of yours...

IMHO, a pointer to the video surface doesn't really belong in that chain, and should be set elsewhere. The clients of Image should have to deal with that.

P.S. Why does pixelRenderer need to be an object? Can't that just be a free function, or maybe a method of VideoScreen?

Share this post


Link to post
Share on other sites
Quote:
Well, that much I guessed, since it's stated somewhere above. What I can't quite see is where Image::draw() is actually getting the pointer to the VideoScreen instance from.
In Antheus' example, the VideoScreen pointer is created in main(), and is then passed to GameMap::render(). Although Antheus did not supply a definition for GameMap::render(), it presumably looks something like this (I'll leave out culling/clipping for simplicity):
void render(VideoScreen * vs, const Rectangle & clip)
{
for (size_t i = 0; i < tiles.size(); ++i) {
tiles[i].image->draw(vs, ...);
}
}
That's a very simple example of course (you could use iterators, for_each and boost::bind - whatever you prefer), but it should show how the video screen makes its way from the main() function to Image::draw().

Share this post


Link to post
Share on other sites
Quote:
Original post by theOcelot
P.S. Why does pixelRenderer need to be an object? Can't that just be a free function, or maybe a method of VideoScreen?


"pixelRenderer" is just an example, albeit a very simple one. In reality, I'd use a "renderer" as an object that's sole purpose is to render a collection of a particular kind of object. The only reason that renderer's have a "drawAt"-type method is to provide flexibility in case some other game subsystem needs to draw an object at a particular location without involving the "render list" of the renderer. The idea is that this way, the objects don't need to know anything about how they are rendered; all they have to know is their position in the game world and their current state.

Say I have an object that represents a game unit in an RTS. It has a unit "type" (i.e. whether it's a tank, a robot, a zombie, etc.) associated with it, a position in the game world, and some state variables that represent how much health, ammo, energy, etc. that particular unit has. It might also have some animation state information associated with it, such as what animation it's currently in and what frame of that animation it is on.

Now, the renderer would have the actual graphics data associated with each type loaded as part of it. It would take a list of each of the GameUnits in the game world. When its Draw() method is called, it renders all of the units in the game world by looking at their state data and drawing the appropriate graphic in the appropriate place. The GameUnit doesn't care how it's drawn; in fact, it doesn't even know that it's drawn at all. Only the Renderer object has to care.

Each game state would have a list of "renderers" associated with it, and when it came time to render everything these renderer's Draw() methods would be called in sequence. The game state and the renderers are entirely independent of one another. All the game state object has to know when it comes to rendering is that the renderer exists, the game world exists, and that the rendering manager exists.

The pixelRenderer, like I said, was really just a test of the system. All it does is put thousands of pixels in random colors in random places on the screen. You're quite right in saying that this system would be inefficient for something as basic as a pixel. :) But the objects Renderers are intended to draw are somewhat more complex than a single pixel, and while the functionality is THERE to draw only one of each object, the intent is for the user of the class (probably just me, but still) to call ONLY the Draw() method.

Like I said, the intention is to isolate the rendering code from everything else as much as possible so that if, for instance, I wished to change the game from 2D to 3D, I'd have to refactor as little as possible.

Quote:
Original post by jykIn Antheus' example, the VideoScreen pointer is created in main(), and is then passed to GameMap::render(). Although Antheus did not supply a definition for GameMap::render(), it presumably looks something like this (I'll leave out culling/clipping for simplicity):
void render(VideoScreen * vs, const Rectangle & clip)
{
for (size_t i = 0; i < tiles.size(); ++i) {
tiles[i].image->draw(vs, ...);
}
}
That's a very simple example of course (you could use iterators, for_each and boost::bind - whatever you prefer), but it should show how the video screen makes its way from the main() function to Image::draw().


Ah, okay, that clarifies it, thank you.

Share this post


Link to post
Share on other sites

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