How to squash bugs!

posted in YAAG
Published April 22, 2015
Advertisement
Yesterday we talked about the game loop and that in my current version there was a bug. This particular bug was rare and would only be seen on older or very slow computers. So the first step in figuring out a bug is to work out a way to reproduce the issue. Several of my testers were not able to give me specific steps to reproduce the bug. But in this specific issue I had already ran into the bug when I was fooling around with the frame rate.

This specific bug is a good example to work together on and see different steps to working through the problem. Our report from the testers was that in some cases the character would show up multiple times on the game field. So here is snap shot of the bug in action:

display-bug.png

To produce this image all I had to do was first set the FPS to 60, run the game and press down on the keyboard. What is happening is the engine will skip drawing a frame if it runs out of time on a frame. So when we want to move the engine will always process the calculations. For example the following image the player is in position 4 of the grid and wants to move towards the square labeled as 1:

bug-clip1.png

So the user pressed down the [A] key and the game knows now to move the player to the left by one square. But because of the processor overhead the game engine runs out of time on the calculations so it skips the drawing. Next frame comes around again and the player is still pressing the [A] key so the game knows to keep moving him left. This time we have time to render and now we get the artifact showing up on the game field like in the next image:

bug-clip2.png

So this basically equates to the following frame sequence:

Start calculating frame 1

  • Loop through all active objects and process their update method

    • Player is moved left one square x = 43 and old position recorded to the right old_x = 44

  • calculate elapsed time
  • do we have time to render? No skipping rendering

  • Start calculating frame 2

    • Loop through all active objects and process their update method

      • Player is moved left one square x = 42 and old position recorded to the right old_x = 43

  • calculate elapsed time
  • do we have time to render? Yes render frame now!

    • Move to x position 43 and draw the cached value of the game field (Erase the characters old drawn position.)
    • Move to x position 42 and draw the player.


  • If you followed the calculations the first time the player was drawn to the game field was when he was at the x position of 44. So when we skip rendering a frame the player is never removed from the old position as it gets updated every time we move.

    So technically there are two solutions to this problem. One would be to change when we update the old players position which would mean we would then need an event to handle drawing inside the IEntity class. Doing it here could get very complicated and add a ton of code to the Entity class that I'm not sure is necessary. The second fix would be to change the game engine loop to alternate between calculating game entities and drawing them to the screen. In this fix we could add a Boolean value that would set to be true every time the engine gets a chance to render. So the update version of the code would look something like this:[code=auto:108]//... Init code has been clipped for brevity.// The game loopdouble time;bool gfxDrawn = true; // Addeddo{ time = clock(); if(gfxDrawn) // Added { Update(); } // Check default controller for the [ESC] key if(Controller()->ChkInput(VK_ESCAPE)) { _isRunning = false; // Set the run state to false, Game over man! continue; } int sleepTime = static_cast(time + MS_PER_FRAME - clock()); gfxDrawn = false; // Added if(sleepTime > 0 || !gfxDrawn) // Added the not gfxDrawn check { //... Cliped more code from this section for this discussion gfxDrawn = true;// Added // Burn some time since we got done with out work early today! std::this_thread::sleep_for(std::chrono::milliseconds(sleepTime)); }}while(IsRunning()); // Shutdown the engine code as we exitreturn iRetVal;
    This worked perfectly because now I can crank up the frame rate and no longer worry about artifacting on the game field. The caveat to this fix is I decided as the engine designer to sacrifice calculations for graphics. Depending on your type of calculations this may not be an option. In a different environment like a tile or sprite based game this fix wouldn't be needed. But for a text based game that has very little physics or calculations it was okay to skip a calculation for one frame.

    Another thing to think about is the pros and cons of each way to fix the problem. Remember this isn't generic to every problem but is specific to this game. If we decided to move the update of old position to the drawing process then it would only record the last drawn position and we could properly update the character being drawn. The problem that I can foresee is that when a render frame is skipped the player would jump a massive amount of spaces on the field. Most of the time when it is 2-3 pixels of a jump it isn't that big of a deal. But for this game since we are using text jumping 2-3 squares will make the character very difficult to move around the game field. As it is when the frame rate gets over 25 the character is very hard to line up with the monsters.

    The moral here is many times the fix is dependent on the type of problem and each problem may have a unique solution. Please feel free to ask questions if something doesn't make sense to you.
    Previous Entry The Game Loop
    0 likes 0 comments

    Comments

    Nobody has left a comment. You can be the first!
    You must log in to join the conversation.
    Don't have a GameDev.net account? Sign up!
    Profile
    Author
    Advertisement

    Latest Entries

    The Arena Update

    1545 views

    The Arena Update

    1619 views

    Game Loop Speed 2.0

    2458 views

    The Game Loop

    2824 views

    Introduction

    2214 views
    Advertisement