Let’s create a mobile game with Xamarin and OpenGL – Part 7 (State)

You may have noticed in the last part, that whenever you leave the game, or even change the screen orientation, you will lose the current state of the game. In this part we are going to learn how we can keep the state of our running game.

Serialization & Deserialization

First we need to extend our FileManager. We need to save the state of our game somehow. In this case we will use a JSON serializiation. Extend the class “FileManager” with the following methods.

using System.IO; using System.Reflection; using System.Text; using System.Runtime.Serialization.Json; using System.Xml; namespace Engine.Shared.Managers { public abstract class FileManager { ... public void Serialize<T>(string path, T data) { DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T)); using (Stream stream = new FileStream(path, FileMode.Create)) { using (var writer = JsonReaderWriterFactory.CreateJsonWriter(stream, Encoding.UTF8)) { serializer.WriteObject(writer, data); writer.Flush(); } } } public T Deserialize<T>(string path) { if (!File.Exists(path)) { return default(T); } using (Stream stream = new FileStream(path, FileMode.Open)) { DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T)); using (var reader = JsonReaderWriterFactory.CreateJsonReader(stream, Encoding.UTF8, XmlDictionaryReaderQuotas.Max, null)) { return (T)serializer.ReadObject(reader); } } } public abstract string GetApplicationDirectory(); } }

Make sure to add a reference to the package “System.Runtime.Serialization” which you can find in “Assemblies > Framework”.

Because getting the application directory is platform specific, we need to define this as an abstract method and implement it specifially for Android in “AndroidFileManager”:

using Android.Content; using Android.Graphics; using Engine.Shared.Managers; using System.Reflection; using System.Runtime.InteropServices; namespace Engine.Android { public class AndroidFileManager : FileManager { private readonly Context context; public AndroidFileManager(Context context) { this.context = context; } ... public override string GetApplicationDirectory() { return context.ApplicationContext.FilesDir.AbsolutePath; } } }

In “MainActivity.cs” make sure to pass the Android context to the “FileManager”:

namespace MyGame.Android { ... public class MainActivity : AppCompatActivity { ... protected override void OnCreate(Bundle savedInstanceState) { ... var fileManager = new AndroidFileManager(ApplicationContext); var gameController = new GameController(fileManager); ... } } }

For the serializer to know which properties have to be serialized, we have to use attributes in the “TicTacToe.cs” class:

using System.Runtime.Serialization; namespace MyGame.Shared { [DataContract] public class TicTacToe { ... [DataMember] public Player?[][] Playground { get; private set; } = { new Player?[]{ null, null, null }, new Player?[]{ null, null, null }, new Player?[]{ null, null, null } }; ... [DataMember] public Player NextTurn { get; private set; } = Player.Cross; ... [DataMember] public int SelectedTiles { get; private set; } ... [DataMember] public Player? Winner { get; private set; } } }

Handle unloading

To detect when our application gets unloaded we need to implement the OnUnload method in “GameView.cs”:

namespace Engine.Android { ... public class GameView : AndroidGameView { protected override void OnUnload(EventArgs e) { if (gameController.IsLoaded) { gameController.OnUnload(); } base.OnUnload(e); } } }

Since we are calling “OnUnload” on the “GameController” we also need a new method and an event there:

namespace Engine.Shared { public class GameController : IDisposable { ... public delegate IScene LoadSceneArguments(GameController gameController, InputManager inputManager, RectangleRenderer rectangleRenderer, SpriteRenderer spriteRenderer, TextureManager textureManager, BufferManager bufferManager, FileManager fileManager); ... public event Action Unload; ... public void LoadScene(LoadSceneArguments create) { sceneManager.LoadScene(create(this, inputManager, rectangleRenderer, spriteRenderer, textureManager, bufferManager, fileManager)); } ... public void OnUnload() { Unload?.Invoke(); } } }

Now we are able to do something whenever our game is getting unloaded. Change the “GameScene” by injecting the “FileManager” and subscribing to the “Unload” event:

namespace MyGame.Shared { public class GameScene : IScene { ... public GameScene( ... FileManager fileManager) { ... this.fileManager = fileManager; this.gameController.Unload += GameController_OnUnload; } private void GameController_OnUnload() { fileManager.Serialize(Path.Combine(fileManager.GetApplicationDirectory(), "tictactoe.json"), ticTacToe); } public void Load() { ticTacToe = fileManager.Deserialize<TicTacToe>(Path.Combine(fileManager.GetApplicationDirectory(), "tictactoe.json")); if (ticTacToe == null) { ticTacToe = new TicTacToe(); } ... } ... } }

Whenever the game is unloaded, the current instance of the TicTacToe class will be serialized. When the game is loaded, we deserialize the json from the filesystem and use an instance of the TicTacToe class with the previous state.

You can download the source code of this article here: