Welcome to MSDN Blogs Sign in | Join | Help

Game State Management and DrawableGameComponent

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:

  • Components are global to the entire game. You control what order they draw in, but cannot do complex interleaved things like drawing part of one component, then part of another, then back to the first.

  • The Game State Management sample implements a more dynamic scheme using a stack of screen objects. This allows it to pop up one screen over the top of another, and to implement transitions between screens.

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.

Published Wednesday, June 17, 2009 9:31 AM by ShawnHargreaves

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

Wednesday, June 17, 2009 9:40 PM by Kevin Gadd

# re: Game State Management and DrawableGameComponent

Nice trick. I've actually been manually converting a bunch of GameComponents to unique types because of the drawing hassles you describe - I hadn't ever thought of making them invisible and manually issuing the Draw calls.

I'll have to see if I can apply this to my game; might save me some effort.

Thursday, June 18, 2009 4:56 AM by NRadford

# re: Game State Management and DrawableGameComponent

I've also hit the problems you describe, and though what you've done is great in a rush. It feels like a hack to me.

There really should be a library for this. I feel a new project coming on. "XNAContrib" anyone? :P

Thursday, June 18, 2009 9:12 AM by lance7

# PBF

I have tried it. It works fine. And it is simple like all great things. Thanks for sharing

Saturday, June 20, 2009 8:35 AM by Alponious

# re: Game State Management and DrawableGameComponent

Thanks for posting this.  I've been struggling with GamestateManagement and using Game Components.

One additional problem you run into is if you add your Game Components in GamePlayScreen.cs, every time you cycle back to the main menu from your game and then go back to the game, you re-add those components to your components collection.  If you check your component count, you'll see that number just grow every time you exit to main menu and back to game.  I would assume, over time, that would lead to a crash.  One solution is to Clear all components when you unload content in GamePlayScreen.cs.  But that cleared components I did not want to clear.  So now I manually remove all components that I added in GamePlayScreen but again....it's just a pain.

Tuesday, June 23, 2009 5:15 AM by Simon (Darkside) Jackson

# re: Game State Management and DrawableGameComponent

That's great stuff Shawn, do you think you'll be able to update the GSM sample with everything you've discovered.  I know the APPWeek stuff is usually private but I think this update should be ok?

Thanks

Tuesday, June 23, 2009 12:11 PM by ShawnHargreaves

# re: Game State Management and DrawableGameComponent

Simon: I guess this comes down to how much we want the sample to be just a starting framework, versus how much we want to polish it into something that can be used as-is. The tension here is between making it as robust and powerful as possible, but not adding so much complexity that people can't understand it enough to build their own stuff on top of it. I keep going back and forth as to which goal I think is more important!

Wednesday, July 01, 2009 3:36 AM by Alvin Tang

# re: Game State Management and DrawableGameComponent

Nice stuff!  I've been inspired and am putting together my own mashup this week during my students' test week.

I had an issue getting the Bloom and Lens Flare components to play well together though.  Using something similar to what you have above, by doing this

           DrawModel(shipModel, ship.World);

           DrawModel(groundModel, Matrix.Identity);

           // Bloom the entire scene that we've just drawn.

           bloom.Draw(gameTime);

           lensFlare.View = camera.View;

           lensFlare.Projection = camera.Projection;

           lensFlare.Draw(gameTime);

The lens flare component doesn't get blocked out when a 3D object covers the light source.

Doing the following:

DrawModel(shipModel, ship.World);

           DrawModel(groundModel, Matrix.Identity);

           lensFlare.View = camera.View;

           lensFlare.Projection = camera.Projection;

           lensFlare.Draw(gameTime);

           // Bloom the entire scene that we've just drawn.

           bloom.Draw(gameTime);

... blurs the lens flare as well, but gives allows the flare and light source to be blocked when an object comes between that and the camera.

Am I missing something or is there no elegant fix?  I'm thinking it's the former. :)

Wednesday, July 01, 2009 11:02 AM by ShawnHargreaves

# re: Game State Management and DrawableGameComponent

Hey Alvin, I actually ran into the exact same issue!

The problem is that the lensflare draw method actually does two unrelated things: first it checks visibility with the occlusion query, then it draws the flares. We want to draw the flares over the top of the bloom, but the visibility query needs to take place before the bloom rendering, as bloom will destroy the depth buffer data that it depends on to detect occlusion.

My solution was to split up the LensFlareComponent.Draw method, so I could call the first part of it (up to and including the UpdateOcclusion method) before bloom, and then the remaining part (DrawGlow and DrawFlares) after the bloom.

Wednesday, July 01, 2009 11:33 AM by Alvin Tang

# re: Game State Management and DrawableGameComponent

Hey Shawn,

Thanks! Nice fix.  Got it up and running as you suggested.  Looks sweet!

Cheers!

Leave a Comment

(required) 
required 
(required) 

  
Enter Code Here: Required
 
Page view tracker