Jump to content
  • Advertisement
Sign in to follow this  
c4c0d3m0n

OOP style question

This topic is 3719 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 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)?

Share this post


Link to post
Share on other sites
Advertisement
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.

  • 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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
"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.

Share this post


Link to post
Share on other sites
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?

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.



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 point
Block::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]

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!