Welcome to MSDN Blogs Sign in | Join | Help

Automatic XNB serialization and *Content classes

Automatic XNB serialization works best when the same classes are available at content build time and at runtime.

Pedantic correction: if you are making an Xbox game, they can't actually be the SAME classes. You must compile the code twice, once for use on Windows during the content build, then again for runtime use on Xbox. This is fine as long as the shared types live in an assembly that has the same name, version, and public key on both platforms.

Sometimes, though, it just isn't possible to use the same class in both places. For instance the Content Pipeline represents a CompiledEffect as an array of bytecode, but at runtime this same data is loaded into an Effect that sends its shader code through the driver to the GPU. Automatic XNB serialization does not work well for types like this. Fortunately, the things which change between build time and runtime are mostly low-level graphics types, for which the framework already provides the necessary ContentTypeWriter and ContentTypeReader, so you rarely need to bother writing these yourself.

A perniciously irritating situation arises if you have a custom data type that you want to share between build time and runtime, but which aggregates a low level type that is not the same in both places.

Aside: I love the word "pernicious" - it makes me happy whenever I find an excuse to use it  :-)

Let's say we are making a custom sprite class that contains a texture plus a rectangle indicating where it should be drawn on the screen. Should be simple and straightforward, neh? But what type should we use for the texture field? This needs to be a Texture2DContent at build time, but then becomes a Texture2D at runtime.

What to do?

 

Proxy Content Types

The most general purpose, flexible, yet longwinded and even perniciously (yay! my favorite word!) verbose solution is to make two versions of our Sprite class, for instance:

    public class Sprite
    {
        public Rectangle Rectangle;
        public Texture2D Texture;
    }

    [ContentSerializerRuntimeType("SharedDataTypes.Sprite, SharedDataTypes")]
    public class SpriteContent
    {
        public Rectangle Rectangle;
        public Texture2DContent Texture;
    }

We must factor these into separate assemblies, as described in the "Creating New Data Types" section of this article. Armed with these classes, we can create a SpriteContent object using the built-in XmlImporter to deserialize this XML:

    <?xml version="1.0" encoding="utf-8" ?>
    <XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.Pipeline.Graphics">
      <Asset Type="SharedDataTypes.SpriteContent">
        <Rectangle>32 32 256 128</Rectangle>
        <Texture>
          <Mipmaps>
            <Mipmap Type="Graphics:PixelBitmapContent[Microsoft.Xna.Framework.Graphics.Color]">
              <Width>2</Width>
              <Height>2</Height>
              <Pixels>
                <Row>FFFF0000 FF00FF00</Row>
                <Row>FF0000FF FF000000</Row>
              </Pixels>
            </Mipmap>
          </Mipmaps>
        </Texture>
      </Asset>
    </XnaContent>

Ok, it's silly to define texture data in XML like this. But hey, it's just an example. You get the idea, right?

Resulting data flow:

  • XmlImporter reads the source XML into a SpriteContent object
  • The automatic XNB serializer writes the SpriteContent into an .xnb file
  • My game calls ContentManager.Load<Sprite>
  • It gets back an instance of the runtime Sprite class

 

Dynamic Types

Ok, so it sucks having to make two versions of our custom data type. There must be a better way, neh? One option is to go all loosey goosey and use dynamic typing, changing the texture field to type object:

    public class Sprite
    {
        public Rectangle Rectangle;
        public object Texture;
    }

This way we can store either a Texture2DContent or a Texture2D in the same field, so we can use the same Sprite class at build time and runtime (with assemblies as described under "Sharing Data Types Between Build And Runtime" in this article).

We must tweak our source XML to match the new type:

    <?xml version="1.0" encoding="utf-8" ?>
    <XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.Pipeline.Graphics">
      <Asset Type="SharedDataTypes.Sprite">
        <Rectangle>32 32 256 128</Rectangle>
        <Texture Type="Graphics:Texture2DContent">
          <Mipmaps>
            <Mipmap Type="Graphics:PixelBitmapContent[Microsoft.Xna.Framework.Graphics.Color]">
              <Width>2</Width>
              <Height>2</Height>
              <Pixels>
                <Row>FFFF0000 FF00FF00</Row>
                <Row>FF0000FF FF000000</Row>
              </Pixels>
            </Mipmap>
          </Mipmaps>
        </Texture>
      </Asset>
    </XnaContent>

Note how the <Asset Type> attribute has changed from SpriteContent to Sprite, and we added a Type attribute to the <Texture> element. This was not needed when using a proxy content type, because IntermediateSerializer already knew this field was of type TextureContent, but now our field is of type object, the XML must explicitly specify what type of object it wants to create.

The downside is we must now add pernicious (huzzah! there's that word again!) and ugly casts to any code that uses the Texture field, for instance to draw the sprite:

    spriteBatch.Draw((Texture2D)sprite.Texture, sprite.Rectangle, Color.White);

 

Generics

If we define our Sprite class like so:

    public class Sprite<T>
    {
        public Rectangle Rectangle;
        public T Texture;
    }

We can specialize this generic definition to make the same Texture field be either a Texture2DContent or a Texture2D.

We must tweak our source XML to match the new type:

    <?xml version="1.0" encoding="utf-8" ?>
    <XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.Pipeline.Graphics">
      <Asset Type="SharedDataTypes.Sprite[Graphics:Texture2DContent]">
        <Rectangle>32 32 256 128</Rectangle>
        <Texture>
          <Mipmaps>
            <Mipmap Type="Graphics:PixelBitmapContent[Microsoft.Xna.Framework.Graphics.Color]">
              <Width>2</Width>
              <Height>2</Height>
              <Pixels>
                <Row>FFFF0000 FF00FF00</Row>
                <Row>FF0000FF FF000000</Row>
              </Pixels>
            </Mipmap>
          </Mipmaps>
        </Texture>
      </Asset>
    </XnaContent>

Note how there is no longer any need for a Type attribute on the <Texture> element, while the <Asset Type> attribute must now specify what type the generic should be specialized on.

A subtle thing happens here. At build time we are dealing with a Sprite<Texture2DContent>, but at runtime that same data becomes a Sprite<Texture2D>:

    sprite = Content.Load<Sprite<Texture2D>>("sprite");

This works because the automatic XNB serializer is smart enough to understand generics. When it sees a generic type Foo<T>, it looks not only to see whether Foo has a different runtime type, but also to see what the runtime equivalent of T is. If Foo is the same at build time and runtime, but T changes to S, it will automatically update the generic to become Foo<S>.

I think this last solution is my favorite. It doesn't offer much excuse for repeating the word "pernicious", but it makes me happy just the same.

Generic network prediction

Government Health Warning: this post contains more C# than English!

My AppWeek game used code from the Network Prediction sample to smooth the movement of remotely controlled avatars, tanks, and hoverships. To support three types of entity with varying physics, I had to make my prediction implementation more generic than the sample code I started out with.

In the original sample, the Tank class implements prediction by storing its physics state in a nested TankState struct. This allows it to maintain three copies of the TankState, representing the current simulation state, the previous state from immediately before the last network packet was received, and the display state, which is gradually interpolated from the previous state toward the simulation state.

I wanted to move the prediction logic into a base class that could be shared by my Dude, Tank, and Ship classes. Because the physics state was different for each entity type, I had to make this base class a generic.

First, I created an interface describing all the things I needed to be able to do with a physics state structure:

    interface IPredictedState<TState>
        where TState : struct
    {
        void Update(GameInput input, Level level);

        void WriteNetworkPacket(PacketWriter packet);
        void ReadNetworkPacket(PacketReader packet);

        void Lerp(ref TState a, ref TState b, float t);
    }

Using this interface, I can declare a generic base class for network predicted objects:

    class PredictedEntity<TState>
        where TState : struct, IPredictedState<TState>
    {
        protected TState SimulationState;
        protected TState DisplayState;
        protected TState PreviousState;

        protected GameInput PredictionInput;

        float currentSmoothing;

The rest of PredictedEntity is similar to the original Tank implementation from the sample. The logic for updating locally controlled objects is simple:

        public void UpdateLocal(GameInput input, Level level)
        {
            this.PredictionInput = input;

            // Update the master simulation state.
            SimulationState.Update(input, level);

            // Locally controlled entities have no prediction or smoothing, so we
            // just copy the simulation state directly into the display state.
            DisplayState = SimulationState;
            PreviousState = SimulationState;

            currentSmoothing = 0;
        }

The update for remotely controlled objects, which use network prediction, is a little more involved:

        public void UpdateRemote(Level level)
        {
            // Update the smoothing amount, which interpolates from the previous
            // state toward the current simultation state. The speed of this decay
            // depends on the number of frames between packets: we want to finish
            // our smoothing interpolation at the same time the next packet is due.
            float smoothingDecay = 1.0f / GameplayScreen.FramesBetweenPackets;

            currentSmoothing -= smoothingDecay;

            if (currentSmoothing < 0)
                currentSmoothing = 0;

            // Predict how the remote entity will move by updating
            // our local copy of its simultation state.
            SimulationState.Update(PredictionInput, level);

            if (currentSmoothing > 0)
            {
                // If smoothing is active, also apply prediction to the previous state.
                PreviousState.Update(PredictionInput, level);

                // Interpolate the display state gradually from the
                // previous state to the current simultation state.
                DisplayState.Lerp(ref SimulationState, ref PreviousState, currentSmoothing);
            }
            else
            {
                // Copy the simulation state directly into the display state.
                DisplayState = SimulationState;
            }
        }

Sending network packets is trivial:

        public virtual void WriteNetworkPacket(PacketWriter packet)
        {
            SimulationState.WriteNetworkPacket(packet);
        }

But reading them is more complex:

        public virtual void ReadNetworkPacket(PacketReader packet, GameInput input, TimeSpan latency, Level level)
        {
            this.PredictionInput = input;

            // Start a new smoothing interpolation from our current
            // state toward this new state we just received.
            PreviousState = DisplayState;
            currentSmoothing = 1;

            // Read simulation state from the network packet.
            SimulationState.ReadNetworkPacket(packet);

            // Apply prediction to compensate for
            // how long it took this packet to reach us.
            TimeSpan oneFrame = TimeSpan.FromSeconds(1.0 / 60.0);

            while (latency >= oneFrame)
            {
                SimulationState.Update(input, level);
                latency -= oneFrame;
            }
        }

In the original sample, Tank.ReadNetworkPacket was responsible for reading the input state and packet send time. I moved this work out to the calling method, who reads those values from the start of the packet and computes the latency before calling PredictedEntity.ReadNetworkPacket. This makes things more flexible if I want to describe the state of more than one entity (for instance an avatar riding in a tank) in a single network packet, as it avoids having to send the input state and time twice.

Armed with a generic base class, I derived classes for each type of entity:

    class Ship : PredictedEntity<Ship.State>
    {
        public struct State : IPredictedState<State>
        {
            public Vector3 Position;
            public Vector3 Velocity;
            public Vector3 Front;
            public Vector3 Up;
            public float TurnVel;


            public void Update(GameInput input, Level level)
            {
                // Ship physics goes here.
            }


            public void WriteNetworkPacket(PacketWriter packet)
            {
                packet.Write(Position);
                packet.Write(Velocity);
                packet.Write(Front);
                packet.Write(Up);
                packet.Write(TurnVel);
            }


            public void ReadNetworkPacket(PacketReader packet)
            {
                Position = packet.ReadVector3();
                Velocity = packet.ReadVector3();
                Front = packet.ReadVector3();
                Up = packet.ReadVector3();
                TurnVel = packet.ReadSingle();
            }


            public void Lerp(ref State a, ref State b, float t)
            {
                Position = Vector3.Lerp(a.Position, b.Position, t);
                Velocity = Vector3.Lerp(a.Velocity, b.Velocity, t);
                Front = Vector3.Lerp(a.Front, b.Front, t);
                Up = Vector3.Lerp(a.Up, b.Up, t);
                TurnVel = MathHelper.Lerp(a.TurnVel, b.TurnVel, t);
            }
        }

Thanks to the IPredictedState interface, this is everything necessary for PredictedEntity to provide network prediction via its ReadNetworkPacket and UpdateRemote methods.

Ashu's new blog: sound effects and video encoding

My colleague Ashu has a blog where he talks about SoundEffect changes and how to encode videos for XNA Game Studio 3.1. He brews a mean IPA, too...

Game State Management and menu sounds

I wanted to add menu sounds to the Game State Management sample code I used in my AppWeek game, to make it play small clicks when changing the menu selection and bigger clicks when moving from one screen to another.

Menu selection sounds were easy: add SoundEffect.Play calls after the selectedEntry-- and selectedEntry++ lines in MenuScreen.HandleInput.

It wasn't so obvious how to add sounds for changing screen. I had maybe a dozen screens to deal with, but it felt like hundreds! If I tried to change each individual screen, I was worried I would miss some places and end up with bugs where one particular menu could be missing a sound if you cancelled it.

How about if I call SoundEffect.Play from the ScreenManager.AddScreen and GameScreen.ExitScreen methods? That catches everything with just two lines of code.

Trouble is, it caught a little too much:

  • I got two sounds when loading a game session, first when the LoadingScreen activated, then again when loading finished and the GameplayScreen took over
  • I got unwanted sounds when the NetworkBusyScreen went away after network operations such as joining a session
  • I got unwanted sounds when my title screen timed out and went to the main menu
  • I got unwanted sounds if network errors popped up a MessageBoxScreen

What is the underlying pattern here?

  • All these unwanted sounds occur when screens are activated or deactivated due to program controlled state changes
  • I only want sounds when screen transitions occur as a direct result of the user pressing a button

Hmm...

I changed the InputState helper to remember whether MenuSelect, MenuCancel, or PauseGame had been pressed within the last frame, and made ScreenManager.AddScreen and GameScreen.ExitScreen only trigger sounds if one of these inputs was detected. I can't make up my mind whether this was a valid solution or an ugly hack (perhaps a little of both?), but it was certainly a tiny amount of code to get the result I wanted!

Game State Management and glitch free loading

I think the core architecture of the Game State Management sample is really solid, but the specific screen implementations included with it are a little more placeholderey. We figured most people would reskin or replace the individual screens, so we concentrated on the design of the core system (GameScreen, ScreenManager, etc) and didn't spend so much time polishing things like MessageBoxScreen and NetworkBusyScreen.

What goes around comes around, right? I didn't have time to do much reskinning while making my AppWeek game. Dang it! Hoist by my own petard...

I found myself irritated by the way many of these screens lazily load small assets in their LoadContent methods. For instance, MessageBoxScreen loads a gradient texture, and LobbyScreen loads icons for displaying the player status. They use a shared ContentManager, which they look up from ScreenManager.Game.Content, and rely on ContentManager to cache the data for them. This means the file will only actually be pulled in from disk the first time someone asks for it, rather than every time a MessageBoxScreen is displayed, but the lazy loading also means there will be a small glitch the first time a MessageBoxScreen comes up.

Even a small loading glitch is enough to make the transition animation look jerky and unprofessional. Creating a game that feels slick and polished is kind of like being an Olympic gymnast, as it's not enough just to do something cool: for that elusive 10 score, you have to make it look effortless, too. Dropping frames while you load content doesn't exactly make the right impression!

To fix this, I declared a list of the offending assets:

    static string[] preloadAssets =
    {
        "UI/gradient",
        "UI/cat",
        "UI/chat_ready",
        "UI/chat_able",
        "UI/chat_talking",
        "UI/chat_mute",
    };

I added this code to the LoadContent method of my main Game class:

    foreach (string asset in preloadAssets)
    {
        Content.Load<object>(asset);
    }

This discards the return value from the Content.Load call, because it is only loading the assets in order to prime the shared ContentManager. When a MessageBoxScreen later asks for that same gradient texture, the ContentManager will already have it loaded, so will not need to hit the disk. No more glitches, and no need to bother changing the individual screen implementations.

Note: screens which use the shared ScreenManager.Game.Content are different to how screens with larger pieces of content (notably BackgroundScreen and GameplayScreen) work. Those have their own local ContentManager, which they initialize and unload in their LoadContent and UnloadContent methods, so their content does not stay loaded after the screen goes away. Although they take a while to load, these screens do not cause ugly glitches because the delay is hidden behind a LoadingScreen overlay, which is used whenever such a 'big' screen is activated.

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.

Content Pipeline types (updated)

My colleague Ito pointed out that my diagram showing the flow of types through the Content Pipeline was a little out of date, and was kind enough to update it to include the more recent audio and video types:

Untitled

AppWeek: level editing

I created the levels for my AppWeek game using Paint.NET and a variant of the TerrainProcessor from the Heightmap Collision with Normals sample.

I wanted hills and valleys in specific places, so I drew a crude approximation in Paint.NET. I started with a circular gradient fill to make my landscape higher in the middle and lower toward the edges. I drew a couple of white peaks using the pencil tool with a giant brush, and some valleys in black. After a Gaussian blur to smooth this out:

image

This is roughly the contours I wanted, but it's terribly boring!

I made a second layer, and pasted this noise pattern (copied from a sample) into it:

image

I can't remember how this noise was originally created. We might have used some kind of fractal or plasma cloud filter, or perhaps a program like Terragen.

I combined my two images by setting the second layer to 'screen' blend mode with 50% opacity, producing this final result:

image

Notice the colored dots which I added on a third layer? These mark locations that are important to gameplay:

  • Red = red team base
  • Blue = blue team base
  • Yellow = hovership spawn point
  • Green = tank spawn point

My enhanced version of the TerrainProcessor scans for these specially marked locations, stores their positions, then replaces the marker pixel with an average of its four neighbors to avoid messing up the regular terrain generation.

I also added new processor parameters, for specifying a normalmap texture in addition to the base terrain texture, controlling how much the normalmap is tiled, specifying the sky texture, rotating the sky so I could make its brightest part match the light direction used by the game, and specifying what background music goes with each level:

image

My TerrainProcessor automatically chains to other custom processors, using the SkyProcessor from the Generated Geometry sample to build the sky texture into a 3D model, and the NormalMapProcessor from the Sprite Effects sample to convert the normalmap texture from grayscale displacement format.

When its work is complete, TerrainProcessor spits out this strongly typed object:

    [ContentSerializerRuntimeType("SampleSmashup.Level, SampleSmashup")]
    class LevelContent
    {
        public ModelContent Terrain;
        public ExternalReference<SkyContent> Sky;
        public HeightMapInfoContent HeightMap;
        public Vector3[] BasePositions;
        public List<Vector3> TankSpawnPoints;
        public List<Vector3> ShipSpawnPoints;
        public string SongName;
    }

Thanks to the magic of automatic XNB serialization, my game can load the resulting .xnb file directly as a Level object:

    class Level
    {
        public Model Terrain;
        public Sky Sky;
        public HeightMapInfo HeightMap;
        public Vector3[] BasePositions;
        public List<Vector3> TankSpawnPoints;
        public List<Vector3> ShipSpawnPoints;
        public string SongName;
    }

This is how the first level looked in game, with gently rolling hills thanks to my aggressive use of Gaussian blur:

level1

Once this custom processor was in place, it was trivial to add a second level. I started with the same noise pattern as before:

image

I drew this on a second layer, again making heavy use of Gaussian blur:

image

This time I set the layer blend mode to 'glow', with 50% opacity. There is no particular reason for choosing that blend mode: I just tried a bunch of different ones until I got a result I liked. Here is the combined heightmap texture:

image

And the second level in game:

level2

Summary:

  • Paint.NET is awesome
  • Combining simple layers can produce interesting results
  • Don't be afraid to experiment with different filters and layer blend modes
  • Gaussian blur is my friend
  • Custom content processor + processor parameters + automatic XNB serialization = good stuff

More AppWeek

My colleague Brandon writes about his Avatar Boxing AppWeek game, which was oh so much more funnier than mine...

AppWeek

Toward the end of every Game Studio development cycle, we schedule some time to down tools, install the latest daily build, and use it to make a game. This serves as a sanity check (does our product actually work?) and also a learning exercise (what things suck the most? What should we try to improve in the next version?)

That's the theory, anyway. And in theory, there is no difference between theory and practice  :-)

We did an AppWeek shortly before shipping Game Studio 1.0, which produced minigames by Dean and Minjie, plus a bunch of stuff that was too unfinished or too much of an IP violation to release. Every subsequent release had an AppWeek in the original schedule, but when push came to shove things just took too long and the bug pile was too big, so AppWeek ended up on the cutting room floor.

Until now. I am happy to report that AppWeek 3.1 took place as originally scheduled. Here's the title screen from the game I made:

title

This is an online multiplayer game, inspired by Capture the Flag but with cats instead. Players are divided into red and blue teams. Each team has a base which is marked by a plume of colored smoke and occupied by a cat. The objective is to infiltrate the other team's base, steal their cat, and take it back to your own base without being killed.

I borrowed from many samples to stitch this together, making it more of a Frankenstein remix than an original piece of programming. One of my goals was to investigate how well our sample code holds up if you use multiple samples in a single game (verdict: this worked well for me).

I started with the Network State Management sample for the main program structure and menus, and added code from the Network Prediction sample to synchronize object positions during gameplay. I used the Heightmap Collision with Normals sample for terrain rendering and collision, and the Generated Geometry sample to create the sky from a photo I took on Orcas Island. I tarted up the terrain rendering by adding a normalmap (using the NormalMapProcessor from the Sprite Effects sample to convert it from a grayscale displacement map), and threw in the Bloom and Lens Flare samples for graphical bling. My player character used an avatar animation system that Dean wrote for a GDC demo (which is not yet a sample, but will hopefully someday become one), plus the Skinned Model sample to provide fallback character rendering on PC and for players who have no avatar. Add a camera (taken of course from the Chase Camera sample) and we have the beginnings of a game:

avatar

I needed weapons to liven things up, and figured vehicles wouldn't hurt either. I used the tank model and turret rotation code from the Simple Animation sample, and added the ability for players to get in the tank and drive it around. For smoke plumes, weapon trails, and explosions, I used the Particle 3D sample, plus the Authoring Particle Systems using XML tutorial so I could easily tweak the particle settings:

tank

Not satisfied with just one type of vehicle, I also included hoverships, using the Custom Model Effect sample to render them with an environment map:

ship

I borrowed more code from the Picking sample (for drawing gamertags above players), the 3D Audio sample (for drawing billboarded cat sprites) and the Safe Area sample (the SafeAreaOverlay component).

It was interesting how my development resembled a scaled down version of working on a commercial game. I spent the first couple of days writing nice code, refactoring as I went, and creating a robust Internet-ready network protocol. Then on Friday I switched into "gotta be done by 2:45 PM" mode, and started hacking as fast as possible. Forget design principles: I made things public any time I needed to access them. Forget network efficiency: I took the easy way out and made the host responsible for all empty vehicles, cat capture status, and scoring.

It worked, at least with two players. In theory it should support up to sixteen, but I didn't have time to test that many. I suspect it uses too much bandwidth for many players to work over the Internet without more optimization, and I have no idea what the rendering perf would be with more going on. The weapon damage amounts and collision radius are less carefully tweaked than I would have liked, and I didn't have time to implement shadows, gameplay sounds, vehicle respawning, or a radar/map.

But hey. Not bad for 4 days work. I continue to be impressed by how productive programming in C# can be!

SoundEffect changes in XNA Game Studio 3.1

One of the few breaking API changes between Game Studio 3.0 and 3.1 concerns the SoundEffect.Play method.

There are two different scenarios for playing sound effects:

  • Fire & forget is when you want to trigger a sound, then not have to worry about it any more, and have the system automatically clean up after it finishes playing.

  • Create & configure is when you want to get back some sort of object that lets you alter the sound (changing volume or pitch, applying 3D settings, pausing or resuming) while it plays. This is obviously more powerful, but also more work because you have to keep track of which sounds are playing and clean up the relevant objects when you are done with them.

We think it is important for the SoundEffect API to support both scenarios, and in Game Studio 3.0, we tried to make the SoundEffect.Play method handle them both. It returned a SoundEffectInstance object, which you could use for create & configure scenarios, but you could also just ignore this return value if you were doing fire & forget, in which case we did some magic to make sure the garbage collector would not try to reclaim the SoundEffectInstance while the sound was still playing.

Turns out, this didn't work as well as we expected. There were two problems:

  • Because the Play method did not know whether you were going to store the return value, it had to allocate a new SoundEffectInstance each time, so fire & forget sounds created garbage.

  • The only way we could tell the difference between fire & forget vs. create & configure usage was by noticing if the garbage collector tried to reclaim a SoundEffectInstance that was still playing. But .NET garbage collection is not deterministic! This worked ok as long as GC occurred regularly, but the better optimized your game was, the less frequently GC would run, in which case fire & forget sounds might not be reclaimed often enough, so the system would run out of voices.

In Game Studio 3.1, we changed the API to make these two usage scenarios explicit:

  • SoundEffect.Play is now only for fire & forget, and it no longer returns a SoundEffectInstance. Instead, you get back a bool indicating whether the call succeeded. This no longer generates any garbage, and no longer supports looping (because if you started a looping sound this way, it would be impossible to ever stop it, which doesn't seem particularly useful :-)

  • For create & configure scenarios, we added a new SoundEffect.CreateInstance method. This returns a paused SoundEffectInstance, so you can set properties such as Volume, Pitch, and Pan, then call SoundEffectInstance.Play when you are ready to trigger it.

We removed the SoundEffect.Play3D method. To play a 3D sound, call SoundEffect.CreateInstance, SoundEffectInstance.Apply3D, and then SoundEffectInstance.Play. You cannot use fire & forget with 3D sounds.

We also changed what happens if you try to play too many sounds at the same time. In GS 3.0, the Play call would throw InstancePlayLimitException. In 3.1, CreateInstance still throws this exception if it runs out of voices, but Play just returns false. This is convenient for the common case where you are playing fire & forget sounds and want to silently ignore voice overflows, as you can simply ignore the Play return value and no longer have to write special error handling code.

Pumping the Guide

I've never been happy with the design of the XNA Framework Guide methods.

I want to write code like this:

    int? button = Guide.ShowMessageBox("Save Game",
                                       "Do you want to save your progress?",
                                       new string[] { "OK", "Cancel" },
                                       0, MessageBoxIcon.None);

    if (button == 0)
    {
        StorageDevice storageDevice = Guide.ShowStorageDeviceSelector();

        if (storageDevice != null)
        {
            using (StorageContainer storageContainer = storageDevice.OpenContainer("foo"))
            {
                ...
            }
        }
    }

But there are no such simple ShowMessageBox or ShowStorageDeviceSelector methods. Even if these methods did exist, the above code would not work. Instead, I have to deal with a tangled mess of Guide.BeginShow*, IAsyncResult, and make a state machine to track when I should call Guide.EndShow*.

Why so complicated?

Back in Game Studio 1.0, we did have just such a simple storage device selector API. It worked fine as long as you called it from a background thread, but if anyone was so foolish as to call it from their main game thread, boom! The Xbox hangs.

People were justifiably surprised by this behavior. The problem is that the Guide UI is displayed over the top of the game, and relies on the game loop calling Present at regular intervals. When the game is blocked inside a ShowStorageDeviceSelector call, it is no longer cycling through the game loop, thus not calling Present, thus the Guide never gets a chance to render itself, thus the user has no way to interact with it, so the Guide call never completes.

It struck us as a bad idea for such a seemingly simple API to behave so rudely, so we spent some time trying to improve it for Game Studio 2.0. The main idea we considered was to make these blocking methods automatically call Present while the Guide was visible. Unfortunately, there are many problems with such a design:

  • Xbox Games are still visible underneath the Guide, partially faded out. If we called Present directly, outside of the normal game loop, this would not be possible, so XNA Framework games would just show black behind the Guide. Ugly.

  • What if we called Game.Draw before each Present, rather than just clearing to black?
    • This only works if Draw is truly independent of Update, and safe to call even while Update is suspended. Sure, a well written game ought to work this way, but can we really assume all games are robust enough?
    • What if the game calls ShowStorageDeviceSelector from inside their Draw method, so we are now calling Draw recursively? No sensible game would do this, but we have to worry about the not-so-sensible ones too :-)
    • To call Draw, the Guide APIs would need access to the Game instance. But Game is defined in the Microsoft.Xna.Framework.Game assembly, which is intended to be optional. We don't want to force anyone to use Game if they prefer to host the framework some other way.

  • To keep the game visible without calling Draw, what if we took a screen grab before activating the Guide, and used that as the background? 
    • The game would no longer animate behind the Guide, but who cares.
    • We'd need extra memory to store the image. Do we really want to take that space away from every game? We can't just allocate it on demand, because we mustn't get in a situation where the Guide can't come up because the game is using too much memory.
    • What if someone calls ShowStorageDeviceSelector from a background thread, in which case the game loop is still running in parallel? Extreme badness would ensure if we tried to call Present at the same time the main thread was drawing something.

  • Ok, so automatically pumping Present is a can of worms. How about if we made this explicit, and had the game pass a delegate into the blocking Guide.Show APIs? We would call this at periodic intervals, so it could do whatever drawing was appropriate.
    • Dang, there goes our nice simple API...
    • This is error prone, and only works if the developer understands enough to specify exactly the right delegate.
    • In fact, what should they specify here? They can't just use Game.Draw, as that would skip the preamble and postamble code which handles lost devices and does the final Present.
    • Oh yeah, lost devices. What should happen if the user locks the desktop while the Guide is up? What if they resize the game window, or drag it to a second monitor, or close it? These actions trigger many crazy events, and there are many things that could go wrong if the game is not expecting them.

When in doubt, play it safe and at least try to do no harm.

Game Studio 2.0 only provides async Begin/End versions of the message box, keyboard input, and storage device Guide calls. These are a pain to use, but at least they explicitly force developers to deal with the resulting state machine, rather than being surprised when crazy stuff happens and unexpected events are raised in the middle of a Guide call. No magic is better than confusing magic that only works half the time, right?

I'm still not happy about this. I keep revisiting it, looking at it from different angles, and concluding that it's still a mess. I don't like what we have now, but I also don't like any of the alternatives!

MotoGP: LOD selection

So there we were with our bikes and track sectors tessellated at three levels of detail (LOD). How do we choose which LOD to use for each model?

  • Naive version: measure distance from the camera. This works fine if your field of view is constant, but our cameras were all over the place. In particular the TV replay cameras used a very narrow field of view (which is the same thing as a telephoto lens) so distant bikes would appear large on the screen.

  • Better version: project the extents of each object into screen pixels. Anything higher than 80 pixels might get high detail, while between 20 and 80 pixels is medium, and we use the low detail model for objects smaller than 20 pixels.

    Math note: the screen size of a 3D object is proportional to (1 / DistanceFromCamera / tan(FieldOfView / 2)).

    This provides consistent visual quality (you never see a low detail model drawn large enough to look ugly), but not consistent performance. If a TV camera with a telephoto lens looks down a straight section of track, all 20 bikes could end up a similar size on screen, but the framerate would plummet if we drew them all at high detail!

  • MotoGP version: sort objects by distance from camera, then allocate LOD on a first come, first served basis. The closest 4 track sectors get high detail, the next 6 get medium, and the remainder get low detail.

    This provides consistent performance (we never draw more high detail models than we can afford) but not consistent visual quality. When the TV camera sees all the bikes coming down that straight, the ones at the back end up with low detail models drawn so big that you can see all the flaws. But hey. Framerate was higher priority, so this was a good tradeoff for us.

    Note that when all the bikes are far away, this algorithm chooses high detail for some even though medium could suffice. We didn't care about that, because the goal was to improve worst case performance in order to maintain a steady 60 fps. There are no prizes for going faster than 60, so no point optimizing scenarios that are already the best case.

LOD selection is a classic case where hysteresis is useful to avoid popping, but I can't remember whether we implemented that in MotoGP.

MotoGP: mind the gap

I keep thinking I'm done writing about MotoGP, but then more topics occur to me.

And then I think, is this just self indulgent rambling through the meadows of nostalgia? "Here's a tall tale from back when I was a lad, when computers were still programmed in binary and Tony Blair was popular..." But people seem to be enjoying this series, so I guess you'll have to bear with me a while longer.

Now where was I?

MotoGP tracks were split into sectors using a kd-tree (similar to an octree). Each sector was tessellated at three levels of detail, so we could draw the nearby environment with nice smooth curves but use fewer triangles for more distant pieces.

When you join pieces of environment that are tessellated differently, there will be cracks where they do not exactly meet up. This is a common problem in terrain rendering, often solved by adding fringes or stitching in extra triangles to fill the holes.

We chose an easier solution. Our content build process simply detected which edges were shared by patches from more than one sector, and always tessellated those edges at maximum detail, even when creating the medium or low detail geometry. This freed the game code to draw each sector at whatever detail level it liked, and everything was guaranteed to join up without requiring special runtime logic. Sure, this added a few extra triangles to our low detail geometry, but the vast majority of patch edges did not lie on sector boundaries, so the cost was insignificant.

Takeaway: moving work from game engine to content build process = good stuff.

MotoGP: curved surfaces

The bikes and tracks in MotoGP were built using a type of curved surface called a Bezier patch.

We originally developed this technology for Playstation 2, and although I never shipped any games using it on that hardware, I still have fond (?) memories of pulling an all-nighter implementing realtime patch tessellation in vector unit assembly language!

On Xbox, we decided it would be more efficient to render our geometry as regular indexed triangle lists, so our game code didn't know anything about Bezier patches. Curved surfaces were tessellated into triangles at build time, using the MotoGP equivalent of a custom content processor.

So why bother with curved surfaces at all, if we're just going to preconvert them into triangles? (this wasn't a cheap technology to develop: neither Max or Maya had good enough support for Bezier artwork, so we ended up writing our own content creation tools).

Three reasons:

  • Working with a higher level surface representation made our artists more efficient
  • Much flexibility to optimize their work into an efficient game format
  • Provided a late-binding knob for adjusting our triangle count

The great thing about curved surfaces is that artists can specify idealized shapes without tying themselves to any specific number of triangles. A simple tessellator might convert each patch to a fixed grid (say 8x8), but since our tessellator was running at build time, it didn't have to be especially fast, and therefore had room to be smarter.

For starters, let's look at how curvy each edge of the patch is. No point adding lots of triangles along an edge that is basically straight, right?

Also, let's consider how big each patch is. Makes sense that large things should have more detail than small ones.

What about applying game-specific knowledge such as the collision attributes for each patch, and the position of the racing line? The road surface obviously needs to be accurate, as this is where the gameplay happens. Surrounding grass areas, not so much. Distant fences, who cares if they're a little jaggy in places? But the boundary between track and grass, now that needs to be super smooth to look good (even more so than the interior of the road), especially where there are rumble strips.

Check out these screenshots, which I created by using telnet tweakables to move the chase camera way above the target bike, setting run/go=false when I reached an interesting place, then switching to wireframe mode via another tweakable:

image

image 

See how the tessellation varies depending on surface type and curvature? That was entirely automatic. Sure, given enough time it would be possible to build something like this by hand, but it would be a serious PITA, and a nightmare to texture compared to the handful of evenly spaced patches used by our artists.

Once you have automatic tessellation, multiple levels of detail come pretty much for free. We tessellated each bike and track segment three times, creating low, medium, and high detail versions of the geometry for different view distances. This hardly took any extra memory, because we made sure the lower detail tessellations would reuse a subset of the vertices from the high detail version, so all three could share a single vertex buffer with only the index values changing.

Best of of all, this system was literally covered in tweakable knobs. If we needed to reduce the triangle count to save memory or speed up rendering, we could simply adjust the tessellation settings (even just for one specific track or bike) with no source code or artwork changes required.

More Posts Next page »
 
Page view tracker