Code Review: Breakout

Started by
15 comments, last by rip-off 10 years, 11 months ago

Congratulations on completing your game! Completing any game is no small task.

Here are some things I noticed when reviewing your code;

  • By placing the projects own header files in stdafx.h, you are causing the entire game to be recompiled when any header changes. This might be manageable in smaller projects, but it will not scale as your project grows.

  • As the automatically generated comments in stdafx.h state, the file should generally be used for external headers (e.g. SFML). There are "internal" headers that it may make sense to include here, such as commonly included utility headers (random.h might be a good candidate).

  • You should not use preprocessor defines for compile time constants. Use the language feature:

    
    const int SCREENWIDTH = 832;
    const int SCREENHEIGHT = 600;
    const int PADDLEYPOS = 550;
    const int MAXPADDLESPEED = 400; 

  • Your game class has a a number helper functions, for example setTextures(). These should be "private", not "public". Likewise, try to design your program so you do not have public fields. The bricks and window members should be private. The event member can probably be a local variable to your event handling code.

  • Consider using an enumeration for the brick types, rather than strings. A typo in a string literal will not be caught at compile time.

  • You seem to have a lot of static variables. I don't think you really need any of them. It should be simple to convert them to members of the Game class. There may be better solutions.

  • Boolean functions such as Game::isExiting can be written to return the expression, rather than write a full if/else statement:

    
     bool Game::isExiting(){
        return m_GameState == exiting;
    } 

  • If statements ending in a semicolon is highly unusual and normally sets alarm bells ringing for most programmers. Consider at least moving the semicolon to the next line and adding a comment saying "intentionally blank" or something. Better still is to avoid writing if conditions that require no logic.

    Instead of:

    
     case gamelost:
        lresult = m_inpMgr.lostEvents(m_Event);
        if (lresult == Input_Manager::nada); 
        else if (lresult == Input_Manager::mainMenu)
            m_GameState = title;
        break; 

    Consider omitting the "nada" branch:

    
     case gamelost:
        lresult = m_inpMgr.lostEvents(m_Event);
        if (lresult == Input_Manager::mainMenu)
            m_GameState = title;
        break; 

  • Some parts of your Game class are complicated by trying to handle each case in one large if/else block. Moving sub-conditions into sub-blocks makes it much easier to read and understand:

    
     if (gresult == Input_Manager::prev)
        m_GameState = title;
    else if (gresult == Input_Manager::paused)
        m_GameState = paused;
    else if (gresult == Input_Manager::idle) {
        if(m_Paddle.livesLeft() < 0) {
            m_GameState = gamelost;
        } else if(isLevelWon()) {
            m_GameState = gamewon;
        }
    } 

  • If you decouple the brick drawing from the brick type, you could probably have a vector of values rather than smart pointers.

  • Your Game_Object class has a virtual member function, it's destructor should also be virtual. Alternatively, consider removing the virtual initialise function, instead require all game objects to be correctly initialised during construction. The base class should require the texture be passed through it's constructor, it should not be necessary to then allow clients to change the texture afterwards.

  • Sometimes code can be easier to read if you pull a common expression into a local variable. Compare:

    
    // Original
    sf::Vector2f Game_Object::getCenter(){
        return sf::Vector2f(m_Sprite.getGlobalBounds().left + m_Sprite.getGlobalBounds().width / 2, m_Sprite.getGlobalBounds().top + m_Sprite.getGlobalBounds().height / 2);
    }
    
    // Local variable 
    sf::Vector2f Game_Object::getCenter(){
        sf::FloatRect bounds = m_Sprite.getGlobalBounds();
        return sf::Vector2f(bounds.left + bounds.width / 2, bounds.top + bounds.height / 2);
    } 

  • Your coding convention is inconsistently applied. For example, Brick::alive and Ball::isBallMoving should start with "m_" to match the other members.

  • The logic in Ball::handleCollisions is very complex and appears to contain much duplicate code. Consider splitting this into multiple functions, one of which determines if any collision occurred, another which calculates which way to bounce, and another one which handles the red/blue brick distinctions.

  • Your input manager is tightly coupled to the specifics of the game states. In one instance, it requires detailed knowledge of where the buttons are and their dimensions. This is exacerbated by the fact that magic numbers are used here, rather than named constants.

  • Your level loading code computes the positions for each brick as it loads individual bricks. This could probably be moved into a separate loop that is run after the individual bricks are instantiated. In fact, your current code contains a bug because the "count" variable in initialised at the start of the function, but it gets incremented many times as the two loops run. "yPos", and to a lesser extend "xPos",

  • Your level loading code does not handle errors very well. Ideally, your code should be able to report to the user if the file is missing, unreadable or corrupt. At the moment, your code prints a log message in the first case, but it appears that the program would still try to run without any bricks. If the file itself is corrupt, then you'll get very strange results.

  • There is no need to declare and define empty destructors, unless you wish to mark them as "virtual", or perhaps to show that the class obeys the rule of three but for some reason the destructor ends up being empty (in that case I would also consider adding a comment to this effect).

  • Speaking of the rule of three, in larger projects consider marking classes as noncopyable where they cannot or should not have value semantics.

Advertisement

@rip-off: Wow. I was hoping for somebody to dissect my code like that. I really appreciate the time you took to look at my game. Thanks a lot.

By placing the projects own header files in stdafx.h, you are causing the entire game to be recompiled when any header changes. This might be manageable in smaller projects, but it will not scale as your project grows. As the automatically generated comments in stdafx.h state, the file should generally be used for external headers (e.g. SFML). There are "internal" headers that it may make sense to include here, such as commonly included utility headers (random.h might be a good candidate).

I'll remember that for my next C++ project. Now that I think about it with what I have done, I have defeated the whole purpose of using pre-compiled headers. I feel like an idiot smile.png.

You should not use preprocessor defines for compile time constants. Use the language feature

I have made this change, but can you tell me the downside of using preprocessor defines and what are the benefits of using compile time constants, as for this program I don't apparently see the benefit of using constants. To me the only difference I see is the program would have to allocate memory for the constants, and no memory would be allocated of the "define".

Your game class has a a number helper functions, for example setTextures(). These should be "private", not "public". Likewise, try to design your program so you do not have public fields. The bricks and window members should be private. The event member can probably be a local variable to your event handling code.

The function setTextures() and the member m_Window could have been made private, but I'm passing the brick object to another class which would then need to call some of its functions, if I set it to private the class to which the brick was passed to will not be able to call its functions and that is why I have made this public. I see how the event member should have been part of the event handling class. I'll be sure to adjust this accordingly for my next game. Thanks

You seem to have a lot of static variables. I don't think you really need any of them. It should be simple to convert them to members of the Game class. There may be better solutions.

Yes I could have made them members of the game class, but I didn't want my game class to be littered with a hundred members. So, instead I decided to make these variables local to the functions that needed them and made them static so they persist even when the function ends. These variables are not needed anywhere except the functions of they are a part of. In a much bigger project what would be a disadvantage or danger of using these static variables?

Boolean functions such as Game::isExiting can be written to return the expression, rather than write a full if/else statement:

Thanks I've made this change. This was really basic I should have known that m_GameState == exiting; will return either a true or false depending on the value in m_GameState.

If statements ending in a semicolon is highly unusual and normally sets alarm bells ringing for most programmers. Consider at least moving the semicolon to the next line and adding a comment saying "intentionally blank" or something. Better still is to avoid writing if conditions that require no logic.

Noted. But the reason I have it there was that it was possible for the lresult variable to get a value of "nada". So I explicitly included it there and told the compiler to not do anything if that happens.

Some parts of your Game class are complicated by trying to handle each case in one large if/else block. Moving sub-conditions into sub-blocks makes it much easier to read and understand:

I see, yeah that if/else block that you highlighted is pretty ugly. I've made the change like you have suggested looks a lot more readable now.

If you decouple the brick drawing from the brick type, you could probably have a vector of values rather than smart pointers.

There is a reason why I did that. My brick object has a property called "alive". I only want to draw bricks that are marked alive = true. Whenever a ball hits a brick that is breakable the bricks value is changed to alive = false. So during my for loop I have to check each brick's status. Now if you have a better solution to this problem please let me know how I can decouple the brick drawing from the brick type.

Your Game_Object class has a virtual member function, it's destructor should also be virtual. Alternatively, consider removing the virtual initialise function, instead require all game objects to be correctly initialised during construction. The base class should require the texture be passed through it's constructor, it should not be necessary to then allow clients to change the texture afterwards.

What benefit would I have for making the destructor virtual? There is nothing that I am allocating on the heap for any of the derived classes of the Game_Object class. The paddle and ball are allocated on the stack so destruction of those should be done when the stack gets destroyed. Yes I am allocating the bricks on the heap, BUT i am using smart pointers so that should take care of the destruction automatically for me right? There is no member variable in any of the derived classes that gets allocated to the heap, only the object itself (in the brick's case) gets allocated to the heap. Am I missing something here? The initialize function is different for the derived classes, it basically initializes the position back to the center of the screen for the ball and the paddle. This function gets called each time the player misses the ball or hits the red brick which also counts as a life lost. I need to call this function several times in the game, if I moved the contents of this function the the constructor then I wouldn't be able to initialize the position of the paddle and the ball each time the player misses the ball.

I'll remove the setTexture() function from the Game_Object class and actually set the texture during construction.

Sometimes code can be easier to read if you pull a common expression into a local variable. Compare:


// Original
sf::Vector2f Game_Object::getCenter(){
    return sf::Vector2f(m_Sprite.getGlobalBounds().left + m_Sprite.getGlobalBounds().width / 2, m_Sprite.getGlobalBounds().top + m_Sprite.getGlobalBounds().height / 2);
}

// Local variable 
sf::Vector2f Game_Object::getCenter(){
    sf::FloatRect bounds = m_Sprite.getGlobalBounds();
    return sf::Vector2f(bounds.left + bounds.width / 2, bounds.top + bounds.height / 2);
} 

Wow, that looks really clean and is easily readable, I'll be sure to keep this in mind. I have done similar sort of this ugly stuff in several places, and it makes it hard to understand when I go back and look at it.

Your coding convention is inconsistently applied. For example, Brick::alive and Ball::isBallMoving should start with "m_" to match the other members.

Thanks for pointing that out, I have made the change.

The logic in Ball::handleCollisions is very complex and appears to contain much duplicate code. Consider splitting this into multiple functions, one of which determines if any collision occurred, another which calculates which way to bounce, and another one which handles the red/blue brick distinctions.

This function is probably the ugliest part of my game. I had a lot of problems with how I should go about handling the collision with the bricks, and my limited math knowledge only made it worse. The majority of the if else blocks are determining the various directions of the ball hitting the brick and reacting differently to where the ball hits the brick. I just couldn't come up with a better solution due to my limited math knowledge. You're right it could have been easier on the eyes if I broke it down into different functions but this was one of the last things I had to do to finish my game and I just hacked together this code, as I was losing motivation on this project fast (I wanted to move on to another project), but I forced myself to finish this game.

I spent at least three or four days on just this function alone. I am considering using Box2D to handle the physics and collision for my next projects, or I'll have to take the time and learn the math. Coming into gamedev I had no idea the amount of stuff that goes into making even the smallest of games, there is so much more that I still need to learn that it sometimes feels too overwhelming, which will probably make me go the easier route and use Box2D smile.png.

Your input manager is tightly coupled to the specifics of the game states. In one instance, it requires detailed knowledge of where the buttons are and their dimensions. This is exacerbated by the fact that magic numbers are used here, rather than named constants.

I'll probably make a button class for the UI stuff in later projects, where each button knows its position and will be able to tell the game when it was clicked instead of using magic numbers. What would be a better way to handle the input?

Your level loading code computes the positions for each brick as it loads individual bricks. This could probably be moved into a separate loop that is run after the individual bricks are instantiated. In fact, your current code contains a bug because the "count" variable in initialised at the start of the function, but it gets incremented many times as the two loops run. "yPos", and to a lesser extend "xPos",

The loop is separate. It appears to be inside the previous loop because the indentation is messed up. The count variable is also fine. It is used in only the second loop, the first loop ends before the second one starts. I have a const variable called BRICKROW = 13, which means that I can only have 13 bricks in one row, so after the 13th brick has been set in a row the yPos should be incremented with the yOffset so new row could be made and the xPos is initialized back to 0.0f. I do this by using that count variable that I have declared before the loop starts, when each brick is set, I increment count by 1, when count reaches 13, then the if block (if (count % BRICKROW == 0)) kicks in and the xpos and ypos are adjusted for the new row. The next time the new row will be made when count reaches 26 because the remainder will then be 0 again when dividing 26 by 13, after that the new row will be made when the count reaches 39 so on and so forth. It should not cause a bug.

I could have done this a little differently and reset the count back to 0 when the new row was made but then I would have had to change the if block to ( if (count == BRICKROW)). Both of these solutions do the same thing. Now unless I am totally missing something please let me know what it is I need to be doing.

Your level loading code does not handle errors very well. Ideally, your code should be able to report to the user if the file is missing, unreadable or corrupt. At the moment, your code prints a log message in the first case, but it appears that the program would still try to run without any bricks. If the file itself is corrupt, then you'll get very strange results.

You're right, I am aware of this. I should be throwing an exception here and then handling it accordingly, but as I have stated above I was in a hurry to just finish this game, and this part I wrote at the end so I was too lazy to do all that. I promise this won't happen in my future projects I swear rolleyes.gif.

There is no need to declare and define empty destructors, unless you wish to mark them as "virtual", or perhaps to show that the class obeys the rule of three but for some reason the destructor ends up being empty (in that case I would also consider adding a comment to this effect). Speaking of the rule of three, in larger projects consider marking classes as noncopyable where they cannot or should not have value semantics.

I wasn't the one that put them there. Visual C++ did that for me automatically, when it created the class for me. I had no intention of putting any code inside the destructors, but I decided to leave them there as I thought they aren't doing anything so no harm. I'll remove them if they get put in there automatically for all my future projects if I don't intend to use them. I had briefly read about the rule of three when I was learning C++ but I had totally forgot what it was, so thanks for reminding me, I'll keep the rule in mind next time.

For my next project I have decided to use C# and XNA, that should remove most of the C++ related problems like including headers etc. I'm already enjoying C# a lot and XNA makes a lot of the stuff real easy. Thanks for reviewing my code, I really appreciate it.


Your Game_Object class has a virtual member function, it's destructor should also be virtual. Alternatively, consider removing the virtual initialise function, instead require all game objects to be correctly initialised during construction. The base class should require the texture be passed through it's constructor, it should not be necessary to then allow clients to change the texture afterwards.

What benefit would I have for making the destructor virtual? There is nothing that I am allocating on the heap for any of the derived classes of the Game_Object class. The paddle and ball are allocated on the stack so destruction of those should be done when the stack gets destroyed. Yes I am allocating the bricks on the heap, BUT i am using smart pointers so that should take care of the destruction automatically for me right? There is no member variable in any of the derived classes that gets allocated to the heap, only the object itself (in the brick's case) gets allocated to the heap. Am I missing something here? The initialize function is different for the derived classes, it basically initializes the position back to the center of the screen for the ball and the paddle. This function gets called each time the player misses the ball or hits the red brick which also counts as a life lost. I need to call this function several times in the game, if I moved the contents of this function the the constructor then I wouldn't be able to initialize the position of the paddle and the ball each time the player misses the ball.
I'll remove the setTexture() function from the Game_Object class and actually set the texture during construction.

ok so I did a little research on my own and checked my game of how the objects were being destroyed and it is fine. However, you were 100% correct, I should be using a virtual destructor in this case nonetheless. My ignorance in this could have easily resulted in a memory leak or the objects not being destroyed properly. I was lucky I was not upcasting my brick class pointer to the game object class anywhere. I was doing this:


bricks.push_back(std::unique_ptr<Brick>(new Brick(m_imgMgr.getBlue(), type)));

If I had done something like this:


std::unique_ptr<Game_Object> pGame_Object = std::unique_ptr<Game_Object>(new Brick());

then I would have been in trouble. Because the unique pointer will not properly destroy both objects. This following code WILL properly destroy both objects


std::shared_ptr<Game_Object> pGame_Object = std::shared_ptr<Game_Object>(new Brick());

It seems the shared pointer will destroy the objects properly even without the virtual destructor. If I want to use unique pointers like in my case and I have for example a base class called enemy and several derived classes, and I have a container of enemy class unique pointers and I have filled it up by upcasting then I NEED vitual destructors, or I would need to use shared pointers instead. I could have run into this problem also if I had a factory class responsible for instantiating these classes.

For my own understanding I did a test and I'm sharing it here so other people like me can benefit too. Consider the following snippet of code:


#include <iostream>
#include <memory>

class Tiger
{
public:
	Tiger() {}
	virtual void talk() { std::cout << "I am just a tiger!" << std::endl; }
	~Tiger() { std::cout << "Tiger is destroyed!" << std::endl; }
};

class BengalTiger : public Tiger
{
public:
	BengalTiger() {}
	void talk() { std::cout << "I am a Bengal Tiger!" << std::endl; }
	~BengalTiger() { std::cout << "Bengal tiger is destroyed" << std::endl; }
};

int main()
{
	std::unique_ptr<Tiger> pT = std::unique_ptr<Tiger>(new BengalTiger());

	pT->talk();

	return 0;
}

Im upcasting the Bengal Tiger unique pointer to a Tiger unique pointer, and don't have a vitual destructor. The output is:


I am a Bengal Tiger!
Tiger is destroyed!

The object was not destroyed properly "Bengal tiger is destroyed" didn't show up.

Changing the unique pointer to a shared pointer in main():


std::shared_ptr<Tiger> pT = std::shared_ptr<Tiger>(new BengalTiger());

I still don't have a virtual destructor, but object will be destroyed properly and the output should now be:


I am Bengal Tiger!
Bengal tiger is destroyed
Tiger is destroyed!

Now if I change the shared pointer back to a unique pointer and make destructor virtual I'll get the same output as the one I got by using shared pointers and the object will be destroyed properly.

Thanks @rip-off for pointing this out, it seems one needs to be careful in C++ even when dealing with smart pointers.

@rip-off: Wow. I was hoping for somebody to dissect my code like that. I really appreciate the time you took to look at my game. Thanks a lot.

You are most welcome.

I have made this change, but can you tell me the downside of using preprocessor defines and what are the benefits of using compile time constants, as for this program I don't apparently see the benefit of using constants.

The preprocessor does not understand C++. It only has a rather basic concept of token replacement. For example, a preprocessor define inside a namespace will not be restricted to replacing symbols inside that namespace.

To me the only difference I see is the program would have to allocate memory for the constants, and no memory would be allocated of the "define".

C++ does not map directly to machine code. There is no particular reason why a modern compiler, in Release mode, would allocate memory for a const value unless you do something such as take it's address.

The function setTextures() and the member m_Window could have been made private, but I'm passing the brick object to another class which would then need to call some of its functions, if I set it to private the class to which the brick was passed to will not be able to call its functions and that is why I have made this public.

That isn't quite how private works. A class may pass it's private members to any other function or class. If it does so by reference, then the class or function may write directly to the "private" value. Private does not prohibit a class designer from making the decision to share it's member with code it "trusts". It could also publish a member function pointer to a private function.

What private is intended for is to ensure that members and functions the class designer does want to keep "secret" cannot legally be accessed by other portions of the code. One reason for this is that most classes establish invariants.

There are strong invariants such as the standard containers, where if some code decided to change on of the members it could break the class (and usually the program!). A slightly "weaker" form of invariant might be covering the "business logic" or "game mechanics". That is, you might want to enforce a rule such as the player can only have so many items in their inventory, or that to obtain a new item from a shopkeeper the player must lose the corresponding number of coins from their pouch. If these kinds of code can be hidden behind a class interface that does not allow the logic to be misused, then the chances of a bug in the game logic is greatly reduced - and when one does occur it is most likely in that classes implementation.

Yes I could have made them members of the game class, but I didn't want my game class to be littered with a hundred members. So, instead I decided to make these variables local to the functions that needed them and made them static so they persist even when the function ends. These variables are not needed anywhere except the functions of they are a part of.

I believe the static variables in Game::setTextures() and Game::run() could be made non-static local variables instead.

For example:

 
switch (m_GameState)
{
case title:
    {
        Input_Manager::titleResult result = m_inpMgr.titleEvents(m_Event);        
        if (result == Input_Manager::exit)
            m_GameState = exiting;
        else if (result == Input_Manager::about)
            m_GameState = about;
        else if (result == Input_Manager::newGame)
            m_GameState = running;
    }
    break;
case about:
    {
        Input_Manager::aboutResult result = m_inpMgr.aboutEvents(m_Event);
        if (result == Input_Manager::back)
            m_GameState = title;
    }
    break;
case running:
    {
        Input_Manager::gameResult result = m_inpMgr.gameEvents(m_Event, m_Paddle, m_Ball);
        if (result == Input_Manager::prev)
            m_GameState = title;
        else if (result == Input_Manager::paused)
            m_GameState = paused;
        else if (result == Input_Manager::idle && m_Paddle.livesLeft() > 0 && isLevelWon() == false)
            /* nothing to do... */;
        else if (result == Input_Manager::idle && m_Paddle.livesLeft() > 0 && isLevelWon() == true)
            m_GameState = gamewon;
 
        else if (result == Input_Manager::idle && m_Paddle.livesLeft() < 0)
            m_GameState = gamelost;
    }
    break;
case gamelost:
    {
        Input_Manager::gamelostResult result = m_inpMgr.lostEvents(m_Event);
        if (result == Input_Manager::nada)
            /* nothing to do... */;
        else if (result == Input_Manager::mainMenu)
            m_GameState = title;
    }
    break;
case gamewon:
    {
        Input_Manager::gamewonResult result = m_inpMgr.wonEvents(m_Event);
        if (result == Input_Manager::idling)
            /* nothing to do... */;
        else if (result == Input_Manager::menu)
            m_GameState = title;
    }
    break;
case paused:
    {
        Input_Manager::pauseResult result = m_inpMgr.pauseEvents(m_Event);
        if (result == Input_Manager::doNothing)
            /* nothing to do... */;
        else if (result == Input_Manager::theMainMenu)
            m_GameState = title;
        else if (result == Input_Manager::resume)
            m_GameState = running;
    }
    break;
default:
    break;
}
 

Adding a block inside each case, I've created a separate scope for each result variable of the appropriate type. At this point, it becomes quite clear that each case can be written as a helper function which can change the current gamestate:

 
void Game::handleTitleEvents() {
    Input_Manager::titleResult result = m_inpMgr.titleEvents(m_Event);        
    if (result == Input_Manager::exit)
        m_GameState = exiting;
    else if (result == Input_Manager::about)
        m_GameState = about;
    else if (result == Input_Manager::newGame)
        m_GameState = running;
}
 
void handleAboutEvents() {
    Input_Manager::aboutResult result = m_inpMgr.aboutEvents(m_Event);
    if (result == Input_Manager::back)
        m_GameState = title;
}
 
viod Game::handleRunningEvents() {
    Input_Manager::gameResult result = m_inpMgr.gameEvents(m_Event, m_Paddle, m_Ball);
    if (result == Input_Manager::prev)
        m_GameState = title;
    else if (result == Input_Manager::paused)
        m_GameState = paused;
    else if (result == Input_Manager::idle && m_Paddle.livesLeft() > 0 && isLevelWon() == false)
        /* nothing to do... */;
    else if (result == Input_Manager::idle && m_Paddle.livesLeft() > 0 && isLevelWon() == true)
        m_GameState = gamewon;
    else if (result == Input_Manager::idle && m_Paddle.livesLeft() < 0)
        m_GameState = gamelost;
}
 
void Game::handleGameLostEvents() {
    Input_Manager::gamelostResult result = m_inpMgr.lostEvents(m_Event);
    if (result == Input_Manager::nada)
        /* nothing to do... */;
    else if (result == Input_Manager::mainMenu)
        m_GameState = title;
}
 
void Game::handleGameWonEvents() {
    Input_Manager::gamewonResult result = m_inpMgr.wonEvents(m_Event);
    if (result == Input_Manager::idling)
        /* nothing to do... */;
    else if (result == Input_Manager::menu)
        m_GameState = title;
}
 
void Game::handlePausedEvents() {
    Input_Manager::pauseResult result = m_inpMgr.pauseEvents(m_Event);
    if (result == Input_Manager::doNothing)
        /* nothing to do... */;
    else if (result == Input_Manager::theMainMenu)
        m_GameState = title;
    else if (result == Input_Manager::resume)
        m_GameState = running;
}

 

switch (m_GameState)
{
case title: handleTitleEvents(); break;
case about: handleAboutEvents(); break;
case running: handleRunningEvents() break;
case gamelost: handleGameLostEvents(); break;
case gamewon: handleGameWonEvents(); break;
case paused: handlePausedEvents(); break;
default:
    assert(false && "Unknown gamestate");
    break;
}
 

In a much bigger project what would be a disadvantage or danger of using these static variables?

If you ever start using multiple threads then the static variables become a hazard, as they are effectively globals shared by any threads which happen to call that function. Aside from that, static variable suffer many of the downsides of global variables. It is harder to understand and reason about a function which makes use of hidden, global state. The function becomes hard to unit test in isolation because you cannot control the value of the static variable explicitly during each test.

Noted. But the reason I have it there was that it was possible for the lresult variable to get a value of "nada". So I explicitly included it there and told the compiler to not do anything if that happens.

It is unnecessary to instruct the compiler to do nothing in this case. The compiler will only do what you tell it to do. Now, there is a reasonable argument to be made that this is for the benefit of the code maintainers, you're showing that you are aware of this case but do not need to take any special steps. I would use a comment to make this crystal clear, as the first instinct of most programmers reading if(condition); is that the author has omitted some code from the branch body.

There are a couple of ways of making the intent clearer, for example:

 
 case gamelost:
    lresult = m_inpMgr.lostEvents(m_Event);
     if (lresult == Input_Manager::mainMenu) {
        m_GameState = title;
    } else { 
        // lresult == Input_Manager::nada, nothing to do...
    }
    break; 
 

You can see I used such a mechanism earlier in this post.

There is a reason why I did that. My brick object has a property called "alive". I only want to draw bricks that are marked alive = true. Whenever a ball hits a brick that is breakable the bricks value is changed to alive = false. So during my for loop I have to check each brick's status. Now if you have a better solution to this problem please let me know how I can decouple the brick drawing from the brick type.

One way is to have two classes. One is just the Brick data, and another knows how to draw bricks:

class Brick {
public:
     bool isAlive() const;
     BrickType getType() const;
// ...
};
 
class BrickRenderer {
public:
    BrickRenderer(TextureHandle red, TextureHandle blue);
 
    // Note: Bricks are value objects!
    void render(const std::vector<Brick> &bricks, Window &window);
private:
    Sprite sprite;
    Texture red;
    Texture blue;
};
 
void BrickRenderer::render(const std::vector<Brick> &bricks) {
    for(auto i = bricks.cbegin() ; i != bricks.cend() ; ++i) {
        Brick &brick = bricks;
        if(brick.isAlive()) {
             switch(brick.getType()) {
             case BrickType_Red:
                 sprite.setTexture(red);
                 break;
             case BrickType_Blue:
                 sprite.setTexture(blue);
                 break;
             default:
                 assert(false && "No such brick type!");
                 break;
             }
             sprite.setPosition(brick.getPosition());
             window.draw(sprite);
        }
    }
}

It might be possible to render a texture directly to the screen at a given co-ordinate, but I haven't used SFML so the above is psuedo-code.

Of course, the other convenience functionality that Sprite provides might make it worthwhile to keep the per-Brick sprite. It isn't a huge deal here, but the basic idea can have a lot of value when the number of objects gets very high (e.g. particle systems).

This function is probably the ugliest part of my game. I had a lot of problems with how I should go about handling the collision with the bricks, and my limited math knowledge only made it worse. The majority of the if else blocks are determining the various directions of the ball hitting the brick and reacting differently to where the ball hits the brick. I just couldn't come up with a better solution due to my limited math knowledge. You're right it could have been easier on the eyes if I broke it down into different functions but this was one of the last things I had to do to finish my game and I just hacked together this code, as I was losing motivation on this project fast (I wanted to move on to another project), but I forced myself to finish this game.

I spent at least three or four days on just this function alone. I am considering using Box2D to handle the physics and collision for my next projects, or I'll have to take the time and learn the math. Coming into gamedev I had no idea the amount of stuff that goes into making even the smallest of games, there is so much more that I still need to learn that it sometimes feels too overwhelming, which will probably make me go the easier route and use Box2D smile.png.

That is fair enough.

What would be a better way to handle the input?

There are a number of solutions. It is nice to decouple input detection from the input "response". A simple structure which has served me well for small games, create a separate class for each game state with a basic interface for interacting with a given state:

class State {
     virtual ~State() {}
 
     virtual void render(/* ... */) = 0;
     virtual void update(/* ... */) = 0;
     virtual void handleEvent(const Event &event) = 0;
};

With such a structure, your top level object will have a pointer to the active state, and can poll for events, passing them to the active state. You probably no longer need an "Input Manager" with such an approach.

The loop is separate. It appears to be inside the previous loop because the indentation is messed up. ...

My bad! I was confused that it worked at all, but I didn't think about it too much, I figured that your data made it appear to work.

ok so I did a little research on my own and checked my game of how the objects were being destroyed and it is fine. However, you were 100% correct, I should be using a virtual destructor in this case nonetheless.

Yes, it is just a common C++ rule to say that any class which has a virtual function must also have a virtual destructor. It is a safety net that catches any cases where an object is destroyed through a pointer to a parent type.

@rip-off: well those are some very good ideas, but you have opened my eyes to the fact that I really need to get educated on good software practices. I've decided to hold off on my next project and concentrate on a ton of reading for a few weeks. Apart from learning more about state machines and handling input I've got this book called Code Complete A practical handbook of software construction, which I think I should finally give a read. Learning some syntax and doing some tutorials on some graphics API are just not gonna cut it. Hopefully the reading will educate me on good software engineering practices which I will then try to incorporate into my future projects. Thanks for the feedback.

@rip-off: well those are some very good ideas, but you have opened my eyes to the fact that I really need to get educated on good software practices. I've decided to hold off on my next project and concentrate on a ton of reading for a few weeks. Apart from learning more about state machines and handling input I've got this book called Code Complete A practical handbook of software construction, which I think I should finally give a read. Learning some syntax and doing some tutorials on some graphics API are just not gonna cut it. Hopefully the reading will educate me on good software engineering practices which I will then try to incorporate into my future projects. Thanks for the feedback.


While this is always a good idea I just wanted to say the best way to get better at code design is to just keep coding. This is because their is no one way to do things. Though it will be a good idea to read about but try to implement it also. Trust me, no matter how much you read your first couple projects will not be the best code design. Professionals still modify some things from game to game and engine to engine.

So read and try to write down something's and see if you can draw it out. Then code. At this point on your first couple projects you should be thinking more about writing clean code than the structure of the program. Of you do this before you know it you'll be doing some good code design without even knowing it.

I concur, keep writing programs. You will only know when to apply which software practises when you build up a working knowledge of how larger and larger programs are written.

An important thing is not to get too bogged down in producing "perfect" software, to the point where your ability to complete projects is degraded. It is something that I, and I would guess many, hobby developers struggle with. There is a delicate balance between being a productive programmer (in terms of the project end goals, not mere lines of code!) while simultaneously keeping the code manageable. It is only by experiencing the problems caused by being unproductive due to spending too much time making your code "perfect". and the problems caused by being unable to be productive due to unaddressed issues in the code, that you will learn when and how to strike this balance.

After several years working as a programmer, and many more as a hobbyist, I am still learning.

This topic is closed to new replies.

Advertisement