Tuesday, May 15, 2007

Quaternion Camera in XNA

XNA unfortunately does not provide a QuaternionCamera class in it's Framework. Nowhere on the Internet could I find a camera class that provides six degrees of freedom without requiring additional coding from the reader. (It may be out there somewhere and my google-fu was weak). I even have a couple books that outline quaternion cameras but stop short of implementing them fully. In the process of attacking this problem in my book, I've made a few camera classes. I'm posting the simplest class here - good for beginners. The final camera class in the book will be more complicated. This class should provide a good jumping-off point for anybody interested, or a simple helper class for those who just want a camera without hassles. In the camera, GetViewMatrix and CreateYawPitchRoll are where the money is. One reason they are statics is to make it easy to test these functions - they don't have to be. Another reason is that I hope it makes them stand out to people familiarizing themselves with the class.

        public static Matrix GetViewMatrix(ref Vector3 position, ref Vector3 target,
            ref Vector3 up, float yaw, float pitch, float roll)
        {
            // The right vector can be inferred
            Vector3 forward = target - position;
            Vector3 right = Vector3.Cross(forward, up);

            // This quaternion is the total of all the
            // specified rotations
            Quaternion yawpitch = CreateFromYawPitchRoll(up, yaw,
                right, pitch, forward, roll);

            // Calculate the new target position, and the
            // new up vector by transforming the quaternion
            target = position + Vector3.Transform(forward, yawpitch);
            up = Vector3.Transform(up, yawpitch);

            return Matrix.CreateLookAt(position, target, up);
        }
        public static Quaternion CreateFromYawPitchRoll(Vector3 up, float yaw, Vector3 right,
float pitch, Vector3 forward, float roll)
        {
            // Create a quaternion for each rotation, and multiply them
            // together.  We normalize them to avoid using the conjugate
            Quaternion qyaw = Quaternion.CreateFromAxisAngle(up, (float)yaw);
            qyaw.Normalize();
            Quaternion qtilt = Quaternion.CreateFromAxisAngle(right, (float)pitch);
            qtilt.Normalize();
            Quaternion qroll = Quaternion.CreateFromAxisAngle(forward, (float)roll);
            qroll.Normalize();
            Quaternion yawpitch = qyaw * qtilt * qroll;
            yawpitch.Normalize();

            return yawpitch;
        }

GetViewMatrix shows you how to transform the target and up vectors using a quaternion. CreateFromYawPitchRoll combines three quaternions into a single quaternion defining all the camera rotations. (NOTE: The XNA version of this function uses the world axes as the axes of rotation. This version uses the camera axes supplied by the caller).

Enjoy! Update: Apparently blogger or my web server doesn't like .cs files. I've changed the link to a zip file that appears to work. SimpleQuaternionCamera.zip

Wednesday, March 28, 2007

A general GameScreen implementation, Pt 2

Previously, I posted an implementation of Eli's GameScreen design that I'm using for the games in my book. I've just added two new virtual functions to the GameScreen class - BeginPause and EndPause. I did this to make dealing with audio easier - when a screen is about to lose updates, it should mute it's sounds, and then it can unmute them when updates resume. The change isn't terribly complicated, and it passes the smoke test, so I thought I'd post it for folks to use. GameScreen1.1.zip P.S. Last night, someone asked me if I was planning to change this class, and I swore I wasn't. I forgot about this feature. Sorry.

Thursday, March 15, 2007

My Rocinante Sails at Night on Her Final Flight

Today was the last day of my contract with the XNA team (non-employees can only work for one year). I had a blast working with them and hopefully I will do so again. Leaving is pretty bittersweet - I want to keep working there, but now I also get to work on the XNA book full-time and get that out the door. My current plan is to spend a couple months doing that and maybe spend some time on game development once I put the book to bed. (regarding the title of this post: I've been listening to a lot of Rush lately in anticipation of their next album.) In other news, there was an XNA user group for Microsoft employees this week. It was a lot of fun meeting folks developing XNA games and there are a couple things I want to share. Respect Da Normals One of the guys had a problem - he had game terrain made up of tiles using 3D objects (as opposed to quads) and each row of tiles was unevenly colored. One side of each tile was completely black, making the whole terrain look like a farm with furrowed rows. Clearly a lighting problem. We suspected that the vertex normals weren't right (he was using vertex lighting) but they were fine in 3DS Max. In 3DS Max, each vertex had 3 normals pointing in the proper directions. But then we re-imported the FBX file, and BAM - the vertex normal was the average of the three distinct normals we had been seeing in 3DS Max. This meant that the vertex normals were pointing away from the light on the far side, so the light color was black, and getting averaged with the vertices on the other side, which were lit normally. We started playing around with 3DS Max, and sure enough on the Export dialog there was a checkbox that said something that can roughly be translated as "Respect Da Normals". Of course it's unchecked by default. But paying attention to your normals is good advice for any time. Avoid Solutions that are Too General We were discussing how you might build a texture manager to load textures on demand and unload them when they're not being used. I suggested a simple COM-like approach - refcount the texture on Load(), and expect a Release() call for the texture to decrement the refcount. "What happens if they don't call Release?" "@#$% 'em", I said. "@#$% 'em?" "@#$% 'em," I repeated. My reasoning is that, especially in game programming, there is such a thing as building a too-general solution. If a programmer uses one of your objects for a game and they don't follow the contract you specify, let 'em suffer. They'll pay the price by keeping the texture in memory if they fail to release it. Like the rule against premature optimization ("don't do it"), you should consider a similar rule against building solutions to problems that most game designers should know better than to cause.

Tuesday, March 13, 2007

XNA Creator's Club has a new website

Most of the XNA community is probably aware that the MSDN forums have moved to the new XNA Creator's Club website, http://creators.xna.com/ . But I want to point out that there are also new samples there, especially the Font Sample, which is VERY useful to those of us who sorely missed fonts in 1.0. Sadly, my contract with the XNA team ends this week, but happily that will give me more time to devote to finishing my book. I can't promise to blog more often, but I can promise to think about blogging more often, and to feel guilty if I don't.

Thursday, February 08, 2007

A general GameScreen implementation

A few weeks ago, Eli posted a design for a GameScreen and a GameScreenManager to use in XNA. The GameScreen is a base class, like Game, that implements one layer of a game (e.g. the game itself, or a menu, or a title screen). The GameScreenManager is used by the Game class to switch between these screens using a stack-based design. I ended up implementing this design for my book, and I've been using a few weeks with no problems so I thought I'd post it here for the XNA community to use. To use the GameScreenManager, declare a public instance of it on your Game object, along with any screens that are spawned by Game and not by other screens. public GameScreenManager screenManager; // Screens StartMenuScreen menuscreen; PlayScreen play; public Game1() {

... screenManager = new GameScreenManager(2); ...
} Add the first screen during Game.Initialize() by creating the screen, initializing it, and calling GameScreenManager.Push. I also create the other screen at this point, but I don't initialize it until it's invoked. protected override void Initialize() {
// TODO: Add your initialization logic here play = new PlayScreen(this); play.Initialize(); screenManager.Push(play); menuscreen = new StartMenuScreen(this); ...
} At some point later, if the menu is invoked, I initialize it and push it over top of the play screen: // if the menu isn't already up if (screenManager.Peek() != menuscreen) { GamePad.SetVibration(PlayerIndex.One, 0, 0); // install menu screen menuscreen.Initialize(); screenManager.Push(menuscreen); Lastly, when the menu is finished, I fire an event back Game, which removes it. public void MenuScreenFinished(int Selection) {
screenManager.Pop(); if (Selection == 0) return; if (Selection == 1) { play.ResetGame(); } if (Selection == 2) { this.Exit(); }
}
You can download the GameScreen.cs and a sample screen here. Also, I should probably hawk the fact that my book is available for pre-order.

Labels: ,

Thursday, January 11, 2007

XNA's LoadGraphicsContent API, Part 2

So, after learning about XNA's LoadGraphicsContent API, an interesting question pops up as soon as you go to implement your own DrawableComponent - why is LoadGraphicsContent a protected method on components? How do I make sure it loads and unloads it's graphic objects properly? The answer is "You don't have to". DrawableComponent is a sneaky and underhanded beast. It stealthily calls it's own LoadGraphicsContent method the first time during Initialize(), and then subscribes to the device reset events just like your Game object does. It calls LoadGraphicsContent or UnloadGraphicsContent in response to these events. This knowledge comes in handy if you try to contain components outside the Game object.

Friday, January 05, 2007

XNA's LoadGraphicsContent API

A friend of mine was a little confused about the best way to use XNA's LoadGraphicsContent API, so I thought I'd try to clarify it a bit here. In general, your XNA Game object (or DrawableGameComponent object) will experience the following calls:

  1. Constructor
  2. Initialize()
  3. LoadGraphicsContent()
  4. Update / Draw repeatedly
  5. UnloadGraphicsContent()
  6. (maybe) goto 3
  7. Dispose()

In your constructor, you don't want to do much of anything. Assign values to things that aren't allowed to be null later, and maybe cache some constructor parameters into private fields of your object. Constructors in C# as a rule should avoid throwing exceptions. Try not to call any system APIs or anything that uses a resource, since that's a good way to encounter an exception. You can and should set your graphics preferences on the graphics object here.

In Initialize you have a lot more freedom. Your graphics object has been initialized and you should have a valid device. This is a good place for all the object creation and system calls that you wanted to make in your constructor. Don't load any Textures or Models yet though.

Load your Textures and Models in LoadGraphicsContent instead. This will get called every time the graphics card is reset (e.g. if the application moves from one monitor to another) and if you load a Texture in Initialize you'll be in for a nasty surprise after the device resets and you didn't reload it in LoadGraphicsContent. This is also where you want to create new RenderTargets.

UnloadGraphicsContent gets called when the device is being reset, or your application is ending. If you've loaded anything that needs explicit disposal, do that here. You'll notice the boilerplate telling the Content Manager to dump what it's loaded. If the device is being reset this call should be followed by a LoadGraphicsContent with the new graphics device settings.

You will rarely need to override Dispose. If you load something in Initialize that requires disposal you should do it here.

Update: One thing I forgot to mention about Initalize - if you are making a GameComponent, you don't want to make any assumptions about whether you are going to be drawn or receive updates soon. It could be a while between the time an application initializes a component and when it actually gets to update or draw. Some of the XNA documention samples do things in Initialize they probably shouldn't - for example, one of the audio samples plays a sound in Initialize (that gaffe in the docs is my fault, actually). While you can get away with that in a Game object it's a bad habit in general.

Update2: If you want to control when LoadGraphicsContent is called, call Initialize on your Game's base class.

Labels: