Porting to Android - Solution and Project Structure
Porting your XNA game to MonoGame involves a lot of new learning. One of the many bewildering things to cope with is how to organise the files and directories, your solutions and projects, in Visual Studio, such that porting will be made easier. If your XNA game is 50k lines of code sprawling across 16 projects, it is hard to know where to start. Should you just copy everything to a separate "Android" directory and just start afresh? Well that's possible but not ideal, because it is a huge benefit being able to keep a Windows, a Windows Phone and an Android build all using the same source files. It makes checking things and debugging just so much easier. We've found during the porting exercise that we've uncovered bugs in the original Windows Phone build, and if we hadn't kept the builds sharing the same source that'd have been really hard to find. So, here are some ideas about project structure.
How can I organise my projects and files to make porting easier?
Let's start with some background, a little peek under the hood of Xamarin and MonoGame. This slide is based on one I found in a MonoGame presentation. The white arrows 1 and 2 represent our Original and Ported Solutions:
The above is pretty straightforward. In the land of our Android port we will have the same game source code (that's the "Your Game" block in blue) as we use today for our original game. The difference is that we'll be using MonoGame instead of XNA and beneath MonoGame there will be some stuff we hope we won't ever have to understand in any detail.
So, now if we dig a bit deeper, let's look at how our solutions and projects might be arranged:
The above slide shows Visual Studio (note this can be VS Express) at the top, where we have our Original Solution file holding the Windows and Window Phone builds (and Xbox too if you so desire). You can see there are multiple projects inside the solution, but no mention of Android. That's because for the Android solution, the Ported Solution, we'll be using Xamarin Studio (to avoid having to buy Visual Studio Professional), and Xamarin Studio is shown in the bottom half of the above slide. Now you can see the Ported Solution doesn't actually have that much in it! It just has links to the files in the Original Solution. For a naming convention I just appended an "A" to the end of any solution, project or file that was Android specific (although I've seen others use a ".Android" addition which is probably better).
Getting to the above state took a huge amount of faffing around, the details of which I will save for another day, but the broad principle is: always keep your original solution and the ported solution sharing as much as is possible. You'll thank me later when you can switch back to your Windows or Windows Phone build and see how it compares with your Android build running the same code!
Now, of course the reality is that you cannot share absolutely everything. For example there are Android-specific events you're game might want to listen to, and of course that code won't compile in a Windows build. There are a few techniques we can use to cope with these cases without making our code base into a big mess.
Handling cases where you cannot share the exact same code across solutions
The simplest way to cope with solution-specific coding is to use the "#if" pre-processor directive. Here is an example used to control the name of the ContentManager root directory:
public const string RootDirectoryMain = "ContentPhone";
public const string RootDirectoryMain = "GPMContentPhone";
So the above example is easy and looks innocent enough, and "#if" is the ideal way to cope with these little changes. But you definitely don't want too many of these "#if" around or your code will become unreadable and unmaintainable.
Another option is to have completely separate .cs code files that are stored in the Ported Solution only, so they'd be visible only from within Xamarin Studio. Now that is appropriate in some cases perhaps, like the Activity.cs class that you need as a wrapper to allow your game to be launched by the Android OS, but I would avoid going too far down that route or we start to stray away from our goal of shared code.
There is another option you can use in cases where there are extreme differences in code between solutions. Let's take an example. Let's say we have implemented an Achievement Manager for our Windows Phone games that speaks to some online servers. For the Android port we've decided to keep the achievements awarded locally, so the code for these two classes will be very, very different. Going further you can imagine an iOS port that might want to speak to OpenFeint or some other service, and again the code for that will be very different.
Now you could have a single AchievementManager class to handle all these cases, and "#if" you're way to a working solution, but I wouldn't want to be the person trying to fix bugs in that later. It would be very hard to read. So what can we do in these situations? Interfaces to the rescue! Interfaces are ideal here because they force encapsulation of any platform-specific code and your main game is blissfully unaware of what platform it is running on for achievements. All your main game needs to do is call an AwardAchievement method!
So the above slide is showing how your core game only ever declares objects as being of type "IAchievementManager", and it does not care how that Achievement Manager is implemented. Your core game just needs to know it can make calls to AwardAchievement() and that is that.
So at most you should need a single "'#if" where you choose to declare your Achievement Manager (a service perhaps) as being of type AchievementManagerXBL, AchievementManagerLocal etc.