| Stephen's profileBadCorporateLogoPhotosBlogLists | Help |
|
April 16 Setting the Default Editor for Content FilesThis week at work, I spent a bit of time playing around with Game Studio, reaffirming once again that I am not a game programmer. For all the years I’ve worked on making Game Studio, I have yet to build an actual game with it. Despite my inability to build anything fun, there’s still a chance I can share something useful from my experience. This week, I found myself switching frequently between editing code to editing textures. The textures were files in my project, because they were being built by the Content Pipeline. So naturally whenever I wanted to view or edit them, I intuitively double-clicked on the PNG files in Solution Explorer. Unfortunately, this opened them up in Visual Studio’s awful, built-in resource editor. Bleah! What I really wanted was to double-click the texture files and open them up in Paint.NET… So I changed the default editor for PNG files, and now that’s what happens! For a lot of people reading, this might be old hat. This feature has been in VS since the dawn of time… But for others, this could be new and exciting! Adding a New Default Editor
What you should see is something like this… …Followed by this… And from now on, double-clicking any file with a PNG extension in the Solution Explorer will open it up in Paint.NET. Woot! April 12 Side-by-Side Versioning and XNA Game Studio 3.1Obligatory AnalogyOnce, I went to rent videos with my friends, Stephen and Shaun. At the video store, we ran into Stephen’s brother, Sean, and Sean’s friend, Steven. We hadn’t all seen each other in about a year, so we started chatting to catch up. A few minutes later, Sean’s new girlfriend arrived, and he introduced us all to her. She was fairly sociable, and tried to get to know us by engaging us in conversation. It didn’t take long before she started to look very confused. For the most part, we knew which of the three Stephens or two Shauns was being discussed based on who was talking, who he was talking to, the context of the story, or which way he nodded his head when he said a name. But the poor girl who’d just been introduced to us had no idea who anyone was talking about, and half the things we said didn’t seem to make any sense! That’s kind of like what happens in a project where you try to mix assemblies from different versions of the XNA Framework. The project knows everybody, and when one assembly refers to a type from another assembly, the project knows exactly what it means. When something goes wrong, the project tries to tell you with a warning or error, but you’re like the new girlfriend – it doesn’t make any sense to hear, “No, I meant Stephen, not Steven.” If you’ve been programming with the XNA Framework for more than a few months, you’re probably already aware that the more versions there are of the XNA Framework, the more complicated things can get. This is because XNA Framework versions are installed side-by-side, which is a fancy term meaning, “completely independent of each other, but with the same name.” When XNA Game Studio 3.1 is released later this year, it will introduce yet another side-by-side version of the XNA Framework. To your existing XNA Framework games and projects, installing a new XNA Framework on your machine will have about the same effect as installing Adobe Reader. The existing games and projects will be completely unaware of the new framework, and its presence will have no impact at all. That is, unless you mix it up with another version. Versioning Then and NowTo date, there have been four official releases of XNA Game Studio. The first one was called XNA Game Studio Express 1.0, the second was XNA Game Studio Express 1.0 Refresh, the third was called XNA Game Studio 2.0, and the fourth was called XNA Game Studio 3.0. Note that we dropped the “Express” part of the name in version 2.0. There were also a few beta and community technology preview (CTP) releases, but I won’t get into those. So far, each version of Game Studio has installed side-by-side with other versions, except for XGSE 1.0 Refresh. Installing this version replaced the old version entirely. A more suitable name for it might have been XGSE 1.0 Do Over. However, with the exception of the 1.0 Refresh replacing the 1.0 version, each subsequent version can be installed side-by-side on the same machine. The table below lists the versions of XNA Game Studio and their installation strategy. Table 1. Game Studio Versioning
As you can see, it’s possible to have Game Studio 1.0, 2.0, and 3.0 all installed on the same machine. We felt that was an important feature, because it allows people to try out the new version without committing to it. If you have a class project that requires using Game Studio 2.0, but you are also working on a personal project that requires using Game Studio 3.0, you can work on both on the same machine! XNA Game Studio 3.1 will be slightly different. :-) XNA Game Studio 3.1 will install side-by-side with prior versions of Game Studio except version 3.0. Whaaaat?! Yes, but don’t worry, Game Studio 3.1 supports projects targeting XNA Framework 3.1 and XNA Framework 3.0. Installing XNA Game Studio 3.1 will require you to uninstall XNA Game Studio 3.0. However, all your 3.0 projects will continue to work in Game Studio 3.1 without upgrading. In addition, you’ll be able to create new projects for XNA Framework 3.1, and you’ll be able to upgrade 3.0 projects to 3.1. In fact, it’s worth noting that projects targeting XNA Framework 3.0 will not be automatically upgraded in Game Studio 3.1. If you want to upgrade an existing project, you’ll need to use the new Upgrade Solution command found on the Project menu, or on the Project and Solution context menus. This command will upgrade all projects in the solution to XNA Framework 3.1. For your own sanity, you should only target one version of the XNA Framework per solution. If you mix projects from different versions, you’re likely headed for trouble. SummaryIn XNA Game Studio 3.1, you can create and use projects targeting XNA Framework 3.1 in addition to XNA Framework 3.0. You need to uninstall XNA Game Studio 3.0 before installing XNA Game Studio 3.1, but you don’t have to give up working on projects targeting 3.0. Projects targeting XNA Framework 3.0 will not be automatically upgraded to target 3.1. This lets you continue working with them as you did before. If you load a 3.0 project and you want to upgrade it to 3.1, the Upgrade Solution command will complete the operation in two or three mouse clicks. I’ll write more about the upgrade process later, and maybe a bit about compatibility. March 26 SpriteBatch.DrawInt32: Eliminate the Garbage from your High ScoresA couple months ago, my wife and I started going to the gym each morning before work. We exercise together, and then we hit the locker room at the same time each day. The trouble is, it takes my wife a lot longer to get out of the locker room than it takes me. She's got long hair that needs blow-drying, and I've got very short hair in various stages of balding. It takes me less time to dry my hair than it does to dry the tears I shed over how I used to have thick, lustrous hair... ;) I'm telling this story because it led to spending time sitting in the waiting area with only my Zune to keep me occupied. Although you can download a few games with the Zune software, they didn't hold my attention for long. After the first week, I began thinking about making my own Zune games with XNA Game Studio 3.0. Last weekend, I was messing around with a game mechanic I had in mind... And for a short time I was distracted by something only marginally related: rendering numerical values without generating any garbage. There was a forum thread on creators.xna.com a while back where people were debating the best way to draw numbers without garbage. Personally, I like my suggestion the best, so last weekend I implemented it. :) One could argue over the merits of premature optimization, but everybody knows Int32.ToString generates garbage, and this is a function I could write in a couple minutes. After that, it would always be there as an option, and I'd never have to think about it again. It's nothing fancy, but it works! Good ol' itoa! (Except my version handles Int32.MinValue, which is a special case that won't produce the proper result in the K & R version of itoa listed on Wikipedia.) About the fanciest thing about the new function is that it is an extension method. That means it can be used like an instance member on any SpriteBatch object, anywhere in your program, and you don't need to cast to a subtype or otherwise alter your code. Neat! Oh, I guess the other fancy thing about it is that it returns the "next" position where you would draw text if you were trying to format the integer in a string. That is, it returns a Vector2 that is the position you passed in, plus the sum of the X values returned by SpriteBatch.MeasureString on each digit of the number drawn. Rather than post the code, I've created an item template to make it easy to add to any Game Studio 3.0 project. The VSI (Visual Studio Installer) file will install the template if you click next, yes, ok, and finish enough times. After that, the template will appear in the XNA Game Studio 3.0 folder when you select Add New Item... in your game or game library projects. SpriteBatch.DrawInt32 Extension Method Item Template The code is straightforward, so you can modify it if you like (say, to create a MeasureInt32 method), or use it to create a bunch of overloads or whatever. December 22 Making Custom Game Templates in XNA Game Studio 3.0I live in the Pacific Northwest region of the United States, and this past week, we've had a patch of bad weather. There's nothing like a bit of nature to put you in your place -- and for the past few days, my place has been at home, staring out the window at the thick blanket of snow covering my neighborhood. Even with 4-wheel drive, my driveway is too steep for my truck to climb without chains. That's a great excuse to do nothing but sit around and play Xbox 360 games all day, but my console finally succumbed to a general hardware failure... Also known as the Red Ring of Death. Without an Xbox 360 to distract me, and without the ability to go out and get a new one, I had to find something else to do. I considered writing a blog entry, but I didn't know what to write about. I did something else instead, and since it's a few days later now, I'm going to write about that. A while back, I explained how to create custom templates for XNA Game Studio 2.0. I wrote that post because the built-in Export Template command in Visual Studio doesn't work with Game Studio projects, and it isn't extensible so we can't modify it to work. So this weekend, I wrote a utility of my own that will turn your game projects into game templates. [XNA Game Studio project template exporter] How to Use the Template ExporterFind a suitable directory on your machine, and extract all the files from the .zip file. One is an EXE named TemplateExporter, and the others should be supporting files (eg, documentation or license). Keep them together. To create a custom template, all you need to do is create a game project in XNA Game Studio, give it an appropriate name, and then run templateexporter.exe on the project file (.csproj). To make things super easy, here's how to create a menu command in VS to export the Game Template:
That's it. Now all you need to do is create a game project and run Tools->Export Game Template... Where the Templates GoIf everything works out, you'll be able to create a project from your template by looking in the My Templates section of the New Project dialog. A Few Words of CautionI haven't really tested this utility much. After it creates the template, it opens up Explorer at the folder where the template was created. This is so you know where it is, since I'm guessing most people will end up sharing the templates. Templates are .zip files, and the name is based on the name of the project you are exporting. If the name already exists, the template will be created with a random suffix to avoid overwriting anything. It's up to you to manage your templates. I don't have support for creating a template description or customizing the icon. You can do those things yourself pretty easily by editing the .vstemplate file inside the template's .zip file. That is, extract all the files, edit the .vstemplate file, then zip all the files back up again. Make sure you zip up only the files, not the folder - the .zip file has to contain the .vstemplate file in its root folder. Keep in mind that when you run the menu command, the arguments specify the current project based on the active document or Solution Explorer selection. Please note that game projects contain nested content projects... which means you can accidentally export just the content project if you have a content item selected when you run the command. If you export just the content without a game or library project, it won't do anything useful. This exporter utility was written for XNA Game Studio 3.0, but it will probably work with XNA Game Studio 2.0 projects as well (but I haven't tried it, and don't recommend it). If you don't have XNA Game Studio 3.0 at all, the templates should end up in your Documents folder instead. Enjoy!
[Note (Jan. 11, 2009): I've uploaded a new version of the template exporter and modified this post to delete the reference to a DLL that is no longer included (or required) in the ZIP file.] October 17 Cross-Platform Programming RevisitedUsing XNA Game Studio 3.0 to Build Cross-Platform GamesIt seems like ages since I blogged about cross-platform game programming in XNA Game Studio 2.0. The good news is that the soon-to-be ubiquitous XNA Game Studio 3.0 includes features that make cross-platform programming easier. The bad news is that there are still a couple tricky areas you should know about so you can deal with them appropriately. I'm going to describe the cross-platform features in Game Studio 3.0, and then provide one or two tips to make it easier to work with. In general, cross-platform programming is an advanced scenario. In Game Studio, we've tried to make it approachable, but to get the most out of your tools, you have to understand how they work so you can configure them to suit your needs. In Visual Studio, there are literally hundreds of configurable options (although not as many in VC# Express). The defaults are set to work with the most common scenarios only. Some of the defaults don't seem to work well at all with a typical Game Studio cross-platform solution. You might wonder why the default would be selected so poorly, but try to understand that there are millions of Visual Studio users, and only a small percentage of those are Game Studio users. The defaults are set to appeal to *most* people, and sometimes that means they will not appeal to anyone at all within a specific group. On the plus side, you have the option to change these settings. Incidentally, Sara Ford has a terrific blog about using Visual Studio. If you're a Visual Studio user, I highly recommend you check out her blog. Even if you're already an expert Visual Studio user, you're likely to learn something new from her posts once in a while. Porting Your Game Project to Another PlatformThe XNA Framework is a cross-platform framework. It provides a common API for programming games on Windows, Xbox 360, and Zune. The frameworks aren't exactly the same on each platform, but they are close enough that porting your game from one platform to another often requires only a recompile. To recompile the source for another platform, you need another project. The reason for this is because the projects need to reference different assemblies for both the XNA Framework and the underlying .NET Framework (Xbox 360 and Zune use the .NET Compact Framework), and C# projects don't provide support for referencing different assemblies for different platforms. Since you probably want to re-use all the same source code, and you probably don't want to maintain two copies of it, XNA Game Studio has a feature built-in to accomplish this for you. On the Project menu, there is a command named Create Copy of Project for <platform> where <platform> is one of the other supported XNA Framework platforms. This command does exactly what it says, but less than it implies. It creates a copy of the project file -- the .csproj file -- with a few tweaks to make it work with the new platform. The file is created in the same directory as the original, so instead of copying all the items in the project, they both share the same items. As a result, the new copy of the project adds only one new file to the solution, even though it looks like it copied all the code files as well. Since both projects contain the same code files, any changes you make to your code files will affect both projects. If you want to write code specific to one platform, you must use preprocessor pragmas to conditionally-compile your code. In some cases, you may want to eliminate the file from one project entirely. To do that, you must right-click on the item, and then select Exclude From Project. Warning: Be careful not to delete the file if you want to keep it in the other project! There's only one file, so if you delete it, it will be gone from both projects. In XNA Game Studio 3.0, certain changes to your projects are automatically synchronized across all copies for other platforms. In particular, whenever you add a new item to one project, that item will automatically be added to all copies of that project. If an item is renamed, the rename is reflected in other copies of that project. If an item is deleted, then it is deleted from all copies of the project. This automatic synchronization should make cross-platform solutions much easier to maintain. If you happen to add an item to one project that you don't want to be shared in the other copies of the project, use the Exclude From Project command in the other projects, as described above. Something to note when working with multiple copies of your game is that some settings are specific to the project. If you change a Build Action property, for example, then that change will not be reflected automatically in cross-platform copies. Setting project properties are also project-specific, as are adding assembly references. A special exception applies when working with content items in cross-platform projects. XNA Game projects support a special nested project type, called a content project. In the default projects, this appears as a special folder in the game project named "Content". When you want to build assets using the Content Pipeline, you have to put the items under this folder -- which is really adding them to a special nested project. When you create a copy of your game project for another platform, the copy shares code files with the original project instead of duplicating them. The same thing applies to the nested content project; the copy ends up nesting the same content project file, without duplicating it. Because the nested content project file contains all the Content Pipeline settings, including references to importers and processors, and Processor Parameters set for individual assets, the settings always apply to all copies of a game project. That means when you add an item and change its processor parameters, you never need to make the same change in the other copy of your project. In some cases, you may find that you want to compile some assets for one copy of your game and not the other. For example, if you have a game ported across Windows and Zune, you may want to use higher-resolution sprites in the Windows version of the game than in the Zune version of the game. Since assets in the shared content project are always built for both game projects, you'll always get both high- and low-resolution images in both copies of the game if you put them in the same content project. A new feature in XNA Game Studio 3.0 allows you to add new content projects to your games. When you do this, the new content project will automatically be added to all copies of your game, just like adding new code files. Similar to code files, however, you can remove content projects from individual game projects with the Remove command. By using three different content projects, you can build platform-specific content when you need it, and share it when you don't. The key is really to have one content project that is shared, and one in each game project that isn't (by removing it from the other project so it is no longer shared). After setting the Content Root Directory property on the content projects, you can ensure that the assets are built to the same location in either version of the game - meaning your content loading code doesn't need to be platform-specific. For example, you could have a texture named "ControlsLayout" that displays keyboard control layout on Windows, and Gamepad control layout on Xbox 360. The two different textures are in different projects, but they have the same name and each one builds to the same relative output location when the game projects are built (because of the Content Root Directory property). This means the loading code doesn't need to know which texture it's going to get at runtime. Working With Multi-Platform SolutionsWhen you work with more than one platform in a single solution, there are a few default settings that often get in the way. I'm going to explain how several options can be customized to streamline your workflow in cross-platform solutions. F5 and the Startup ProjectThe Start Debugging command, whose typical shortcut key is F5, is a complex command. By default, it means, "build every project in the active solution configuration, then deploy every project in the active solution configuration that supports deployment, and then run the startup project under the debugger." Start Without Debugging (CTRL-F5) means the same thing, except that it runs the startup project without the debugger. The part that trips most people up is that changing the startup project doesn't change which projects are built or deployed. So if you are working in a solution with an Xbox 360 game and a Windows copy of that Xbox 360 game, then by default, F5 will continue to build and deploy the Xbox 360 game even if the Windows copy of the game is the startup project. (Same goes for Zune, but it's easier to just talk about one at a time.) I say default a lot, because I want to impress upon you that you can change it. :-) There is an option available to only build the startup project and its dependencies when you run. That way, when you change the startup project from the Xbox 360 copy of the game to the Windows copy of the game, you'll stop deploying the Xbox 360 game on F5. The Build Solution command will still all projects in the active configuration, but F5 will skip the ones you don't need. Another way to avoid deploying projects when you want to work locally is to change the active solution platform in Configuration Manager. By default, the active solution platform in a multi-platform solution is Mixed Platforms. If you open Configuration Manager, you can see that the Mixed Platforms configuration is set to build and deploy all projects (as indicated by the checkboxes). By changing the active platform, you change the set of projects that will be built and/or deployed when you build the solution. You can quickly change the active platform using the Solution Platform combobox on the standard toolbar. Warning: The Startup Project is independent of this setting. So if you change the active solution platform, you should also change the Startup Project to correspond to a project for that platform, and vice versa. Intellisense and This File is Already Open in Another ProjectWhen working with files that are shared across multiple projects, it's important to recognize that the context under which you open a file is relevant. If you don't already know what I'm talking about, that sentence probably didn't make a lot of sense. What I mean is, when you open a file that belongs to a project, the code editor uses settings in that file's project. Intellisense and statement completion is provided for assemblies referenced by the project, as well as by other code files in the same project. If you have conditionally-compiled blocks of code, syntax highlighting is provided based on preprocessor symbols defined in the active project configuration. Now, suppose that you have one file that belongs to two projects that reference different assemblies and which define different preprocessor symbols. Then it makes sense for the code editor to provide its functionality based on the project that opened the file. In other words, if you open a shared file under a Windows game project, the code editor will behave differently than if you opened the same shared file under a Zune copy of the Windows game project. If you try to open the file from the Zune project and it was already opened by the Windows project, then an informative dialog will pop up to tell you that the file is already open under another project. That dialog isn't giving you a warning; it's just telling you that the editor won't give you the context that you might expect. If you double-clicked on the file under the Zune project, you might not expect to see statement completion for types that are not part of the Zune framework's API. In many cases, it won't make a difference to you, and you can just dismiss the dialog and continue editing. However, if your statement completion or conditional blocks seem to be off, then you might want to close the editor and re-open the file in the context you intended to be working in. SummaryCross-platform programming in XNA Game Studio 3.0 is easier than ever. You can port your game to another platform in a single click, and the projects automatically keep themselves in sync when you add, delete, or rename files. Working with multi-platform solutions, it's important to recognize which projects are going to build and deploy when you press F5. Although not set by default, the "Only build startup projects and dependencies on Run" option is extremely useful. I recommend you set it. Code editors provide features based on context from the project. Try to open your files from the project corresponding to the platform you're working on, or the editor may not provide the help you want. October 05 Making a Designer of Your Own - An Imperfect ExampleMonths ago, I started a little project that was supposed to be a diversion from my day job. My day job is building XNA Game Studio... And my project was an experiment to make programming with XNA Game Studio easier. I admit that I'm not very good at creating diversions for myself. Anyway, after a couple nights of playing with samples from the XNA Creators Club Online web site, I had integrated the MenuScreen class into Visual Studio's designer framework. The result was being able to double-click a code file in a game project, and have a game menu open up in a document window. I added support for dragging-and-dropping menu items from the Toolbox, and editing properties in the Properties Window. It demonstrated some exciting possibilities, but at that point was only barely functional. After posting some initial screenshots to my blog, I discovered that, although I'm terrible at distracting myself from work, I am pretty good at finding interesting things to work on. My blog traffic exploded by two orders of magnitude, and I received a number of requests for source code. I'm pretty certain that my source code isn't self-explanatory. The designer I built is a customization for an existing framework, and the vast majority of the functionality isn't in my source. Anyone who understands enough of the framework to see how my source code works doesn't need the source code in the first place. So instead of answering requests with code, I started to explain the framework I was integrating into. I'm finally at the point where further explanation will be easier to follow with a working example. For that reason, I've put my prototype project on CodePlex. The project doesn't build a ready-to-use designer. The design-time experience is runnable, but not usable. It's got tons of potential, and I'm going to continue working on it, but at this point, it's far from perfect. My CodePlex project is called ferpect. If you are interested in building designer support for your own libraries, then I encourage you to take a look at it, and join the discussion if you'd like to request specific features be added for your reference. I'm really looking forward to continuing my work on this project. Now that the fundamentals are out of the way, I think being able to review changesets and associated check-in comments will cut down on the amount of explanation required for new features and concepts. That's all for now! September 28 Making a Designer of Your Own - Mixing and FoldingFor those of you who are waiting for my next article on creating a designer for XNA Framework game components, here it is! I've been working on creating a CodePlex project for the sample source code, but this turned out to be more work than I first imagined. I took a few shortcuts when I initially created my little designer, and for the code to work on other people's machines, I had to implement a bunch of additional features (no more hard-coded paths or pre-compiled assets!). Anyway, as I continue to get that all prepared, I was forced to fix a few bugs in my designer serializer. I'd forgotten how some of my own code worked, so it took a bit of debugging before I figured out what was going on and how to fix it. Certainly there will be more bugs, so I decided to write about how serialization works -- both for my own benefit, and for others. What The Heck is Serialization?In the managed designer infrastructure, serialization is the process of converting objects into source code, and deserialization is the process of converting source code into objects. In general, serialization is easier than deserialization, but both are quite complicated. Fortunately, the managed designer infrastructure provides an extensible serialization implementation based on CodeDom. The key word there is "extensible," because it means we can rely on the standard behavior most of the time, and then customize things when we need to. For convenience in the following description, let me introduce the term serialization manager as the part of the designer infrastructure that coordinates serialization and deserialization. To deserialize an object from source code, the serialization manager invokes a parser to convert the source code from a file into its CodeDom representation. The CodeDom represents a type declaration and its initialization method. The serialization manager reflects on the base type to retrieve its DesignerSerializer attributes, which identify the specific CodeDom serializers to use. Then the serialization manager will instantiate the appropriate serializer and invoke its Deserialize method. There are two kinds of serializers: TypeCodeDomSerializer and CodeDomSerializer. The first is for serializing/deserializing a type declaration. The second is for serializing/deserializing statements. To deserialize an object from code, you need both kinds of serializers. This is a good time for an example. Let's say that I have the following code in a file called MenuScreen1.cs: 1: public class MenuScreen1 : MenuScreen 2: {3: private ToggleMenuItem toggleMenuItem1; 4: 5: public MenuScreen1() 6: {7: this.InitializeComponent(); 8: } 9: 10: private void InitializeComponent() 11: {12: this.toggleMenuItem1 = new Ferpect.GameState.MenuItems.ToggleMenuItem(); 13: // 14: // toggleMenuItem1 15: // 16: this.toggleMenuItem1.Label = "Sound"; 17: this.toggleMenuItem1.ToggleValue = true; 18: // 19: // MainMenuScreen 20: // 21: this.MenuItems.Add(this.toggleMenuItem1); 22: this.Text = "Options"; 23: } 24: }When the parser reads this file and converts it to its CodeDom representation, it ignores all properties and methods in the class except the InitializeComponent method. The resulting CodeDom is a CodeTypeDeclaration with fields and one method containing a statement collection. After parsing, the serialization manager needs to get the serializers for the base type of the class being deserialized. By inspecting the CodeTypeDeclaration, the serialization manager will determine the base class is MenuScreen (as seen on line 1). It then uses ITypeResolutionService to get a Type instance for MenuScreen, and then uses TypeDescriptor to get its DesignerSerializer attributes. If the base type doesn't have any DesignerSerializer attributes, the serialization manager continues searching up its inheritance chain until it finds a base type that identifies a TypeCodeDomSerializer. After finding the type serializer for MenuScreen, the serialization manager will instantiate the serializer and invoke its Deserialize method, passing it the CodeTypeDeclaration for MenuScreen1. The standard TypeCodeDomSerializer will look at the base type in the CodeTypeDeclaration, MenuScreen, and instantiate it. This very first object is returned to the serialization manager and placed on the design surface. Since it's the first component on the design surface, it becomes the root component, and its name is the name of the type being deserialized, MenuScreen1. The serialization manager then adds MenuScreen1 to its name table. At this point, MenuScreen1 is only partly deserialized. After adding the root component to its name table, the serialization manager begins to interpret the code statements in the InitializeComponent method. The InitializeComponent method contains statements that are sorted into groups according to the object reference on the left-hand side. That is, all the statements referencing "toggleMenuItem1" are grouped together, and so are the statements referencing "MenuScreen1". Each member variable is added to the name table, then for each one, the serialization manager retrieves the CodeDomSerializer for the member variable's type, and uses it to deserialize that member's assignment statements. The last step is to retrieve the CodeDomSerializer for MenuScreen, and use it to deserialize the remaining assignment statements for MenuScreen1. If there is an assignment statement that references another member variable on the right-hand-side, the serialization manager is used to resolve the name. If the object hasn't been added to the name table yet, then the serialization manager will immediately deserialize the named component's statements so that it can return the instance. In the example above, toggleMenuItem1 will be deserialized into a ToggleMenuItem instance, and that instance's Label and ToggleValue properties will be initialized to "Sound" and true, respectively. When MenuScreen1's statements are deserialized, you can see there is a reference to toggleMenuItem1 on line 21. The serializer will resolve this reference by asking the serialization manager for an object with that name. Since toggleMenuItem1 is already deserialized at that point, it is returned, and then can be used to be added to the MenuItems collection. When the deserialization is complete, the design surface shows a MenuScreen instance that has been initialized exactly like the code in the MenuScreen1.cs file. Serialization is similar, but works in the opposite direction. The root component is serialized into a type declaration, then each of the other components on the design surface becomes a member variable plus some initialization statements, and finally the root component's initialization statements are generated. The statements usually are generated into the InitializeComponent method, because that's the only method that will be parsed during deserialization. A designer isn't going to be much use if it can't round-trip from objects to code and back again. The code file, MenuScreen1.cs may contain other code written by the user. For example, the user may add event handlers. After serializing the design surface to CodeDom, the CodeDom is turned into source code by a code generator. That source code is then strategically inserted into the source file to completely replace existing methods with the same names. Skillful Substitution ExplainedIn my last post on designers, I said the following:
The reason I started to write this whole series in the first place was to explain how to re-use the existing Windows Forms designer for XNA Framework game components. The problem with trying to use the Windows Forms designer is that it only works with Windows Forms Control classes, and our XNA Framework game components are not Controls. As I discussed in previoius posts, it is possible to implement a Windows Forms Control that will create an XNA Framework graphics device and draw an XNA Framework game component. Following my example above, let's imagine that MenuScreen is an XNA Framework game component. To be able to design this component in the Windows Forms designer, I need to deserialize MenuScreen into a Control that can draw a MenuScreen component (let's call that a MenuScreenControl). I need several things for this. First, I need to put an attribute on MenuScreen to point to a custom type serializer. The type serializer is responsible for deserializing the CodeTypeDeclaration into an object instance. With a very small amount of code, it's possible to create a custom TypeCodeDomSerializer that simply modifies the CodeTypeDeclaration it is given so it looks like MenuScreen1 derives from MenuScreenControl, and then passes it along to the TypeCodeDomSerializer for MenuScreenControl. The MenuScreenControl instance will use ICustomTypeDescriptor to make it look like a MenuScreen instance when viewed through reflection (design-time reflection is always done with TypeDescriptor). Since serialization uses TypeDescriptor, we don't need to modify the TypeCodeDomSerializer's serialization step -- because the ICustomTypeDescriptor implementation fools the serializer into thinking it's serializing a MenuScreen instance. The MenuScreenControl implementation is pretty straightforward. It is just a GraphicsDeviceControl with ICustomTypeDescriptor. Since it needs to draw a MenuScreen instance, it makes sense to instantiate one of those and store it in a member variable. Then the ICustomTypeDescriptor implementation just needs to invoke TypeDescriptor on the MenuScreen instance. Note: There is a bit of futzing required to make the properties work properly in the Properties Window, but I'll explain that another time. The last thing required is a custom CodeDomSerializer, also identified by an attribute on MenuScreen. This one is required to acquire the CodeDomSerializer of a standard Component, and then just delegate its Serialize method to that instance. The reason for this is because the initialization statements are generated by the CodeDomSerializer, and the standard serializer for a Control will generate additional statements for Control layout that won't compile when applied to a game component. Wrapping Up the LeftoversAt this point, you're probably thinking, "How many times can this guy write 'serializer' in one post?" I don't blame you. But if you stuck through all this, then you're probably starting to see how extensible the designer framework really is, and how finding the right extensibility points is the key to re-using code and saving yourself a truck load of work. September 17 Debugging Zune Games in XGS 3.0If you've been using XNA Game Studio to develop games for Zune, you're probably familiar with the following debug output:
This message gets spit into the Output Window while debugging, about once per second, if not more. It shows up so frequently, in fact, that it makes it very hard to see any other output at all! Since it's pretty rare that you're actually interested in these thread exit messages, seeing them can get quite aggravating. That's why I'm posting this to explain how to suppress these messages from the debug output. While debugging, right-click on the Output Window, and uncheck "Thread Exit Messages". Done! Now you can use the Output Window again! Contact InfoI know I said in my last post (ages ago!) that I would provide designer source code in my next post (this one)... But I was wrong, because here is the next post and there's no source code anywhere to be found! This post is specifically to address a couple people who have tried to contact me recently. I can't reply to messages unless you provide an e-mail address, or you set your account permissions to allow anyone to send you messages. If you were expecting a reply and didn't get one, please verify that you gave me a way to answer, and ping me again. I'm happy to answer a few questions, but I do need a way to respond. Thanks! P.S. For the rest of you, I've started setting up a project on CodePlex... But in doing so I found a bunch of features I haven't implemented yet. For example, rendering the design surface when an asset can't be loaded (what should the menu designer show when there are no fonts in your project?). July 27 Making a Designer of Your Own - Soup StockIn my last couple posts, I've been explaining concepts needed to customize the managed designer infrastructure in Visual Studio. I've been forced to put this project aside for quite a while on account of being too busy with other things... But this weekend I really need to think about something other than work for a while. Despite Shawn thinking I'm nuts for having the same hobby as my work, the important thing for me is that I work on different problems. Thinking about my own programming problems doesn't involve quite the stress as thinking about programming problems for work. Anyway, I've been using the analogy of learning to cook as the theme of my blog posts. In this post, I'm going to describe a set of base classes that we can re-use for nearly any type of designable component. In cooking terms, it'll be like creating a soup stock. It forms the basis of many dishes, and each one can be uniquely characterized by adding different elements! The ConceptI want to create a visual designer for an XNA Framework drawable component, so the first thing I need is a way to render it in a Visual Studio editor. When I first thought about this, two things came to mind. First, there is already a designer for Windows Forms Controls. Second, the Creators Club Online web site has a sample showing how to render XNA Framework graphics classes in a Windows Form Control. At this point, it almost seems like there's nothing left to do! On the other hand, I don't really want a Windows Forms Control in my game because Windows Forms is not supported on either Xbox 360 or Zune. I would prefer to keep the code in my game libraries platform-neutral, so I can use the same components no matter what platform I'm targeting. Hmm... I really want a designable XNA Framework game component that doesn't derive from Control, but creates a Control in the design surface when opened in design view. Furthermore, I want to edit the component's properties instead of the Control's properties. Sounds difficult -- and it is -- but applying the right ingredients to a powerful stock will result in exactly the secret sauce I'm after. For now, let's focus on building the right stock. Later, I'll explain the switcheroo. The PrepThe sample (here) provides source code for GraphicsDeviceControl, a Windows Forms Control that renders XNA Framework content. It's trivial to derive from this existing class and override the Draw method to draw whatever XNA Framework content that I want. That means it's trivial to make a designable Control that uses the XNA Framework to render itself. To get started, we need to prepare a couple things. First, I'd like a base class for my designable components. The XNA Framework's DrawableGameComponent isn't suitable as a base for my designable components because it requires a Game class instance to be provided to its constructor. At design-time, we're not going to have a Game instance, so we wouldn't be able to instantiate and use it. We also would like the base class to derive from Component so that Visual Studio recognizes it as designable (at some point, we're going to need this, even if I haven't explained how it will work yet). To start, we need two classes: the first will derive from GraphicsDeviceControl, and the second will be a modified version of DrawableGameComponent.
To create a specific designable component, I'll need to introduce more classes. However, each specific designable component can re-use these same base classes. We'll look at examples another time. The CodeI put the code files below on my skydrive, with links below. Some of the code files are copied or derived from samples that were made available under the Microsoft Permissive License (Ms-PL). In accordance with the license, my derivative source can only be made available under the same license. Be sure to read it before looking at any of the code files! For your convenience, I'll note the source of the original code file and a summary of the changes I've made (at least those I remember).
Next StepsWe've gathered up the primary ingredients, but it's pretty clear we can't just throw them in a bowl and set the mixer on high. We have a ViewControl class that is supposed to draw the ViewComponent, but we don't have a way to get the user experience we need. What we want is to be able to add types in a project that derive from a specific ViewComponent, and have that show up in the designer when you double-click the file. So, for example, let's say we have this specific ViewComponent:
We want to be able to define new classes in our projects like this:
...And then have MenuScreen appear in the designer when the code file is opened in design view. As I described in a previous post, the designer infrastructure will instantiate the base class of the designable component. We already know that MenuScreen is a ViewComponent, so it relies on its host to provide a graphics device and to tell it when to draw. To be able to draw the MenuScreen, we need another class:
We don't want this control to show up anywhere in the project where MenuScreen1 is defined, but we do want it to be instantiated in the designer in place of MenuScreen. The magic trick to this is customizing MenuScreen's designer serializer. For detailed information on designer serialization, refer to Designer Serialization Overview. When you try to open a code file in design view, the designer infrastructure looks at the base type of the class to see if it inherits from Component. If it does, it then looks to see if the class has a designer serializer. If the type doesn't specify one, the serializer is inherited from its base class. By default, every designable component will use the Component's designer serializer. The job of the serializer is to instantiate the component from its serialized state (or to deserialize it). The designer serialization is based on CodeDom, so the serializer is passed a CodeDom definition of the base class from the code file, and is asked to deserialize it. Naturally, the default serializer simply instantiates the type specified in the CodeTypeDeclaration passed to it. By providing a custom designer serializer, we can customize the deserialization step. You might have already guessed that by doing this, we can instantiate a MenuScreenControl in place of the MenuScreen component specified in the CodeTypeDeclaration! Skillful SubstitutionEvery experienced cook knows how to substitute certain ingredients for others. However, it takes a certain amount of skill and know-how to still end up with a palatable result. (I'm really determined to keep this cooking analogy going...) In our designer, it's not enough to just instantiate a MenuScreenControl in place of a MenuScreen. When looking at the design surface, it'll be exactly what a user might expect, but as soon as the user tries working with it, the illusion will fall apart! The Properties Window is going to reveal that the class is a MenuScreenControl instance, and it won't have the MenuScreen's properties. It's like we're trying to pass off tofurkey for the real thing at Thanksgiving dinner! Nobody who's ever had real turkey before is going to be fooled! More design-time tricks are needed, and this time, ICustomTypeDescriptor is our secret ingredient. By implementing ICustomTypeDescriptor on our MenuScreenControl, we can make it appear to be an instance of MenuScreen. We can give it the same type name, the same properties, the same attributes. The best part is that if we do it right, we'll even fool the MenuScreenControl's serializer into generating code for a MenuScreen. That means no more serializer customization! To pull off the ICustomTypeDescriptor implementation, a few helper classes are needed. They aren't very complicated, but it'll be easier to just show them off with a sample. Next time, I'll provide a working sample and explain how to set it up properly. Proper set up is essential to avoid common pitfalls in designer debugging. After that, I'll start to introduce some neat feature ideas specifically for XNA Framework projects. July 12 Managed Designer Resources: The Restaurant Supply StoreRecently, I've been pretty swamped by work. Nights, weekends, holidays... I've been in the office so long, my wife has forgotten my name! This year, crunch time is like the playoffs - I'm not shaving and my beard is growing out. It's my first time with a beard, really. Honestly, it drives me nuts. I hate having food or drinks touch my my moustache and leave a lasting flavor. So besides working all the time, I'm also constantly wiping my mouth. Bleah! Anyway, I've gotten a few messages from people asking for more about designers. Well, I still don't have time to put into writing. I did a couple weeks ago, but a hardware failure destroyed several hours of work and I didn't feel like starting again right away. Then I got too busy. So instead of writing my own article on designers, I'm going to post a number of links where you can get information from the source: MSDN. If I'd found the second link earlier, I probably wouldn't have written one of my posts since it covers the same stuff. I think I remember reading it several years ago, but I didn't find it when I looked again. Here's a topic on developing components: http://msdn.microsoft.com/en-us/library/51sc2s5c(VS.71).aspx This specific sub-topic discusses design-time attributes: http://msdn.microsoft.com/en-us/library/tk67c2t8(VS.71).aspx That page links to the docs on customizing the design-time, which is what I'm writing about: http://msdn.microsoft.com/en-us/library/37899azc(VS.71).aspx The set of topics above actually provides a great deal of information and several samples. I think that's the best set of documentation for this kind of stuff. After having posted these links, I'll try to focus on XNA Framework-specific customizations from now on. It'll probably be another week before my schedule is back to normal. After that, I'll be back to writing. June 24 More on Designers... Still to ComeI've started the next three posts for my series on customizing the designer. Unfortunately, I haven't had time to finish any of them. I've received a couple messages from people saying thanks for the info... But when is the next part coming? :-) I really appreciate that people are interested, and I do intend to continue writing. Sorry that it's taking me so long. Life has caught up with me. When I built my menu designer in the first place, it was because I was looking for a diversion after writing a big design document. Well, implementation of that design is underway, and I'm having to put in longer hours to try to keep the schedule I set for myself (I ran into a couple unexpected setbacks -- which, ironically, is generally expected). That leaves less time at the end of the day for other interests. Add to that the fact that it finally stopped raining (it rains all winter here), and suddenly I don't have an excuse anymore to sit inside instead of going out to work on my house and property. The remodel I started in January isn't finished yet, and I've got family coming to visit later this summer. So my evening hours are spent working with power tools instead of software tools. My wife doesn't care who's waiting for my next blog post - she wants the house put back together and the furniture moved back in! Thanks for your patience... I'll be posting the next article as soon as I can! June 10 Making a Designer of Your Own - House SaladLast time, I started to write about the managed designer infrastructure in Visual Studio. This infrastructure is all part of the .NET Framework's System.ComponentModel namespace, and so it naturally deals with components (things that implement System.ComponentModel.IComponent) as the base unit. The component model is meant to provide a generic framework for any kind of designer, but in Visual Studio it was implemented specifically to support Windows Forms and source code generation. It was later made more generic, with the intention of supporting any components, but there is still a lot of implementation that works only with Controls. So what does the Control designer have to do with making a designer for XNA Framework game components? Nothing, except that in order to re-use the existing infrastructure, you'll need to know a bit about Control designers. Combining all the ingredients available in the Component Model to create a visual programming experience for XNA game components takes a fair bit of background knowledge. As I said in my last post, you can't just look at my recipe for a Game State designer and know how to build your own Input designer. The bulk of the implementation isn't in my code - it's in the designer infrastructure. If you want to make your own dishes, you need to know what's in the pantry, and how to combine certain things to get the desired flavor. Last time, I showed how any class deriving from Component will support a design view in Visual Studio. This design view is based on the concept of composition. You add objects to the class by dragging them from the Toolbox onto the design surface. Doing so results in a field being declared for the new object instance, and code to instantiate and initialize it. The designer lets you select and name objects on the design surface so that you can further edit them in the Properties Window. After making these changes, the entire design surface is serialized to code so that the composite class can be compiled and then re-created at runtime. When you design a Control, the objects on the design surface are usually represented at design time by the same objects you'll use at runtime. So when you add a Button to the Form designer, a real Button instance is created on the design surface to represent the Button instance you'll get at runtime. When you change the Text property on the Button in the Properties Window, the text on the button changes on the design surface because you've actually changed the Text property of that Button instance. You can change the BackColor and ForeColor properties for more visual evidence that the Properties Window is letting you edit the actual control instance on the design surface. At first glance, you might imagine that the Properties Window is using reflection to enumerate the properties of the selected object, get their values, and allow you to set them. That makes sense and might even seem pretty obvious at first... But if you change the Visible property to false, suddenly things are not so obvious. The button is still visible on the design surface! (If you aren't sure what Visible is intended to do, you can run the project at this point to see that the button isn't visible at runtime.) Figure 1 - Something invisible is revealed when you set the Visible property to false. The reason the Button didn't disappear is because the Visible property in the Properties Window doesn't belong to the Button the way the Text, BackColor, and ForeColor properties do. Instead, it belongs to the Button's designer. :-) At this point, things are undoubtedly going to get confusing. The term "designer" is overloaded, and I need to use it to mean something other than the visually-oriented composition editor we've been discussing until now. Every component created on the design surface has a designer associated with it. The designer is another class that modifies the design-time appearance and behavior of the component. One role of the component designer is to filter or enhance an object's metadata. To put it another way, it can change the attributes on a component or its properties, and can remove, replace, or add properties. A designer can do this to change both the appearance and behavior of a control at design-time. As an example, the Button's designer replaces the Visible property with a different property that comes from the designer instance. In designer-speak, this is called shadowing. In the Properties Window, the Visible property appears to belong to the Button, but in fact it belongs to the Button's designer. When you change the Visible property value, it's the designer's property that is changed (you don't see the effect because the designer was never visible in the first place). This prevents the Button instance from becoming invisible, which would make it hard to select or reposition it on the design surface. If you look more closely at the properties of the Button instance, you'll notice there are a few listed that aren't actually properties of the Button class. To pick out just a few, in the Design category, you'll find "GenerateMember", "Locked", and "Modifiers". These control whether the instance will be declared as a field of the class or a local variable, whether the control can be moved or resized on the design surface, and the visibility modifier of the field declaration (if there is one), respectively. Changing these properties affects the code generated for the Button instance, but they don't represent actual properties of any object on the design surface. A basic designer implementation is provided by System.ComponentModel.Design.ComponentDesigner. This class makes it easy to override just the parts you want to modify. As the documentation says, you associate a designer with a component through the DesignerAttribute. Now, a word of caution when using the DesignerAttribute. Its constructor has overloads to use System.Type or System.String arguments to specify the designer's type. If you implement the designer in the same assembly as the component, you can use the System.Type constructor. However, if you implement the designer in a separate assembly, it is almost always a good idea to use the System.String constructor. The reason is that the designer is always given a reference to the component instance, and if you want the designer to be able to reference that component by its proper type, it will be dependent on the component's assembly. That precludes having a reference to the designer assembly from the component assembly, because then you have a circular dependency. So the way to avoid it is to allow you component to be ignorant of its designer, except for its name. A very good reason to have the designer in a different assembly is because the designer will often depend on a bunch of assemblies that you don't ever need at runtime, or that maybe aren't redistributable. In fact, the designer class itself is not needed at runtime! So if you keep it in a separate assembly, then you can keep your runtime libraries lightweight and your redist small. Your customers will appreciate that! I think I'll wrap it up here. Next time, I'll write more about how designers affect the behavior of controls on the design surface. At this point, it's also appropriate to start using code samples to illustrate, and I don't have anything ready right now. June 02 Making a Designer of Your Own - A Starter CourseA couple people have e-mailed me recently about my "teaser" screen shots, because they want to know how I did it. As much as I'd like to explain the menu designer I'm working on, it's complicated. Writing long blog entries just isn't as interesting as writing code (for me, at least), so in all honesty, I'd rather work on the designer than try to explain what I'm doing. The easiest way to explain it is, I'm using the Windows Forms designer framework. It's reasonably well-documented, but not all in one place. You can't read it like a book. Someone For that reason, I'm not going to share my recipe -- not yet, at least. Instead, I'll try to focus on explaining how to cook. Let's start with an appetizer, and work our way up to the entree. In Visual Studio, there is already an extensible designer infrastructure for components (types that implement System.ComponentModel.IComponent). This infrastructure has further specialization to support Components (types that derive from System.ComponentModel.Component) and Controls (types that derive from System.Windows.Forms.Control). Note that the general infrastructure is for the IComponent interface, while there is extra stuff for the Component class, which is a default implementation of the IComponent interface. There is even more extra stuff for Controls and various specialized subclasses, like Form. The extra stuff is the interesting part. Windows Forms has an editor registered in Visual Studio that will handle any code file in a code project that contains a class declaration for a class that derives from Component. The IDE determines this by asking the language service (the thing that gives you Intellisense) for the first class declaration in a code file. Then it uses another service (probably ITypeResolutionService, but I forget, and it doesn't matter enough to check), to determine whether the base class of the type declaration derives from Component. If so, then the file is treated as a "component" file. Let's take a look at an example. Figure 1 shows a regular Class Library project in VC# Express. Look what happens when I edit the declaration for Class1 to derive from Component. Figure 2 shows the subtle change. In the Solution Explorer, the icon next to the Class1.cs file is the "component file" icon. Even though the file is already open in the code editor, now when you double-click on the file in Solution Explorer, it will open the Component Designer. Figure 3 shows the Class1 class in the the Component Designer. The Component Designer provides a basic, visual code editing experience. You can drag and drop components onto the Class1 design view from the Toolbox, and edit those objects in the Properties Window. The changes you make will be serialized to code in a method called InitializeComponent in the Class1 class declaration. After some experimentation, an observant person will note several things. First, the generated code appears in InitializeComponent, but that function is never called. This is because the designer generally ignores everything else in the class except that function. The standard practice is to define a default constructor that calls InitializeComponent. Since Class1 was not created from a Component template, I'll need to add that code myself. Second, the name of Class1 in the Properties Window refers to the name of the type Class1. Every other component dropped onto the design surface has a name that refers to the name of the member variable that the component is assigned to. Third, the properties in the Properties Window aren't the properties of Class1. If you add a new property to the class definition in Class1.cs, it won't appear in the Properties Window at all. For example, let's add a string property to Class1. public class Class1 : Component { private string myProperty; public string MyProperty { get { return myProperty; } set { myProperty = value; } } } Going back to the design view, MyProperty is nowhere to be seen. If you look closely at the Properties Window, you'll see the reason why. It shows Class1 to be an instance of System.ComponentModel.Component. This is important -- the designer creates an instance of the component's base class at design time. The same goes for editing Forms or Controls. This only applies to the root component, however -- the type being designed -- and not for components that are dragged onto the design surface. Part of the reason for this is because you are changing the source code for the type as you edit it in the designer -- making it difficult to keep a compiled instance of the type-being-edited up to date with the latest changes. Now that you know the designer creates an instance of the base type instead of the actual type, you might already have guessed what I will do next. I'm going to add another class to the project in its own file, Class2.cs, and declare a new class Class2 that derives from Class1. Now, as long as I've compiled the project first, I can double-click on Class2.cs and have it open up in the designer. This happens because Class2 derives from Component. Except this time, the instance on the design surface is an instance of Class1! See how the Properties Window shows MyProperty? Figure 4 shows that you can design your own types in the Component Designer. Getting your own class into the Component Designer is the first step. Now it's time to customize! There are a lot of ways to customize the design-time experience for particular components. Most of it is driven through metadata, or custom attributes. For example, using attributes, I can change the way MyProperty appears in the Properties Window. public class Class1 : Component { private string myProperty = "elephant"; [DefaultValue("elephant")] [Category("Animals")] [DisplayName("My Property")] [Description("Set this property to the first thing that comes to mind.")] public string MyProperty { get { return myProperty; } set { myProperty = value; } } } After rebuilding the project, the Properties Window for Class2 reflects the changes. Figure 5 provides an example of how you can customize the design-time behavior of your components. The MyProperty property is now displayed as My Property (note the space), it is in the Animals category instead of Misc, and it has a description at the bottom of the Properties Window. The value of the property is "elephant", which is the default value. This is important to the designer because when a property has its default value, it is not serialized to code. Imagine having a component with two hundred properties, and all but one is set to its default value (the value it has when constructed from a default constructor). You would want the code generated for that component to only set the one property that was different from its default, not all 200 values. Otherwise, such a class would initialize 199 of its properties twice! It's important to note here that the DefaultValueAttribute custom attribute is used as a hint to the designer, and doesn't actually set the property to anything. It's purpose is to tell the designer what the default value is, so the designer can compare the property's actual value against it when it's time to generate code. The Properties Window also uses it to visually indicate which properties need to be serialized -- that is, if a property's value is anything but the default, then that value will appear in bold in the Properties Window. If you specify the wrong value in the attribute, the designer will behave incorrectly -- so be careful! There are a bunch of attributes in System.ComponentModel that are interesting. A few noteworthy attributes in terms of editing properties are:
In my menu designer, I've been getting a lot of mileage out of creating custom type converters. I created one to enumerate all types that derive from GameScreen to assign to a System.Type property, I created one to enumerate the names of all SpriteFonts in the nested Content project to assign to a string property, and I created another to enumerate the names of all Texture2D content to assign to another string property. Okay, that's as far as I'm going to get in one sitting. Our starter course is looking pretty modest -- more like cheese and crackers than shrimp cocktail -- but at least there's something on the plate! May 27 Compelling Design-Time Experiences in an XNA Game Studio-Based DesignerThe other week, I started playing around with building a designer for Game State Management. It wasn't long before I had something working -- mainly because I leveraged a lot of existing code in the .NET Framework. The screen shots I showed previously are the result -- a design surface that can interact with the Toolbox and Properties Window. This is sufficient to quickly add menu items, edit their appearance, and even generate event handlers for input events. I was pretty excited about it and that's why I posted to my blog. Unfortunately, the MenuScreen and MenuEntry classes in the Game State Management sample weren't intended to be designable. They didn't have many interesting properties or events, so the designer could only save you from writing a couple lines of code before you had to drop back to source view and type the rest of your program with a keyboard. Gross! ;-P Last week, I thought about how I often navigate menu screens in games -- selecting a menu item to activate another menu screen, then canceling back to the previous screen, and so forth. To build a navigation system without writing any code, I needed a way to specify what the next screen from within the designer. What I came up with was a property of type System.Type, which could only be set to a type that derives from GameScreen (the base class for all screens). I implemented a navigation item that, when selected, would activate a new instance of the specified GameScreen type and begin a transition to it. That was pretty easy, except for the idea of someone trying to edit the System.Type property in the Properties Window. You can customize the design-time behavior of properties by using a TypeConverter, that you can associate with the property by using a TypeConverterAttribute. My custom TypeConverter class uses ITypeDiscoveryService to enumerate all the available GameScreen types in the project and list them in a combobox for easy selection. This new ability to select a screen type in the designer makes it possible to build fancy menus with pop-ups and structured navigation without writing any code. After that, it was a snap to add another navigation item that could both specify the next screen, as well as a second screen to animate during lengthy, asynchronous content loads. More recently, I thought about how I'd like to customize the appearance of a menu. I'd like to do things like customize the menu's font, or create background screens by selecting textures. I actually would like to be able to create a whole 2D scene out of sprites on non-menu screens, or to display sprites when menu items are activated... But first things first. A compelling design-time experience for any of this would require the designer to know about the content built by a project. If the menu had a Font property, you'd want the Properties Window to let you choose from a list of SpriteFont items in your project. Then, for a really good design-time experience, you'd want the designer to actually use that SpriteFont when rendering the design surface. The way I picture that working would be to have a string property for the Font, which specifies the asset name of the SpriteFont. The TypeConverter for this property would enumerate the SpriteFont items from the project, and then the menu would load the asset by name during initialization. The hard part, unfortunately, is figuring out what SpriteFonts are available in the project at design time. Tonight, I believe I've solve this problem. This figure shows a SpriteFont property added to the MenuScreen which lists available SpriteFonts from the content project at design-time. It doesn't yet use that font for rendering the design surface, but that's the next logical step. Writing a TypeConverter to be aware of the content items was tricky and required very specific domain knowledge of Visual Studio and the managed designer infrastructure, but didn't take a lot of code. I'm still implementing everything in Visual C# Express 2005, and am able to run it and debug it without installing any packages or add-ins into the IDE. I also do not have the VS SDK installed on my laptop. To be honest, I'm a bit surprised at how much you can do without the VS SDK! Being able to enumerate content from within the designer opens the door to all kinds of interesting scenarios! I'm looking forward to thinking about it some more! (I'm always looking forward to having free time!) :-) May 20 Going Somewhere? Menu Navigation Woes.There have been about a billion views of my blog since I posted screen shots of the menu designer I started working on last week. For those of you who are keenly interested in that particular topic, I am still working on it. I mean you, Eli! ;-) Getting a design surface going took some technical trickery, but I've done it before so it was no big deal. The real difficult part is making a set of classes that are intuitively designable. What does that mean? Well, I guess I'm trying to figure out the right set of classes to support, the right properties to put on all the classes, and the right set of toolbox items to have available. I want something that is both simple enough to get people started right away, and easy enough to customize that not every game looks like it came from the same designer. I've been building this designer and its components in Visual C# 2005 Express Edition. A few of you might be surprised at that, mainly because you can't use the VS SDK to build customizations for VC# Express (except for some very special cases like XNA Game Studio, but my personal projects are no exception). The fact is, a visual designer doesn't require customizations to the IDE. There's already a powerful designer infrastructure built-in to VC# Express -- the very same one used by Windows Forms! To create custom design-time experiences, you don't need to modify anything in the IDE. All you need is the right metadata on your classes, and you can take control of that infrastructure for yourself. Anyway, tonight I am trying to make sense of the mess I made with respect to navigation items. I've created an item that you can drop onto the menu and then specify what type of screen it navigates to. Selecting this menu item when the menu is running will transition to the next screen. I also gave it the option of specifying a custom background for the next screen, and for loading it on a separate thread with a load screen. The trouble is that if you use the load screen, you can't "cancel" back to the previous screen. If you don't use it, then you can. I'm having trouble figuring out what to call the properties so the resulting behavior will be obvious. Clearly, this one navigation item is too complex. The best thing to do is probably to have more than one navigation item -- say, one that pushes new screens onto the screen manager's stack (allowing back-button navigation), and another that clears the stack to start from scratch (like a gameplay or level screen). That would definitely be easier to understand, but what do I call them? Argh! The thing I like about the navigation items is that it gives you an easy way to build a complex menu system without writing any code. This screen clipping above shows how the NextScreen property has a dropdown combobox to select the type of the next screen. The combobox will list all the game screens in your project (or referenced by it). It's neat and easy, but the resulting behavior is not yet intuitive. Coming up with good names is hard. It reminds me of a quote from Knuth:
Once again, it's time for bed. May 14 Game State Management for N00bs Like MeLast week, I finished writing a design document that I'd been working on for about a month. The design review meeting seemed to go well -- at least, I think it did. There were a lot of nodding heads around the room as I presented my design, and I like to think they were nodding in approval. The alternative is that they were nodding off to sleep, but I'm going to assume that wasn't it. :-) Anyway, after so long working on the same thing, I decided to take a look at something new and different this past weekend. I installed XNA Game Studio 2.0 and VC# 2005 Express Edition on my laptop at home and started to poke around at some samples. I downloaded Nazeeh's Game State Management project template -- something that I consider to be a terrific example of what a starter kit should really be -- and created a project from it. For those of you who are not familiar with this template, I'll briefly describe it. It creates a project for a game that has a couple menu screens, and an animated loading screen to entertain people while larger screens (aka, game levels) load asynchronously in the background. It is a slick starting point for just about any game. I ran the project and put breakpoints in a few places to see how things were being invoked to get a better understanding of the code. Then I modified a few things and found it was reasonably easy to make changes and customize the screens. For those of you who don't know me, my background is in tools. I make the programs that make it easier to develop games with the XNA Framework, but I don't work on the XNA Framework libraries, and I hardly know anything about game programming. So when I looked at the sample code and saw how easy it was to customize the game state, the first thing I thought of was, "That's pretty cool -- but I can make this easier!" The funny thing about things being easy is that they are usually easy to automate. Building menus, changing properties, handling input events... Hmm, where have I seen that before? Instead of playing around with XNA Game Studio to finally learn about game programming, I decided to build a designer for Game State Management! :-) Tonight, I have a working menu designer. I used the WinFormsGraphicsDevice sample to create a design surface that actually draws the menu screen as menu items are added to it, and updates in real-time as properties are edited. To design a menu, I just need to declare a class that derives from my MenuScreen type, then double-click on the code file, and up pops the designer! I only have two types of menu items to play with at the moment -- a counter and a toggle. There is a lot of work to do before it would be suitable for general use, but I'm excited about the possibilities. The samples provided a big head-start! Here's a series of screen shots to show you what I'm talking about. Figure 1 - Shows the code required to get started (an obvious template candidate). The context menu provides a "View Designer" command to open the designer. Double-clicking the code file in Solution Explorer works just as well. Figure 2 - The initial design view. This is an XNA GraphicsDevice cleared to CornflowerBlue and rendering a menu with no items. Figure 3 - Shows the menu after editing the Title property, then adding a ToggleMenuItem and editing its label and name. Figure 4 - Back to code view to see what the designer generated. It might not look like much today, but it kept me busy for the past four evenings and was kind of fun! My goal really is to make it easier to get going right away. When I've worked through all the kinks enough to share the designer, I'll blog about how the designer works. Now it's time for bed! May 09 Game Studio and Visual Studio 2008Last December, we released XNA Game Studio 2.0 with support for Visual Studio 2005. This was a big step up from having Visual C# Express support only -- but since it was hot on the heels of the Visual Studio 2008 release, some people were still disappointed. People were excited to get VS 2008, and they were excited to get XGS 2.0 -- but they wouldn't work together! In and around Microsoft, we have a pretty big community of XGS enthusiasts. Some of them have backgrounds in tools development, including Visual Studio extensibility. Those people knew that the extensibility model for VS 2008 was completely backwards-compatible with VS 2005 (or at least it is advertised to be). So why couldn't XGS 2.0 install into VS 2008? There were inevitable questions about whether XGS 2.0 could be made to work in VS 2008. Not the naive questions about whether you could compile code against the XNA Framework -- but whether you could get the project system to install into it, use the Content Pipeline, and debug Xbox 360 games. In January, we gave it a shot and it didn't work. It turned out there was a backwards-compatibility bug in VS 2008 that prevented content projects from loading into the IDE. It was a tiny bug with a trivial fix, but one that had to be made by in Visual Studio itself. As a result of finding this bug, word got out that XGS 2.0 didn't support VS 2008 because it was blocked by a compatibility issue that will be fixed in VS 2008 SP1. When we released our CTP this week, someone asked me how we did it -- because weren't we blocked by the same bug that blocked XGS 2.0? I'd like to set the record straight on that, because we were never blocked by a compatibility issue. The incompatibility makes for a convenient excuse, but it’s not the reason we didn’t support VS 2008 in XGS 2.0. Our test team was overburdened even as it was for the XGS 2.0 release. There was absolutely no way we could have delivered support for two versions of VS, even if we’d wanted to. We knew that from the beginning, and originally JoeN and I pushed to make the one version be VS 2008. In the end, our leadership vetoed our proposal and chose VS 2005. That decision was made around April -- eight months before the release of XGS 2.0! We put our heads down and committed to the task at hand -- shipping on VS 2005. It wasn't until after VS 2008 had shipped and after XGS 2.0 had shipped that we finally looked at VS 2008. We found and identified the compatibility bug within an hour of getting the XGS 2.0 bits installed into VS 2008. So to be completely honest, if we’d had any intention of supporting VS 2008, we would have easily caught this bug before the Orcas release. Its impact on our projects was obvious, and the fix is trivial. If we’d even bothered to try our stuff in VS 2008 before it shipped, that bug would no longer exist. Letting people believe that a bug is the reason we don't support VS 2008 is like saying, “Hey, it wasn't not our fault – blame the VS team!” But in fact it is entirely our own responsibility. Whether everyone understands it or not, we decided not to support VS 2008. I feel pretty strongly now that we made the right choice -- even though I was opposed to it at the time of the decision. If you don't agree, I happen to have good news for you... We're supporting VS 2008 in XGS 3.0! The XGS 3.0 CTP was released this week to preview our addition of support for Zune, and the CTP targets VS 2008! With this release, you can develop games for Zune from either VS 2008 Standard (or higher) or VC# 2008 Express Edition! April 06 Extending VS 2005 to Improve Cross-Platform DevelopmentLast time I wrote, I had an idea to create an add-in to simplify the view of the solution when developing cross-platform games in XNA Game Studio 2.0. I spent an evening messing with automatically creating and hiding solution folders, but quickly came to the conclusion that simplifying the view was going to be harder than I thought. I've put it aside for a while, and started on something else... I wrote an add-in that detects when you add a new file to a code project, and then adds that same file to its cross-platform counterpart. Likewise, the add-in also handles renaming files. In this post, I'm going to discuss how I did it, and what its limitations are. If you don't care about the details, but you want to try the add-in, you can skip directly to the download after considering the disclaimer at the end of this post (click to see my Add-Ins). Detecting Cross-Platform ProjectsIn XGS 2.0, there is an association between projects created from the "Create Copy of Project..." command and the original. Unfortunately, we didn't expose that association to the automation model, so you can't detect it in macros or add-ins. However, project copies share the same source code. For the vast majority of games, that means cross-platform projects have the same GuidAttribute, defined in the shared AssemblyInfo.cs file. The GUID specified in this attribute is accessible to the automation model, and so it's possible to identify project pairs in a macro by enumerating all projects in a solution and looking for any with a matching assembly GUID. Handling ProjectEvents.ItemAddedOne thing that gave me trouble was adding empty folders to the project. The code I used for files worked well for folders created in the project root directory, but had strange side-effects when new folders are created under existing folders. In the end, I decided to skip empty folders altogether. There doesn't seem to be any real drawback to this, because empty folders aren't really interesting to a project in the first place. If you later add a file to the empty folder, the paired project will get the new file in the right place, including any folders. I was actually surprised at how well the handler worked! It magically handles multi-file dependent items like UserControls and Components. (Not that you will want to add those to your Xbox 360 projects, but I was still surprised they preserve the dependency!) Likewise, I was pleased to see that including an entire folder with multiple files into a project was handled correctly. Not bad for a couple hours of absent-minded coding while watching TV! Handling ProjectEvents.ItemRenamedHandling the item renamed event was a bit trickier than I first imagined. The event handler receives a reference to the item that was renamed, as well as the item's old name. To rename the item in another project, you can use the item reference to get the full path, then use the old name (not a full path) to recreate the item's old full path. Then you can search for the old item in other projects, remove it, and add the new item (re-using the item added event handler). Unlike the item added event, the item renamed event does not fire for folder items. Instead, you get the event for any file items under that folder. This is a problem, because you can't reconstruct the old path name from the information given to the event handler. In such a case, the new file name and the old file name are the same, and the old path isn't specified. To handle folder renaming as well as possible, my add-in will add all the files from under the new path to the cross-platform projects. The old items will still be there, but the files they reference no longer exist. When you later try to build the project, those files will be unresolved and will give you errors. To fix this, just delete the items from your project. Installing the Add-InYou can download and install the add-in from my XNA Game Studio 2.0\Add-Ins folder. For now there is only one add-in there, but I might eventually add more. The one described in this post is called RighteousTool.Xgs.2.0.AddIns.ProjectSync.vsi. Be advised that Visual C# 2005 Express Edition does not support add-ins or macros. You'll need to have Visual Studio 2005 in order to install it. DisclaimerThe add-in referenced by this post is provided by the author as-is and without warranty, either express or implied. Downloading, installing, and using the add-in must be done at your own risk. March 28 Dreaming of a Cross-Platform UtopiaMy wife has been sick the whole week, and this morning I woke up with watery eyes and clogged sinuses. I went to work anyway, though, because when I close my office door, I can sometimes go a whole day without talking to anybody. So I went to work and started to work on getting the cross-platform project command to show up in the right place for Zune projects. I could feel the pressure building in my head. My ears popped, and I started to feel like I was underwater. This feeling was very distracting, and my mind wandered... It was then I started to think about Solution Folders (yes, even my daydreams are about Visual Studio extensibility)! Before I describe my daydream, let me enlighten you about Solution Folders and what they mean to cross-platform development in XNA Game Studio... Solution Folders provide a convenient way to organize a solution with many projects. If you have a game with several library projects, then going cross-platform doesn't add much overhead in the way of files (one extra file per project), but it can sure make a mess of the Solution Explorer! That's where Solution Folders can shine. Let's say I have a medium-sized project, with separate libraries for physics, animation, and some kind of high-level game engine. Throw in a couple custom processors, and it might look something like this: If I run the Create Copy of Project for Xbox 360 command on EpiphanyGame, I'll end up with this: Woah! Ten projects is starting to look complicated! As you can see, all the new projects are named "Xbox 360 Copy of ...". The prefix has caused all the Xbox 360 projects to be sorted together at the bottom, which is somewhat convenient. But if I wanted to rename them (which I normally do), then I'd lose that organization... Bleah! If I don't keep these projects collapsed, how am I going to find anything?! :-P One answer is to create some Solution Folders. If I create one for Windows, one for Xbox 360, and one for Content Pipeline, then I can organize my solution thusly... The solution folders let me organize the projects by platform, and it's easy to collapse them so the projects for the platform I'm not working on don't get in the way. Okay, now back to my clogged-sinus-induced daydream... I started to wonder what it would be like if these solution folders were created automatically. Maybe the folder for the platform you're not working on could be collapsed automatically, too. I thought that might be neat. Not too elegant, but maybe it would work for some people. I mean, if the cross-platform copies of the projects could automatically stay in sync with the originals, then you would hardly ever need to switch from one to another. Then I wondered if they could be hidden altogether. I found the answer here. That's cool, I thought, I wonder how we could use that...? At some point, I realized I'd been staring at the same syntax error in the code window for thirty minutes, and I'd written only about four lines of new code all morning. So I went home. After I got home, I turned on my trusty laptop and spent ten minutes writing a VS macro. The macro looked at the active solution configuration's platform name, and hid any solution folders that didn't match. The first attempt showed a big flaw in that logic, since the default platform name is "Mixed Platforms". I added a special case for that. Then I added another special case to ignore any solution folder that wasn't a valid platform name. For a mere half-hour of VB programming, it worked pretty good. I searched around to find an event to trigger the macro when the active configuration changed, but I wasn't able to find one. Unfortunately, that means I'll need to write an add-in to make it work. When I feel a bit better, I think I'll do that! |
|
|