Let’s create a mobile game with Xamarin and OpenGL – Part 1 (Setup)
In this blog series I want to talk about a matter of heart. Since I was a child I always wanted to create my own computer game. I started with C++ and bought a book about how to create a game with DirectX. After a lot of time I managed to create my own games. But it was very complicated and took a lot of time. Four years ago I started developing a mobile game called “Mouse revenge”. Since I wanted it to become a mobile game I chose C# and Xamarin. In the beginning I was really struggling to find a good tutorial on how to create a platform independent game with Xamarin. So I tried a lot myself and finally found a structure that I could use to write only little platform dependent code and have most part of the game engine separated. After experiencing some bugs on specific devices I managed to have a stable version with zero crashes since months. I think it’s the right time to share my knowledge to everyone who wants to create his own mobile game with a good programming language.
In this first part we will have a look at setting up a basic project that will setup everything we need for using OpenGL on different platforms. To keep it simple we will start with Android first. Eventually our game will also run iOS and we’ll implement as little platform specific code as possible.
Creating the Android project
Open Visual Studio and create a new project. Look for the “Android App (Xamarin)” project template and click “Next”.
Pick the project name “MyGame.Android” and the solution name “MyGame”.
Click “Next” and on the next screen choose “Blank App” and the latest Android version your phone supports. Confirm by clicking “OK”.
A new solution has been created with one project that only contains a few files. This project will contain all the Android specific code.
Creating the engine
Now we will add another Android specific project. Right click on your solution and choose “Add > New project…”. Look for the project template “Android class Library (Xamarin)” and click “Next”.
Enter the project name “Engine.Android” and click next.
This class library will contain all the Android specific code that we need for our game engine. It will contain an AndroidGameView implemention and in the future we can add implementations for playing audio or access the file system.
The next step is to add the OpenTK library. OpenTK is wrapper for C# that allows us to use OpenGL. Right click on your project “Engine.Android” and choose “Add > Reference”. Then go to “Assemblies” and choose “OpenTK” version 1.0.0.0. Confirm by clicking “OK”.
Repeat this step and do the same for the “MyGame” Android project that we created initially.
Now we can add our first class. Create a new file “GameView.cs” in the “Engine.Android” project and use the code below:
using Android.Util;
using Android.Views;
using System;
using OpenTK.Platform.Android;
using Android.Content;
using OpenTK;
using OpenTK.Graphics;
using Android.Runtime;
namespace Engine.Android
{
public class GameView : AndroidGameView
{
public GameView(Context context, IAttributeSet attrs)
: base(context, attrs)
{
}
public GameView(IntPtr handle, JniHandleOwnership transfer)
: base(handle, transfer)
{
}
protected override void CreateFrameBuffer()
{
ContextRenderingApi = GLVersion.ES3;
string tag = "Engine";
try
{
Log.Verbose(tag, "Loading with high quality settings");
GraphicsMode = new GraphicsMode(new ColorFormat(32), 24, 0, 0);
base.CreateFrameBuffer();
return;
}
catch (Exception ex)
{
Log.Verbose(tag, "{0}", ex);
}
try
{
Log.Verbose(tag, "Loading with default settings");
base.CreateFrameBuffer();
return;
}
catch (Exception ex)
{
Log.Verbose(tag, "{0}", ex);
}
try
{
Log.Verbose(tag, "Loading with custom Android settings (low mode)");
GraphicsMode = new AndroidGraphicsMode(16, 0, 0, 0, 0, false);
base.CreateFrameBuffer();
return;
}
catch (Exception ex)
{
Log.Verbose(tag, "{0}", ex);
}
try
{
Log.Verbose(tag, "Loading with no Android settings");
GraphicsMode = new AndroidGraphicsMode(0, 4, 0, 0, 0, false);
base.CreateFrameBuffer();
return;
}
catch (Exception ex)
{
Log.Verbose(tag, "{0}", ex);
}
throw new Exception("Can't load OpenGL");
}
}
}
This class is an implementation of the class “AndroidGameView”. Basically this will try to create a framebuffer with 32 Bit colors. If this will fail it will try it with lower settings but all new phones should support 32 Bit colors. This view will be used to display our content that we render with OpenGL. It can be directly embedded in the layout XML of our Android activity. Also you can see the “CreateFrameBuffer” is implemented with some custom code.
Now we have to embed our GameView in the layout. Expand the “MyGame” android project and open the file “Resources > layout > activity_main.xml”. Use the following content for this file:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/linearLayout2"
android:minWidth="25px"
android:minHeight="25px"
android:saveEnabled="false">
<Engine.Android.GameView
android:id="@+id/openglview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:saveEnabled="false"/>
</LinearLayout>
We create a simple linear layout as a container. Then we use the namespace of our “GameView” class “Engine.Android.GameView” directly as an xml tag. This is where our rendered content will be displayed. Also we give this element an id “openglview” which we will need later.
We stay in the “MyGame” project and open the file “MainActity.cs”. This class is on of the first access points of our Android application. Use the following code for this class:
using Android.App;
using Android.OS;
using Android.Views;
using AndroidX.AppCompat.App;
using Engine.Android;
namespace MyGame.Android
{
[Activity(Label = "@string/app_name", Theme = "@style/AppTheme", MainLauncher = true)]
public class MainActivity : AppCompatActivity
{
private GameView view;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
RequestWindowFeature(WindowFeatures.NoTitle);
// Hide title bar
SupportActionBar.Hide();
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.activity_main);
// Pass information from this activity to the view
view = (GameView)FindViewById(Resource.Id.openglview);
view.Run(60); // Run with 60 updates per second
}
}
}
Next to some cosmetic stuff, like hiding the title bar, we also access our “GameView” class that we added to the layout. We can use the id we added previously to find it. Then we call “view.Run()” which will basically start the rendering process of our “GameView” class that is provided by the “AndroidGameView” base class.
At this point you can already run the application. Plug in your phone and hit F5. The app will start with a nice Xamarin logo and then… We get a black screen!
The reason for this is, that we are not rendering anything yet. Our engine has no heart, it will only set the color mode and that’s it.
Let’s change that by adding another project to the solution. This time it will be a “Shared project”.
Give it the name “Engine.Shared” and click “OK”.
A shared project is useful when developing over multiple platforms. It can be included by our Android project but also by future iOS project. In this case we need it to use different OpenTK implementations for different platforms. The shared project will use the OpenTK implementation of the target project. This because all the method names, namespaces etc. are identical in the OpenTK implementations for Android, iOS etc.
Add a new file “GameController.cs” to this new project and use the following code:
using OpenTK.Graphics.ES30;
namespace Engine.Shared
{
public class GameController
{
public bool IsLoaded { get; set; } = false;
public void Load()
{
// Return since state is only clear for OnStop, OnPause or low memory.
// https://developer.android.com/reference/android/app/Activity#ActivityLifecycle
if (IsLoaded)
{
return;
}
IsLoaded = true;
GL.ClearColor(1f, 1f, 1f, 0f); // Use white as clear color
GL.Enable(EnableCap.CullFace); // Activate differentiation which that only one side of faces is visible
GL.CullFace(CullFaceMode.Back); // Don't draw backside of faces
GL.Disable(EnableCap.DepthTest); // Enable for transparent textures
GL.Disable(EnableCap.Dither); // Turn off color illusion
GL.FrontFace(FrontFaceDirection.Ccw); // Polygons that are draw counter clock wise are pointing to the front
}
public void Render()
{
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
}
public void Resize(int width, int height)
{
if(width > 0 && height > 0)
{
GL.Viewport(0, 0, width, height);
}
}
}
}
This class finally brings some heart to our engine. On load it will set a white clear color and configure some other settings. The clear color is used before drawing each frame. It will ensure that everything from the last frame is wiped and we can draw something new.
Now we have to also use the “GameController” class. For this we have to add a project reference to “Engine.Shared” in the “Engine.Android” project. In the Reference manager you can find it under “Shared projects”.
Now we need to change the “GameView” class and add a new “Initialize” method:
public class GameView : AndroidGameView
{
private GameController gameController;
public void Initialize(GameController gameController)
{
this.gameController = gameController;
}
...
protected override void OnRenderFrame(FrameEventArgs e)
{
base.OnRenderFrame(e);
MakeCurrent();
gameController.Render();
SwapBuffers();
}
protected override void OnLoad(EventArgs e)
{
if (!gameController.IsLoaded)
{
gameController.Load();
}
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
if (gameController.IsLoaded)
{
gameController.Resize(Width, Height);
}
}
}
The “Initialize” method will just receive the “GameController” instance reference. Also we implemented “OnRenderFrame” and “OnLoad”. “OnRenderFrame” is called whenever a frame is rendered. In the best case this happens 60 times per second, since that’s what we passed to the “Run” method. The OnLoad method just calls the “Load” method on the “GameController”. This will happen initially and will set the configuration we looked at in the previous step.
For the last step we also have to adjust the “MainActivity” class. The only thing we have to do is to call “GameController.Initialize” before “Run”:
public class MainActivity : AppCompatActivity
{
private GameView view;
protected override void OnCreate(Bundle savedInstanceState)
{
..
// Pass information from this activity to the surfaceview
view = (GameView)FindViewById(Resource.Id.openglview);
view.Initialize(new GameController());
view.Run(60);
}
}
When you start the app now you will see that there is… a white screen! This doesn’t seem to be a lot, but we basically setup everything we need to start developing a platform independent mobile game!
You can download the source code of this article here: