Xamarin App Development: How to Build Portable Games

Xamarin App Development, Writing Mobile Games

I recently read an article on the mono project's blog about the “Wordament” game developed by a Microsoft team, and how they used Xamarin to ship the mobile app to Android and iOS platforms, with the code that is 80% shared.

Wordament can be compared to any common application: it is made with common controls as buttons, displays a lot of text, etc. But most of the mobile games do not look like this. Most of the games make large use of image drawings, musics, sounds and 3D, etc. In this article, we will see how it is possible to write traditional games using Xamarin, and with levels up to 90% of the shared code base, by developing a simple tic tac toe mobile game.

 

Presentation of the Monogame framework

Microsoft first released the XNA framework [1] in December 2007, which gives developers the possibility to make games targeting both PC and XBOX 360. This framework has seen a lot of improvements and new features and final version was released on 31 January 2013. Meanwhile, the Monogame [2] project appeared to port XNA framework to the Mono framework. This open source project is actively maintained, and is compatible with Xamarin, allowing developers to target a large amount of systems, including Windows, Linux, IOS, Android, Ouya [3] consoles, and many more.

 

Setting up a Monogame project

Our project will be split in 2 distincts parts:

1 - Minimal platform specifics projects

2 - .Net library which can be referenced within our specifics platform projects.

 

First, we’re going to add the Monogame Template to our Visual Studio

After installing the templates, we’re going to create the 3 projects for the shared code and the specific platform.

For the Android and iOS project, we’re going to select the correct template and NuGet Package.

We’re going to use this as our token for the players.

Image

 

logo.png

 

Create a new folder ““Graphics”, then open the MonoGame Pipeline tool or install from here.

Modify the output directory from the graphics resource

 

Build our content

Go to the output directory and add the files to the visual studio

 

Android case the build action set as AndroidAsset

 

iOS case the build action set as Content

After setup all our game resources we’re going to write some code for each platform and must ship with a Game1.cs file, which will init the specific parts for the device (graphics, sounds, etc…), and delegate all the logic to our PCL project.

As you can see, each project comes with a Game.cs file. This class has 3 key methods:

protected override void Initialize ();

protected override void Update(GameTime gameTime);

protected override void Draw(GameTime gameTime);

 

We will use the initialize methods to instantiate our TicTacToeGame class from our core library and initialize it with the screen dimensions so we can scale and locate our images on the screen whatever the resolution of our device is.

 

Game1.cs

       #region Fields

       GraphicsDeviceManager graphics;

       TicTacToeGame TicTacToeGame;

       SpriteBatch spriteBatch;

       Texture2D logoTexture;

       Texture2D Square;

       Texture2D Pixel;


       public MouseState MouseLast { get; set; }

       public MouseState MouseCurrent { get; set; }

       #endregion


       public Game1()

       {

           TicTacToeGame = new TicTacToeGame();

           graphics = new GraphicsDeviceManager(this);


           graphics.IsFullScreen = false;

           Content.RootDirectory = "Content";

           graphics.SupportedOrientations = DisplayOrientation.LandscapeLeft | DisplayOrientation.LandscapeRight;

       }


       /// <summary>

       /// Allows the game to perform any initialization it needs to before starting to run.

       /// This is where it can query for any required services and load any non-graphic

       /// related content.  Calling base.Initialize will enumerate through any components

       /// and initialize them as well.

       /// </summary>

       protected override void Initialize()

       {

           // TODO: Add your initialization logic here

           base.Initialize();

           var screenHeight = graphics.PreferredBackBufferHeight;

           TicTacToeGame.Init(graphics.PreferredBackBufferWidth, screenHeight, screenHeight / 4);

       }


       /// <summary>

       /// LoadContent will be called once per game and is the place to load

       /// all of your content.

       /// </summary>

       protected override void LoadContent()

       {

           //TODO: use this.Content to load your game content here

           // Create a new SpriteBatch, which can be used to draw textures.

           Pixel = CreatePixel();

           spriteBatch = new SpriteBatch(graphics.GraphicsDevice);

           logoTexture = Content.Load<Texture2D>("Graphics/logo");

           Square = CreateSquareTexture(graphics.PreferredBackBufferHeight / 4);

       }


       protected override void UnloadContent()

       {

           // TODO: Unload any non ContentManager content here

       }


       /// <summary>

       /// Allows the game to run logic such as updating the world,

       /// checking for collisions, gathering input, and playing audio.

       /// </summary>

       /// <param name="gameTime">Provides a snapshot of timing values.</param>

       protected override void Update(GameTime gameTime)

       {

           base.Update(gameTime);

           GetInputs();

       }


       /// <summary>

       /// This is called when the game should draw itself.

       /// </summary>

       /// <param name="gameTime">Provides a snapshot of timing values.</param>

       protected override void Draw(GameTime gameTime)

       {

           //TODO: Add your drawing code here

           graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

           spriteBatch.Begin();


           foreach (var column in TicTacToeGame.Board)

           {

               foreach (var cell in column)

               {

                   spriteBatch.Draw(Square, new Vector2(cell.PositionOnScreenX, cell.PositionOnScreenY), Color.White);

                   DrawBorder(new Rectangle(cell.PositionOnScreenX, cell.PositionOnScreenY, cell.Size, cell.Size), 2, Color.Black);

                   if (cell.Move != Player.NOTHING)

                   {

                       var cellCenter = cell.Center;

                       var color = (cell.Move == Player.CROSS) ? Color.Green : Color.Red;

                       spriteBatch.Draw(logoTexture, new Vector2(cellCenter.X - (logoTexture.Width / 2), cellCenter.Y - (logoTexture.Height / 2)), color);

                   }

               }

           }


           if (TicTacToeGame.SomePlayerHasWin)

           {

               if (TicTacToeGame.Winner == Player.BOTH)

               {

                   spriteBatch.Draw(logoTexture, new Vector2(graphics.PreferredBackBufferWidth / 8, graphics.PreferredBackBufferHeight / 2), Color.Black);

               }

               else

               {

                   var color = (TicTacToeGame.Winner == Player.CROSS) ? Color.Green : Color.Red;

                   spriteBatch.Draw(logoTexture, new Vector2(graphics.PreferredBackBufferWidth / 8, graphics.PreferredBackBufferHeight / 2), color);

               }

           }


           spriteBatch.End();

           base.Draw(gameTime);

       }


       private Texture2D CreateSquareTexture(int size)

       {

           Texture2D texture = new Texture2D(GraphicsDevice, size, size);

           Color[] data = new Color[size * size];


           for (int i = 0; i < data.Length; i++)

           {

               data[i] = Color.White;

           }


           texture.SetData(data);


           return texture;

       }


       private Texture2D CreatePixel()

       {

           Texture2D texture = new Texture2D(GraphicsDevice, 1, 1);

           Color[] data = new Color[1];

           data[0] = Color.White;

           texture.SetData(data);

           return texture;

       }


       private void DrawBorder(Rectangle rectangleToDraw, int thicknessOfBorder, Color borderColor)

       {

           // Draw top line

           spriteBatch.Draw(Pixel, new Rectangle(rectangleToDraw.X, rectangleToDraw.Y, rectangleToDraw.Width, thicknessOfBorder), borderColor);


           // Draw left line

           spriteBatch.Draw(Pixel, new Rectangle(rectangleToDraw.X, rectangleToDraw.Y, thicknessOfBorder, rectangleToDraw.Height), borderColor);


           // Draw right line

           spriteBatch.Draw(Pixel, new Rectangle((rectangleToDraw.X + rectangleToDraw.Width - thicknessOfBorder), rectangleToDraw.Y, thicknessOfBorder, rectangleToDraw.Height), borderColor);


           // Draw bottom line

           spriteBatch.Draw(Pixel, new Rectangle(rectangleToDraw.X, rectangleToDraw.Y + rectangleToDraw.Height - thicknessOfBorder, rectangleToDraw.Width, thicknessOfBorder), borderColor);

       }


       protected void GetInputs()

       {

           MouseLast = MouseCurrent;

           MouseCurrent = Mouse.GetState();


           // For Mobile devices, this logic will close the Game when the Back button is pressed

           // Exit() is obsolete on iOS

           if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))

           {

               Exit();

           }


           if (MouseCurrent.LeftButton == ButtonState.Released && MouseLast.LeftButton == ButtonState.Pressed)

           {

               if (Clicked((int)Mouse.GetState().X, (int)Mouse.GetState().Y))

                   return;

           }


           var touchCollection = TouchPanel.GetState().Where(x => x.State == TouchLocationState.Pressed);

           foreach (var touch in touchCollection)

           {

               if (Clicked((int)touch.Position.X, (int)touch.Position.Y))

                   return;

           }

       }


       protected bool Clicked(int x, int y)

       {

           foreach (var column in TicTacToeGame.Board)

           {

               foreach (var cell in column)

               {

                   if (cell.Collide(x, y, cell.Size, cell.Size))

                   {

                       TicTacToeGame.CellClicked(cell);

                       return true;

                   }

               }

           }

           return false;

       }

 

Writing the shared code base

Now it is time to write our game logic inside the Portable Class Library. A Tic Tac Toe game consists of a 3x3 board, in which a player makes move. The first one to make a row of 3 moves to win.

Our library will have a TicTacToeGame class which will encapsulate our Cells objects, and a Solver class to determine if someone has won the game.

Our Cells will have a position on the screen, a position on the board game, and a player move which can be a cross, circle, or nothing.

We initialize our tic tac toe game so that the board will occupy 3/4 of the screen, and center it:

TicTacToeGame.cs

     public Player CurrentPlayer { get; private set; }

       public Player Winner { get; set; }

       public Cell[][] Board { get; private set; }


       private Solver Solver = new Solver();


       public TicTacToeGame()

       {

           Board = new Cell[3][];

           CurrentPlayer = Player.CROSS;

           Winner = Player.NOTHING;

       }


       public bool SomePlayerHasWin

       {

           get

           {

               return Winner != Player.NOTHING;

           }

       }


       public void Init(int screenWidth, int screenHeight, int cellSize)

       {

           var totalSize = cellSize * 3; //3 cells

           // We want it centered, whatever the resolution is

           var adjustHorizontal = CalculAdjust(screenWidth, totalSize);

           var adjustVertical = CalculAdjust(screenHeight, totalSize);


           for (int i = 0; i < Board.Length; i++)

           {

               Board[i] = new Cell[3];

               for (int j = 0; j < Board[i].Length; j++)

               {

                   Board[i][j] = new Cell(cellSize, j, i, adjustHorizontal, adjustVertical);

               }

           }

       }


       public void CellClicked(Cell cell)

       {

           if (Winner == Player.NOTHING)

           {

               if (Board[cell.Y][cell.X].Move == Player.NOTHING)

               {

                   Board[cell.Y][cell.X].Move = CurrentPlayer;

                   CurrentPlayer = (CurrentPlayer == Player.CROSS) ? Player.CIRCLE : Player.CROSS;

               }

               Solver.Solve(cell.X, cell.Y, 3, this);

           }

       }


       private int CalculAdjust(int screenTotal, int cellsTotal)

       {

           if (cellsTotal >= screenTotal)

               return 0;

           return (screenTotal - cellsTotal) / 2;

       }

 

Here is our Solver class:

Solver.cs

       public void Solve(int x, int y, int n, TicTacToeGame game)

       {

           SolveInternal(Player.CROSS, 0, y, n, game);

           SolveInternal(Player.CROSS, 1, y, n, game);

           SolveInternal(Player.CROSS, 2, y, n, game);

           SolveInternal(Player.CROSS, x, 0, n, game);

           SolveInternal(Player.CROSS, x, 1, n, game);

           SolveInternal(Player.CROSS, x, 2, n, game);


           SolveInternal(Player.CIRCLE, 0, y, n, game);

           SolveInternal(Player.CIRCLE, 1, y, n, game);

           SolveInternal(Player.CIRCLE, 2, y, n, game);

           SolveInternal(Player.CIRCLE, x, 0, n, game);

           SolveInternal(Player.CIRCLE, x, 1, n, game);

           SolveInternal(Player.CIRCLE, x, 2, n, game);

       }


       private void SolveInternal(Player testAgainst, int x, int y, int n, TicTacToeGame game)

       {

           //check col

           for (int i = 0; i < n; i++)

           {

               if (game.Board[x][i].Move != testAgainst)

                   break;

               if (i == n - 1)

                   game.Winner = testAgainst;

           }


           //check row

           for (int i = 0; i < n; i++)

           {

               if (game.Board[i][y].Move != testAgainst)

                   break;

               if (i == n - 1)

                   game.Winner = testAgainst;

           }


           //check diag

           if (x == y)

           {

               for (int i = 0; i < n; i++)

               {

                   if (game.Board[i][i].Move != testAgainst)

                       break;

                   if (i == n - 1)

                       game.Winner = testAgainst;

               }

           }


           //check anti diag

           for (int i = 0; i < n; i++)

           {

               if (game.Board[i][(n - 1) - i].Move != testAgainst)

                   break;

               if (i == n - 1)

                   game.Winner = testAgainst;

           }

       }

 

Here is our Cell class:

Cell.cs

       public Player Move { get; set; }

       public int Size { get; private set; }

       public int X { get; private set; }

       public int Y { get; private set; }

       public int PositionOnScreenX { get; set; }

       public int PositionOnScreenY { get; set; }


       public Cell (int size, int x, int y,

           int adjustHorizontal, int adjustVertical)

       {

           Move = Player.NOTHING;

           Size = size;

           X = x;

           Y = y;

           PositionOnScreenX = x * size + adjustHorizontal;

           PositionOnScreenY = y * size + adjustVertical;

       }


       public Vector Center

       {

           get

           {

               return new Vector (PositionOnScreenX + (Size / 2),

                   PositionOnScreenY + (Size / 2));

           }

       }


       public bool Collide(int x, int y, int width, int height)

       {

           return (

               (Math.Abs (x - PositionOnScreenX) * 2 < Math.Abs (width + Size)) &&

               (Math.Abs (y - PositionOnScreenY) * 2 < Math.Abs (height + Size))

           );

       }

Here is our player enum:

Player.cs

   public enum Player

   {

       NOTHING,

       CROSS,

       CIRCLE,

       BOTH

   }

 

Here is our vector class:

Vector.cs

   public class Vector

   {

       public Vector ()

       {

       }


       public Vector(int x, int y)

       {

           X = x;

           Y = y;

       }


       public int X { get; set; }

       public int Y { get; set; }

   }

 

We are now ready to test our tic tac toe!

Run the game!

Android

iOS

 

Download our Free E-Book and learn how the Product Mindset can benefit your mobile app. 

Through this guide, we want to give you, product owners, product managers, engineers, entrepreneurs and anybody looking to build great digital products, more insights into the importance of user-centric design and development and how to correctly achieve it.

How Product Mindset Can Save your Sinking App

You may also like:

Post Your Comment Here