Game State Management and DrawableGameComponent

Originally posted to Shawn Hargreaves Blog on MSDN, Wednesday, June 17, 2009

I used the Game State Management sample as a basis for my AppWeek game. I also used several samples that are implemented as a DrawableGameComponent (bloom, lensflare, 3D particles).

Herein lies a dilemma:

I wanted my components to be local to the GameplayScreen, not global to the entire game. I wanted to draw gameplay objects, then particles, then apply the bloom filter, draw the lensflare, and then draw any other popup screens or transition effects over the top of all that. When you pause the game, I don't want particles or bloom appearing on top of the pause menu!

One solution would be to host components directly inside the GameplayScreen, rather than registering them with the main game. I could write my own code to maintain a list of components, taking care to call their Initialize, Update, and Draw methods at the right times and in the right order. This would be elegant and powerful, but also a lot of subtle code to write, and I was in a hurry.

I chose an easier approach. I registered my components globally with the main game, but set their Visible property to false so the system would not attempt to draw them. Then my GameplayScreen looked up the components it was interested in, and manually called their Draw method at the appropriate time.

Here's how I created the components in my Game constructor:

    Components.Add(new LensFlareComponent(this) { Visible = false });
    Components.Add(new BloomComponent(this) { Visible = false });

I added a couple of fields to my GameplayScreen:

    LensFlareComponent lensFlare;
    BloomComponent bloom;

I added this method to GameplayScreen (using LINQ extension methods, so you need the System.Linq namespace for it to compile):

    T FindComponent<T>()
    {
        return ScreenManager.Game.Components.OfType<T>().First();
    }

GameplayScreen.LoadContent looks up the components it is interested in:

    lensFlare = FindComponent<LensFlareComponent>();
    bloom = FindComponent<BloomComponent>();

Now GameplayScreen.Draw can manually draw these components in the appropriate order:

    level.Sky.Draw(camera.View, camera.Projection);

    DrawCat(cats[0], Color.Red);
    DrawCat(cats[1], Color.Blue);

    DrawNameLabels();

    bloom.Draw(gameTime);

    lensFlare.LightDirection = camera.LightDirection;
    lensFlare.View = camera.View;
    lensFlare.Projection = camera.Projection;
    lensFlare.Draw(gameTime);

    DrawHud();

Tada! Bloom and lensflare rendering is now local to the GameplayScreen, and correctly positioned underneath the pause menu.

Blog index   -   Back to my homepage