Lee Richardson on 2020-10-07 #monogame #gamedev #fsharp #android
A game in F# on Android? Truly we are living in the future.
Project set up
We want 3 projects.
- Android
- Desktop
- Shared code
Android
Let's start with the Android. I won't go into detail on the parts that did work fine, so you can follow the Getting Started docs for this part. If you're using the .NET Core template its name is mgandroid
. Call it something like "Game.Android". This will be a C# project because that's the only available Android template and it doesn't matter because we won't be writing our game code here. You should now have a project which runs the cornflower blue screen on your phone or emulator.
Desktop
Now we want to do the same for a desktop GL project so we can develop without having to deploy to the phone every time. There is an F# template for this, but it is not on NuGet as far as I can see at the time of writing. No problem, we can just go to the repo and get it. Either clone it or download the zip. Chuck that alongside our Android project and rename it something like "Game.Desktop". You could also install it as a dotnet
template with dotnet new -i <template-dir>
. Ok, you should be able to run this straight away and see the cornflower blue as well.
Shared
Now we have two totally separate projects with separate Game implementations and one in C#, one in F#. We want our Game class and everything else possible to be shared between them.
For our shared project we can use the MonoGame template in a similar fashion to how we made the desktop one, or we can just make a new .NET Standard project with dotnet new classlib -lang "f#" --framework netstandard2.0
. If you make your own you need to reference the MonoGame.Framework.DesktopGL
NuGet package and add <PrivateAssets>All</PrivateAssets>
to the fsproj as per the template. This is important as it stops the DesktopGL from bleeding out into the projects which reference it. Our Android project has its own version of MonoGame and they would clash. Then you get weird errors. The refernce to AndroidGameActivity
suddenly could not be found in Activity1.cs
. I was scratching my head for some time about this because I forgot the template existed and it was not obvious to put that project setting in from the completely unrelated error.
We want a solution to tie them together so back in the root dir run dotnet sln new
. Then for each project dotnet sln add <project-dir/project-file>
. Now our directory structure should look something like this:
- Game
- Game.sln
- Game.Android/
- Game.Android.csproj
- Game1.cs
- ...
- Game.Desktop/
- Game.Desktop.fsproj
- Game1.fs
- ...
- Game.Shared/
- Game.Shared.fsproj
- ...
Connecting it up
Righty ho. Now move Game1.fs
to Game.Shared, fix the namespace, delete Game1.cs
from Game.Android, reference Game.Shared from both Game.Android and Game.Desktop. You are probably getting missing references to the Game
class now, so let your IDE sort it out, or just open the Game.Shared namespace from Game.Android/Activity1.cs
and Game.Desktop/Program.fs
and boom, we can start writing our game with nary a care about platform... except for one thing. Files.
File loading
I found it all a bit confusing with the content pipeline vs loading raw files and Android assets on top of that, so here I'll try to clear this up for you.
What does the content pipeline do and do you need it?
You don't need it, but you should use it for media. It preprocesses your files so the device you're running it on doesn't have to do it on every launch. This usually doesn't really matter for desktop, but can do for phones or weaker devices or anything with specific requirements.
Loading content platform agnostically
So each platform needs it's own Content.mgcb
set for that platform. They should be set up for you by the templates. When you import a file using the mgcb-editor
(which you can find the details of in the MonoGame Getting Start guide) it gives you the option of linking to the asset file. So you can keep your assets in a separate directory (maybe your shared project) instead of having a copy of every asset for each platform.
There's one extra step for the Android project. You need to build the project once, triggering the content pipeline, then add the newly built .xnb
files to the project under the Content
dir and set their build actions to AndroidAsset
either in Visual Studio file properties or editing the csproj
manually.
<ItemGroup>
<AndroidAsset Include="Content\image.xnb" />
...
Now loading an image is as simple as this.Content.Load "image"
from your Game
class on any platform.
What about other file types?
The content pipeline is designed for processing files for the platform, not just serving files. So if you want to load a text file or something, your options are to either load it manually or write your own content importer which is beyond the scope of this post. Here's the template for going that route though.
Android has lots to care about with files. Permissions, multiple locations, directory constants strewn around. We just want the simple job of loading files that we packaged with the project. Normally you'd do something like pass the ApplicationContext
from the Activity
class to the Game
class in Activity1.cs
when we create the Game
, then on Game.LoadContent
we'd get the assets with androidContext.Open
. But that means the desktop can't access the files unless we write something to check what platform we're on. We're lazy and want to write as little platform specific code as possible. MonoGame knows how to handle files because it must have just done it to load the pipeline content. So we can ask it to do the lifting for us. For this we use the TitleContainer.OpenStream
static method. "Title" referring to the game title as XNA dubbed it.
From our Game
class:
use stream = TitleContainer.OpenStream "names.txt"
use streamReader = new StreamReader (stream)
let names = streamReader.ReadToEnd ()
Remember to set the file's build action to AndroidAsset
too.
Screen resolution
I won't go into specifics, but if you want your game to run on desktop and mobile then you'll need to care about changing resolutions and aspect ratios. Take a look at MonoGame.Extended's camera and a brief explanation of the viewport adapters here.
Fin
There you have it, a MonoGame project ready to deploy to Android and the Desktop in F#. Lovely. Send me an email if you run into any problems with this.