OOP style question
Hello
I am programming a game using SDL. I have a very simple question regarding "proper" OOP style. Should I always have my classes send all information that is needed for drawing to the main() function and then draw it there, or is it ok to have a class with a function Draw() that I can call (using SDL_GetScreenSurface() and globals)?
Using globals is not contradictory with the rules of object-oriented design (using objects to represent your problem) but goes against the point of object-oriented programming (writing reusable and easily extensible code).
Here's a quick description: object-oriented programming is about objects sending messages to other objects. For instance, your Character object sends a message to your Renderer object explaining how it should be drawn. Now, what is important is how your character object knows which renderer object should receive the message.
You need to choose the level of reuse potential for every class that uses a renderer. If you don't intend to reuse a given class as part of this project or another, then the low level is acceptable. If there is any chance of reusing the class, then the medium level is necessary, and the high level would be even better if you intend to reuse it a lot.
Note that there is nothing wrong with creating a global variable—the only issue is hard-coding a global variable access inside a class, because this reduces the reuse potential of the class. You could of course create a global instance and then pass that global instance to your object using any of the other two reuse modes.
Here's a quick description: object-oriented programming is about objects sending messages to other objects. For instance, your Character object sends a message to your Renderer object explaining how it should be drawn. Now, what is important is how your character object knows which renderer object should receive the message.
- The renderer is passed to the character as part of the process which involves sending the message. This is the highest level of reuse potential, since any user of a character object can decide what renderer will be used to draw the character.
- The renderer is passed to the character when it is constructed. This is a medium level of reuse potential, since any creator of a character object can decide what renderer will be used to draw the character. A simple user of a character object, however, will have no choice but to use the associated renderer.
- The renderer is hard-coded in the character class when it's implemented. This is a low level of reuse potential, since nobody can decide what renderer will be used to draw the character—that choice was made at implementation time, fixing forever the object that will be used.
You need to choose the level of reuse potential for every class that uses a renderer. If you don't intend to reuse a given class as part of this project or another, then the low level is acceptable. If there is any chance of reusing the class, then the medium level is necessary, and the high level would be even better if you intend to reuse it a lot.
Note that there is nothing wrong with creating a global variable—the only issue is hard-coding a global variable access inside a class, because this reduces the reuse potential of the class. You could of course create a global instance and then pass that global instance to your object using any of the other two reuse modes.
Passing the required information to the classes is better style in this situation. Globals are a sign of bad design and violate the locality prinicple.
Avoid globals when possible, they cause more problems than they solve.
Avoid globals when possible, they cause more problems than they solve.
Thanks fo the replies! I was asking because of the structure of my program, it looks so crazy:
main()
> main() creates Player
> > Player creates Tetrominoe
> > > Tetrominoe creates Matrix
> > Player creates Field
> > > Field creates Matrix
> main() calls Player.GetInfo()
> > Player calls Tetrominoe.GetInfo()
> > > Tetrominoe calls Matrix.GetInfo()
< < < Matrix passes info to Tetrominoe
< < Tetrominoe passes info to Player
> > Player calls Field.GetInfo()
> > > Field calls Matrix.GetInfo()
< < < Matrix passes info to Field
< < Field passes info to Player
< Player passes info to main()
> main() draws using info
It's crazy because most of these steps happen many many times, seeing getting the info from the Matrix happens in a few for's.
main()
> main() creates Player
> > Player creates Tetrominoe
> > > Tetrominoe creates Matrix
> > Player creates Field
> > > Field creates Matrix
> main() calls Player.GetInfo()
> > Player calls Tetrominoe.GetInfo()
> > > Tetrominoe calls Matrix.GetInfo()
< < < Matrix passes info to Tetrominoe
< < Tetrominoe passes info to Player
> > Player calls Field.GetInfo()
> > > Field calls Matrix.GetInfo()
< < < Matrix passes info to Field
< < Field passes info to Player
< Player passes info to main()
> main() draws using info
It's crazy because most of these steps happen many many times, seeing getting the info from the Matrix happens in a few for's.
main is the program entry point. As such, its task should be to collect the initialization parameters from the standard arguments and the environment variables, and then pass them to a function or object representing the actual game.
Then, the actual game should update the state (this process is represented by a function or object), then display the state (this process is represented by another function or object).
However, there is no issue with these two subprocesses delegating their work to the Player and Piece objects they are handling.
Then, the actual game should update the state (this process is represented by a function or object), then display the state (this process is represented by another function or object).
However, there is no issue with these two subprocesses delegating their work to the Player and Piece objects they are handling.
"passes"....
Since I doubt you're using message-driven architecture, what exactly are you referring to.
If these are function calls, you have two terms: "call", "returns".
Can you show some code? Especially since, unless I'm mistaken, SDL is not OO, so proper OO isn't possible, you can merely emulate it.
Since I doubt you're using message-driven architecture, what exactly are you referring to.
If these are function calls, you have two terms: "call", "returns".
Can you show some code? Especially since, unless I'm mistaken, SDL is not OO, so proper OO isn't possible, you can merely emulate it.
Alright, I didn't know passing was soemthing different. I meant return. Why is it impossible to do OOP with SDL? What does a library need to be OOP-able?
Quote:Original post by Antheus
If these are function calls, you have two terms: "call", "returns".
A function call between objects is equivalent to message passing in proper object oriented parlance.
Quote:...unless I'm mistaken, SDL is not OO, so proper OO isn't possible...
Your ability to write an object oriented program is not limited by the libraries you employ. You simply create an object or objects to manage interface with said libraries.
Right now, my main() function does pretty much everything... I understand now I should make a seperate class for the Game and a seperate class for the Renderer. I tried tackling this issue, but it's over my head, I don't know where to start nor what to do. I have included my current main() function here, can someone please help me by telling me what should go where? Thanks in forward.
All variables that are in ALL_CAPS are global constants. *screen is a global SDL_Surface. block[] is a global array of Block's. I also included my Block class.
color[] is an array of SDL_Color's. I used enumerations for the names of the colours. Each block consists of 3 rectangles in each other with colour variations to obtain a pretty effect. If you are confused, take a look at the blocks, I used them in a deviation on DeviantArt: [link]
int main ( int argc, char* argv[] ){ if( !Init( &screen, SCREEN_WIDTH, SCREEN_HEIGHT ) ) { printf( "Initialization failed, returning...\n" ); return -1; } // Seed the randomizer srand( time(0) ); // Timestep variables int t = 0; const int dt = 1; int current_time = SDL_GetTicks(); int accumulator = 0; int new_time; int d_time; // LOADING STARTS HERE SDL_Rect backgroundrect; backgroundrect.x = backgroundrect.y = 0; backgroundrect.w = SCREEN_WIDTH; backgroundrect.h = SCREEN_HEIGHT; // The field where the tetrominoes will end up SDL_Rect fieldrect; fieldrect.x = (SCREEN_WIDTH / 2) - (FIELD_WIDTH * BLOCK_SIZE / 2); fieldrect.y = (SCREEN_HEIGHT / 2) - (FIELD_HEIGHT * BLOCK_SIZE / 2); fieldrect.w = FIELD_WIDTH * BLOCK_SIZE; fieldrect.h = FIELD_HEIGHT * BLOCK_SIZE; // The little window that shows the upcoming tetrominoe SDL_Rect nxtblockrect; nxtblockrect.x = fieldrect.x + (BLOCK_SIZE * (FIELD_WIDTH + 1) ); nxtblockrect.y = fieldrect.y; nxtblockrect.w = nxtblockrect.h = BLOCK_SIZE * 4; // The player that does everything Player player( FIELD_WIDTH, FIELD_HEIGHT ); // LOADING ENDS HERE // MAIN LOOP STARTS HERE bool quit = false; while( !quit ) { // TIMESTEP STARTS HERE new_time = SDL_GetTicks(); d_time = new_time - current_time; current_time = new_time; accumulator += d_time; while ( accumulator >= dt ) { // Process here player.Update( dt ); t += dt; accumulator -= dt; } // TIMESTEP ENDS HERE while ( SDL_PollEvent(&event) ) { // If we try to close the program if( event.type == SDL_QUIT ) { printf( "Received exit-request, leaving main loop...\n" ); quit = true; } if( event.type == SDL_KEYDOWN ) { // Escape quits if( event.key.keysym.sym == SDLK_ESCAPE ) { printf( "Received exit-request, leaving main loop...\n" ); quit = true; } // The arrow keys move the tetrominoe if( event.key.keysym.sym == SDLK_LEFT ) player.MoveLeft(); if( event.key.keysym.sym == SDLK_RIGHT ) player.MoveRight(); // With A and Z we flip the tetrominoe if( event.key.keysym.sym == SDLK_a ) player.FlipCW(); if( event.key.keysym.sym == SDLK_z ) player.FlipCCW(); } } // END OF POLLING // DRAWING STARTS HERE // Background SDL_FillRect( SDL_GetVideoSurface(), &backgroundrect, SDL_MapColor( screen->format, color[blackc] ) ); // Border around field for( int i = -1; i < FIELD_HEIGHT + 1; i++ ) { for( int j = -1; j < FIELD_WIDTH + 1; j++ ) { block[8].Draw( (j * BLOCK_SIZE) + fieldrect.x, (i * BLOCK_SIZE) + fieldrect.y, SDL_GetVideoSurface() ); } } // Field for( int i = 0; i < FIELD_HEIGHT; i++ ) { for( int j = 0; j < FIELD_WIDTH; j++ ) { block[player.GetFieldXY( j ,i )].Draw( (j * BLOCK_SIZE) + fieldrect.x, (i * BLOCK_SIZE) + fieldrect.y, SDL_GetVideoSurface() ); } } // Border around next plane for( int i = -1; i < 5; i++ ) { for( int j = -1; j < 5; j++ ) { block[8].Draw( (j * BLOCK_SIZE) + nxtblockrect.x, (i * BLOCK_SIZE) + nxtblockrect.y, SDL_GetVideoSurface() ); } } // Next plane for( int i = 0; i < 4; i++ ) { for( int j = 0; j < 4; j++ ) { block[player.GetNextXY( j ,i )].Draw( (j * BLOCK_SIZE) + nxtblockrect.x, (i * BLOCK_SIZE) + nxtblockrect.y, SDL_GetVideoSurface() ); } } // Current Tetrominoe for( int i = 0; i < 4; i++ ) { for( int j = 0; j < 4; j++ ) { if( player.GetCurrXY( j, i ) ) block[player.GetCurrXY( j, i )].Draw( ( j * BLOCK_SIZE ) + fieldrect.x + ( player.GetPosX() * BLOCK_SIZE ), ( i * BLOCK_SIZE ) + fieldrect.y + ( player.GetPosY() * BLOCK_SIZE ), SDL_GetVideoSurface() ); } } // DRAWING ENDS HERE SDL_Flip( SDL_GetVideoSurface() ); } // END OF MAIN LOOP return 0;}
All variables that are in ALL_CAPS are global constants. *screen is a global SDL_Surface. block[] is a global array of Block's. I also included my Block class.
enum typesoftetros { none, I, J, L, O, S, T, Z };class Block{private: SDL_Rect borderrect; SDL_Color borderclr; SDL_Rect lightrect; SDL_Color lightclr; SDL_Rect mainrect; SDL_Color mainclr;public: Block() {} Block( int type, int size ); void Draw( int x, int y, SDL_Surface *surface );};// Fancy constructor that actually has a pointBlock::Block( int type, int size ){ borderrect.w = borderrect.h = size; lightrect.w = lightrect.h = size - 2; mainrect.w = mainrect.h = size - 4; // All types of tetrominoes if( type == I ) { borderclr = color[redc]; lightclr = color[reda]; mainclr = color[redb]; } else if( type == J ) { borderclr = color[whitec]; lightclr = color[whitea]; mainclr = color[whiteb]; } else if( type == L ) { borderclr = color[purplec]; lightclr = color[purplea]; mainclr = color[purpleb]; } else if( type == O ) { borderclr = color[bluec]; lightclr = color[bluea]; mainclr = color[blueb]; } else if( type == S ) { borderclr = color[greenc]; lightclr = color[greena]; mainclr = color[greenb]; } else if( type == T ) { borderclr = color[brownc]; lightclr = color[browna]; mainclr = color[brownb]; } else if( type == Z ) { borderclr = color[orangec]; lightclr = color[orangea]; mainclr = color[orangeb]; } // For border blocks of type -1 (Border blocks) else if( type == -1 ) { borderclr = color[blackc]; lightclr = color[blackb]; mainclr = color[blackc]; } // For blocks that don't belong to any tetrominoe else { borderclr = color[blackc]; lightclr = color[blacka]; mainclr = color[blackb]; }}void Block::Draw( int x, int y, SDL_Surface *surface ){ borderrect.x = x; borderrect.y = y; lightrect.x = x + 1; lightrect.y = y + 1; mainrect.x = x + 2; mainrect.y = y + 2; SDL_FillRect( surface, &borderrect, SDL_MapColor( surface->format, borderclr ) ); SDL_FillRect( surface, &lightrect, SDL_MapColor( surface->format, lightclr ) ); SDL_FillRect( surface, &mainrect, SDL_MapColor( surface->format, mainclr ) );}
color[] is an array of SDL_Color's. I used enumerations for the names of the colours. Each block consists of 3 rectangles in each other with colour variations to obtain a pretty effect. If you are confused, take a look at the blocks, I used them in a deviation on DeviantArt: [link]
This topic is closed to new replies.
Advertisement
Popular Topics
Advertisement