The One With The Game Loop

The One With The Game Loop

When building any game, you always start out writing the “Game Loop”. If all the game data was the blood of our game, the game loop would be the pumping of the heart that keeps us running. In Win32 programming, the game loop typically consists of several steps: initializing graphics, rendering, and handling messages. The power of working in a managed, object-oriented environment is that we can take advantage of several advancements that Microsoft has provided us with – mainly Events and the various method overrides that accompany these events when programming Windows Forms.

If you were to look at the “typical” game loop (like those provided with the DirectX SDK), you would most likely find something along the following lines:

static void Main()

{

      using (BattleForm battleForm = new BattleForm())

      {

            battleForm.InitializeGraphics();

            battleForm.Show();

            while(battleForm.Created)

            {

                  battleForm.Render();

                  Application.DoEvents();

            }    

      }

}

While the code above appears fine at first glance, there is an underlying issue that makes this a problematic game loop. The problem lies with the Application.DoEvents() call. Deep within the DoEvents() method, there are several allocations that take place. This may be acceptable for a normal Windows application, but for a game, these allocations can quickly become troublesome. The reason that these allocations can become troublesome is because we will be making this method call at least thirty times per second for a typical game. With all these extra allocations, our garbage collector will more than likely be running more often than it needs to be. The extra runs of the garbage collector can lead to our game objects being promoted from Generation0 to Generation1 when they don’t need to be. For more information on this, please see the post entitled “The Render Loop” by Tom Miller.

So how do we prevent the above from happening and still have a readable game loop? The answer is actually quite simple. All we really need to do is to leverage the functionality of Windows Forms that Microsoft has already provided us. We can do this by changing the game loop from above to something like the following:

static void Main()

{

      using (BattleForm battleForm = new BattleForm())

      {

            battleForm.Initialize();

            battleForm.Show();

            Application.Run(battleForm);

      }

}

But, wait a minute; we aren’t calling a Render function, so how can we possibly be drawing anything?!?!?!? Well, we will now do all our rendering through the OnPaint override that is provided by Windows forms. To ensure that OnPaint is called as often as possible, we will want to invalidate our form after we perform all our rendering.

protected override void OnPaint(PaintEventArgs e)

{

      // Render here

      // IMPORTANT: Ensure that we are called again

      // as soon as we are drawn

      this.Invalidate();

}

However, there is a catch with the code above. We are not guaranteed that our OnPaint method will always be called. This is because Windows will do some of the painting itself. While I am not entirely sure why this is the case (probably something having to do with performance or handling other window behaviors), there is an easy way to override this Windows behavior. In our form’s constructor, we need to inform Windows that all painting is going to be done in our OnPaint method.

public BattleForm()

{

      this.Text = "Battle!";

      this.Size = new Size(800, 600);

      this.StartPosition = FormStartPosition.CenterScreen;

      this.FormBorderStyle = FormBorderStyle.None;

      this.Cursor.Dispose();

      // IMPORTANT: Tell Windows that we will do all 

      // drawing in the Paint method

      this.SetStyle(ControlStyles.AllPaintingInWmPaint | 

                    ControlStyles.Opaque, true);

}

With that finished, we now have the bones of our game setup. The code for the entire game loop at this point should look like the following:

static void Main()

{

      using (BattleForm battleForm = new BattleForm())

      {

            battleForm.Initialize();

            battleForm.Show();

            Application.Run(battleForm);

      }

}

public BattleForm()

{

      this.Text = "Battle!";

      this.Size = new Size(800, 600);

      this.StartPosition = FormStartPosition.CenterScreen;

      this.FormBorderStyle = FormBorderStyle.None;

      this.Cursor.Dispose();

      // IMPORTANT: Tell Windows that we will do all 

      // drawing in the Paint method

      this.SetStyle(ControlStyles.AllPaintingInWmPaint |

                    ControlStyles.Opaque, true);

}

public void Initialize()

{

      // This is where we will initialize our engine

}

protected override void OnPaint(PaintEventArgs e)

{

      // Render here

      // IMPORTANT: Ensure that we are called again as

      // soon as we are drawn

      this.Invalidate();

}

And that’s how our main game loop works. Stay tuned for the next article where we will initialize DirectX.