Wildcard content using MSBuild

Originally posted to Shawn Hargreaves Blog on MSDN, Wednesday, June 6, 2007

One of my favorite things about the C# project system in Visual Studio is the way it is implemented on top of MSBuild. Visual Studio provides a nice user interface for editing your project, but when you hit F5 it just calls through into MSBuild to do the work of building everything. For those of you brave enough to edit XML by hand, this opens up the opportunity to tweak your projects in ways not directly supported by the Visual Studio interface. Similar in spirit to adding custom build actions, but vastly more powerful and flexible.

One easy tweak is to reference content files using wildcards, so you can say things like "build all the bitmaps in this directory", rather than having to explicitly add each one to the project.

An aside: I actually kind of like having all my content in the project, because this gives me one central place to see everything I'm using in my game. So I probably wouldn't do this myself. But I might if I had a lot of textures. I'm rambling. The point is that you have the flexibility to do this if you want.

The first step is to close Visual Studio and load your .csproj into an XML editor. Notepad will do if you don't have anything better. Find these lines toward the end of the file:

  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
  <Import Project="$(MSBuildExtensionsPath)\Microsoft\XNA\Game Studio Express\v1.0\Microsoft.Xna.ContentPipeline.targets" />
  <Import Project="$(MSBuildExtensionsPath)\Microsoft\XNA\Game Studio Express\v1.0\Microsoft.Xna.Common.targets" />

And insert another immediately after them:

  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
  <Import Project="$(MSBuildExtensionsPath)\Microsoft\XNA\Game Studio Express\v1.0\Microsoft.Xna.ContentPipeline.targets" />
  <Import Project="$(MSBuildExtensionsPath)\Microsoft\XNA\Game Studio Express\v1.0\Microsoft.Xna.Common.targets" />
  <Import Project="includes.proj" />

Now put this includes.proj file in the same directory as your .csproj:

  <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <ItemGroup>
      <WildcardContent Include="Models/*.fbx">
        <XNAUseContentPipeline>true</XNAUseContentPipeline>
        <Importer>FbxImporter</Importer>
        <Processor>ModelProcessor</Processor>
      </WildcardContent>
    </ItemGroup>

    <Target Name="BeforeBuild">
      <CreateItem Include="@(WildcardContent)" AdditionalMetadata="Name=%(FileName)">
        <Output TaskParameter="Include" ItemName="Content" />
      </CreateItem>
    </Target>
  </Project>

And there you have it! This will automatically build all the .fbx files from the Models directory, using the FbxImporter and ModelProcessor.

You can add more WildcardContent elements inside the ItemGroup if you want to include different types of file, or specify a different importer or processor. For instance you could use one WildcardContent to build all the textures in one directory using the SpriteTextureProcessor, and another to build textures from a second directory using the ModelTextureProcessor.

The first time you load this edited project you will get a warning saying it has been modified. You want to load the project normally, not just for browsing. You should only see this warning once.

The CreateItem task in the BeforeBuild target fills in the asset name metadata, using the filename with the extension stripped off (which is also the default behavior in our regular Visual Studio interface). Change the AdditionalMetadata attribute if you want different content names.

At this point the astute reader may be wondering why I put my wildcard XML in an external include file rather than adding it directly to the main .csproj. This is to prevent Visual Studio messing things up. It doesn't properly understand wildcards, and if it sees any it will just expand them out, replacing them with a list of all the individual files, after which any new files added in that directory will not be picked up. Tucking my wildcards away in an include file stops Visual Studio from noticing and messing with them.

Blog index   -   Back to my homepage