Development Environment - An Overview

en·tro·py - Inevitable and steady deterioration of a system or society

It's a fact. When you are a developer, entropy is your enemy. The more time that elapses with your personal project, the more chaos will creep into it. It is inevitable (by definition, nonetheless). The best we can do is to limit the impact of the chaos that will creep into the project. In this respect, the one thing that you don't want to do is to get off on the wrong foot. It is extremely important to build a solid foundation on which you can build your entire house. In the development world, it all starts with your development environment. And the development environment all starts with your directory structure.

Files, Directories, and Structure, Oh My!

The directory structure is the first line of defense against the axis of evil that is entropy. The directory structure outlined below is just my personal preference (and I must admit, it is a directory layout that is quite similar to the one that I first learned about from Mike McShaffery and the book Game Coding Complete). In hindsight, I have found that it allows me to keep my project in tip-top shape a lot easier than usual. If you are at all familiar with development in the *nix world, the following structure just might look familiar. Included are some typical files you are likely to find in a project.

[ProjectName]
|
|........[branches]
|
|........[tags]

|........[trunk]
|          |
|..........|.........[_temp]
|          |
|..........|.........[bin]
|          |            |
|..........|............|.........DefaultProjectName.exe
|          |
|..........|.........[doc]
|          |            |
|..........|............|.........GameEngine.uml
|          |
|..........|.........[src]
|          |            |
|..........|............|........[DefaultProjectName]
|          |            |                |
|..........|............|................|.........DefaultProjectName.proj
|          |            |
|..........|............|........[SecondProjectName]
|          |            |                |
|..........|............|................|.........SecondProjectName.proj
|          |            |
|..........|............|........ProjectName.sln
|          |
|..........|.........[test]
|          |
|..........|.........[tools]
|          |
|..........|.........default.build

Let's go over these folders really quick-like (nice and painless, right?). We have (if I am indeed reading this correctly (which I like to think I am since I actually wrote this in the first place :P)) a couple of directories located under our root ProjectName directory. These directories are branches, tags, and trunk, respectively. The main reason that these directories exist is to make tagging, and branching/mergin much easier with our source control system. The idea is that if we branch at revision 1123 let's say, then the entire trunk at revision 1123 would be copied to branches\revision1123. This branch would also be under source control.  

As you might have gathered from the branching description above, the heart of our project is the trunk directory. The trunk directory contains the most current, up-to-date sources and is where most development will take place (except if you are working on a specific branch of course). In the trunk directory, we have a couple common directories: bin, doc, src, test, and tools. The bin folder is where release builds of our project are placed. The doc folder is where all our documentation and related documents are placed. The src folder is, well, where all source code is placed (not exactly rocket science, huh?). The test folder is where all debug builds are placed. And finally, the tools directory contains the executables of any tools that are needed in order to build the project. Let me riff on the idea of a tools directory for a while, since it isn't exactly a conventional idea.

The idea of a tools directory is rather simple. Hopefully, you will see like I did that the pros of source controlling your dependant tools' executables far outweigh the cons. The concept of being able to have the right version of tools required to build an application is an important one. To understand why, you have to look at the long-term development of your project. As an example, let's say we were developing an enterprise application and also supporting that application for a number of years. Let's say that your biggest customer uncovered an extremely bad bug in your system from the version that you sold to them (which happens to be a year and a half old, and nearly 1500 revisions ago in the repository).

We all know that we don't live in a perfect world. When starting a new project, I always like to have the latest and greatest tools available. But if I'm not storing these tools at a project level, then there is a good chance that the latest version of a tool will not be compatible 100% with revision from a year and a half ago. To remedy this situation, if we include all depedant tools' executables with that project, then when we check out that early revision, it will also check out all the tools that were used at that exact time to build that exact project. So, you could just check out the revision from a year and a half ago, and have all the tools necessary to build that specific revision without having to go digging around on computers or the internet to find a compatible version of the software from a year and a half earlier (which some of us know is a pain to do). I myself went back and forth on this concept for quite a while. But really, it makes perfect sense to source your tools also. Really, there's no reason not too. And if you like to support your software, than you will save yourselves many headaches if you find yourself in the situation of having to support software that is several versions old. 

And that's basically it. There isn't much to this directory structure. The key isn't a complicated directory structure. The key is a directory structure that is easy to maintain, and is well constructed.

What's the impact of this directory structure on Visual Studio, though? Obviously this isn't the directory structure that Visual Studio would like to use out of the box. So how do we get Visual Studio to do our bidding? Well, if you want to manipulate someone, the best way to do so is to not try. So, let's not try to wrangle Visual Studio into this structure. What you'll find is that it is more trouble than it is worth to get Visual Studio to play by your rules. You'll find that stuff like Intellisense won't work if you break the way Visual Studio expects things to work. My solution? You might notice that I didn't discuss the _temp folder above. Well, there is a reason for that. I actually have Visual Studio build the projects into a _temp\VS directory. The way this is done is by specifying a directory of "..\..\_temp\VS\" in each of your projects. That way Intellisense is happy since it knows where to find the files, and we are not building binaries into our source directory (which is a pet peeve of mine, especially for source control reasons). The drawback of how Visual Studio works with VB and C# projects though is that you can't change the intermediate directory. In other words, your "obj" directory will always exist under your project directories. So, I simply tell source control to ignore these directories.

So if we are going to let Visual Studio run loose without supervision on a school night, why do we have our own directory structure for builds? It is important to realize that when working on a project, Visual Studio is only one piece of the puzzle. A mistake that a number of hobbyist developers make is to treat their IDE as if it is the only tool they have in their tackle box. You know what they say, if the only tool you have is hammer, everything starts looking like a nail. Since Visual Studio is only one piece of the puzzle, logic dictates that there exists other pieces in order to make the puzzle complete. Let's start discussing some of those other pieces. The next step along the yellow brick road is our build environment.

NAnt

A game is a complicated program, this is no secret. There are scripts, art assets, game engine modules, configuration files, among other things. A game is not just an executable. I'm sure you can configure Visual Studio with all sorts of pre and post build commands in order to properly bring all the game's assets together in one place. But once you do that, you find yourself seeing everything as a nail. Use the right tool for the right job. And when it comes to building a development project, I have found that one of the best available tools in .NET is NAnt. For us hobbyist developers, the best news is that NAnt is not only free, it is open source.

I will not go into the details of developing an NAnt script. If you are new to NAnt, please checkout http://nant.sourceforge.net/ to get up to speed. One of the many things I love about NAnt is that an NAnt build script is written in XML. Hence, if you are new to NAnt, it shouldn't take you to long to get up to speed. So, now that you are familiar with NAnt (or should be (like I said before, checkout the link above (if you don't, I'm not responsible for your machine blowing up when you try to build Pong v1.0))), let's discuss what we need for our build script.

You can do many things with NAnt. You can run unit tests, zip up your builds, shoot off emails, post to FTP servers, etc. However, we will keep our build script relatively simple. We will have three basic tasks that our build script can perform. We will be able to build our project, clean our project (delete all build directories to ensure that the entire project is built), and run various integration tools over our built projects (like NUnit, FxCop, NCover, etc.). This will be able to be done for both debug and release builds. Before writing the script, it is best to ask yourself how you want to run the script (the interface of the script). The verbose format that I personally use for the commands is as follows:

-- DEBUG

-- build
> NAnt build

-- clean
> NAnt clean

-- integrate (get latest sources from source control, build, and run various integration tools over builds)
> NAnt integrate

-- RELEASE

-- build
> NAnt -D:debug=false build

-- clean
> NAnt -D:debug=false clean

-- integrate (get latest sources from source control, build, and run various integration tools over builds)
> NAnt -D:debug=false integrate

Debug will be the default type for any target, and "build" will be the default target. So, for debug build, you can just run "NAnt". Since I'm not going into detail about how to build an NAnt script, I will simply post a sample build script that adheres to the interface explained above.

<?xml version="1.0" ?>
<project name="Dynamo" default="build">

         <!-- default configuration -->
         <property name="debug" value="true" unless="${property::exists('debug')}" />
         <property name="project.baseDir" value="${nant.project.basedir}\src" />
         <property name="build.config" value="debug" />
         <property name="build.baseDir" value="" />
         <property name="build.assetsDir" value="" />
         <property name="tools.FxCop.params" value="" />

         <!-- common paths -->
         <property name="build.tempDir" value="${nant.project.basedir}\_temp" />
         <property name="build.testingDir" value="${nant.project.basedir}\_unitTests" />
         <property name="build.logDir" value="${nant.project.basedir}\log" />
         <property name="tools.FxCop"
                  value="${nant.project.basedir}\src\tools\FXCop\fxcopcmd.exe" />
         <property name="tools.NUnit"
                  value="${nant.project.basedir}\src\tools\NUnit\nunit-console.exe" />

         <target name="init" unless="${target::has-executed('init')}">
                  <call target="debug" if="${debug}" />
                  <call target="release" unless= "${debug}" />< BR>
                  <property name="build.assetsDir" value="${build.baseDir}\data" />
         </target>

         <target name="debug">
                  <property name="build.config" value="debug" />
                  <property name="build.baseDir" value="${nant.project.basedir}\test" />
                  <property name="tools.FxCop.params"
                           value="/f:test\Dynamo.dll
                                    /f:test\IncredibleJourney.exe" />
         </target>

         <target name="release">
                  <property name="build.config" value="release" />
                  <property name="build.baseDir" value="${nant.project.basedir}\bin" />
                  <property name="tools.FxCop.params"
                           value="/f:bin\Dynamo.dll
                                    /f:bin\IncredibleJourney.exe" />
         </target>

         <target name="clean" depends="init">
                  <delete dir="${build.tempDir}" failonerror="false" />
                  <delete dir="${build.baseDir}" failonerror="false" />
         </target>

         <target name="rebuild" depends="init">
                  <call target="clean" />
                  <call target="build" />
         </target>

         <target name="build" depends="init">

                  <echo>Preparing Build Directories</echo>
                  <mkdir dir="${build.tempDir}" />
                  <mkdir dir="${build.baseDir}" />
                  <mkdir dir="${build.assetsDir}" />
                           
                  <echo>Building Projects</echo>
                  <solution configuration="${build.config}" outputdir="${build.tempDir}">
                           <projects>
                                    <include name="src\Dynamo\Dynamo.csproj" />
                           </projects>
                  </solution>
                  <solution configuration="${build.config}" outputdir="${build.tempDir}">
                           <projects>
                                    <include name="src\IncredibleJourney\IncredibleJourney.csproj" />
                           </projects>
                           <referenceprojects>
                                    <include name="src\Dynamo\Dynamo.csproj" />
                           </referenceprojects>
                  </solution>

                  <echo>Distributing Assemblies</echo>
                  <copy todir="${build.baseDir}">
                           <fileset basedir="${build.tempDir}">
                                    <include name="Dynamo.*"/>
                                    <include name="IncredibleJourney.*"/>
                           </fileset>
                  </copy>
                  <copy todir="${build.assetsDir}">
                           <fileset basedir="${nant.project.basedir}\assets">
                                    <include name="*.*"/>
                           </fileset>
                  </copy>

                  <echo>Cleaning Temporary Directories</echo>
                  <delete dir="${build.tempDir}" failonerror="false" />
         </target>

         <target name="integrate" depends="init">

                  <echo>Get latest sources and build projects</echo>
                  <svn-update uri="svn://BuildMachine/Dynamo/trunk/"
                           destination="${project.baseDir}" />
                  <call target="build" />

                  <echo>Preparing Integration Directories</echo>
                  <mkdir dir="${build.testingDir}" />
                  <mkdir dir="${build.logDir}" />

                  <echo>Distributing Assemblies</echo>
                  <copy todir="${build.testingDir}">
                           <fileset basedir="${build.baseDir}">
                                    <include name="Dynamo.*"/>
                           </fileset>
                  </copy>

                  <echo>Building Unit Tests</echo>
                  <solution configuration="${build.config}" outputdir="${build.testingDir}">
                           <projects>
                                    <include name="src\DynamoTests\DynamoTests.csproj" />
                           </projects>
                           <referenceprojects>
                                    <include name="src\Dynamo\Dynamo.csproj" />
                           </referenceprojects>
                  </solution>

                  <echo>Generating Reports</echo>
                  <exec program="${tools.FxCop}"
                           commandline="${tools.FxCop.params} /o:log\fxcop-results.xml"
                           failonerror="false"/>
                  <exec program="${tools.NUnit}" workingdir="${build.testingDir}"
                           commandline="DynamoTests.dll /xml:../log/nunit-results.xml /nologo"/>
         
                  <echo>Cleaning Temporary Directories</echo>
                  <delete dir="${build.testingDir}" failonerror="false" />
         </target>
</project>

And there you have the build script :D (once again, I encourage to read up all you can at NAnt's site, especially if any of the above intimidates you).

Source Control

I feel it is vitally important for every developer to use some sort of source control. It doesn't matter if your are developing a project by yourself. Source control isn't necessarily for projects that have multiple developers. Think of source control as your safety net. I'm sure there are many of us out there that have been working on a project when we decide to make a change. You make the change and then your application doesn't work. "Crap! I broke it." Well, if you are working with source control, you would simply roll back your changes and not have to worry about how you are going to "undo" what you just did. For this to be successful though, you need to remember to check in your files regularly (just like saving a file regularly to not lose changes you've made).

There are many flavors of source control out there. There is CVS, Subversion, Source Safe, Perforce, Alien Brain, and Vault, just to name a few. Now, I can't tell you which one to use. The one piece of advice that I will give to you (and some of this comes from prior experience): don't use Source Safe! I know that it's perhaps the easiest to use as first since it is fully integrated with Visual Studio, but it will most likely come back to bite you later. Hopefully in the future I can come back and correct this to recommend Source Safe, but I don't see that happening any time soon (even though this article might be biased, I think it would be good for you to read it if you're currently looking at or using Source Safe). If Microsoft doesn't eat their own dog food, how can we be expected to do so?

So, what should we use? You really have to ask yourself what your requirements are for a source control system. For our project, requirements are pretty simple: 1) good integration into Windows 2) free of charge (for multiple users) and 3) support a test-driven and refactoring development environment (and hence, support file renaming). Because of these requirements, we are able to narrow our options down to CVS and Subversion. How? Well, just off the top of my mind, Alien Brain and Perforce aren't free, Vault is only free for a single user (not for multiple users), and of course, Source Safe is out of the question. CVS is used widely as a source control system and is quite mature. However, the biggest problem for me is that I like to refactor code. Often, that might include having to rename a file, and to make a long story short, it is not easy to rename files with CVS while keeping the revision history intact (although I'm sure there are roundabout ways of doing it (I don't want a roundabout way though :))). So, in my opinion, CVS is out of the question due to my coding practices.

That leaves us with Subversion. Subversion is a relatively new product, so support is good and the support continues to grow as its popularity grows. Subversion natively supports file renaming (see the Subversion homepage for more details on why Subversion is better than CVS) and there are a good number of integration tools we have to make our job even easier. Off the top of my head, you will find tools like TortoiseSVN (Windows Shell integration to support working with Subversion from Windows Explorer), AnkhSVN (working with Subversion from Visual Studio), and SVNService (running Subversion as a Windows Service). Not only that, but it is popular enough now that if you download the NAnt Contrib tasks (which I highly recommend you do if you are working with NAnt), there are a couple tasks specific to working with Subversion. Nice integration, I say.

If you are a developer who doesn't often rename files, you might be interested in just using CVS. If so, I encourage you to visit the Subversion homepage for more details on why Subversion is better than CVS. Of course, your situation might be different from mine. For all I know, there are specific reasons why you like CVS over Subversion. To all his own. In my shoes, though, for what I need my source control system to do and to support, Subversion is the perfect fit for me.

In Closing

I know you probably have many un-answered questions, but this is just a small step on the road to a fully functional game (remember, "Baby Steps!"). In this article, we learned the basics of our different tools (development, building, and source control). Please don't skip any of these. Take it from me, your life as a developer will be much happier if you go the distance and don't take any short cuts.

So, where are we going from here? In the next article, you will start making real progress with our game. We will actually setup the environment for our project and be ready to get to coding when we are done. We have a long road ahead, so buckle up and enjoy the ride. Some of this may seem tedious at first, but you will be extremely satisfied when you are done.

Until then, take it easy! If you have any questions, feel free to contact me :D.