| Stephen's profileBadCorporateLogoPhotosBlogLists | Help |
BadCorporateLogoGood Workmen Never Quarrel with their Tools |
|||||||||||||||||||||||||||||||||||||||||||||||
|
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. |
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|