Click here to Skip to main content
15,881,248 members
Articles / Monogame

MonoGame: Building Portable Solutions

Rate me:
Please Sign up or sign in to vote.
4.75/5 (3 votes)
21 May 2013CPOL10 min read 12.4K   4  
How to build portable solutions in MonoGame

This article appears in the Third Party Products and Tools section. Articles in this section are for the members only and must not be used to promote or advertise products in any way, shape or form. Please report any spam or advertising.

In one of my previous articles, I talked about how MonoGame could be used with portable libraries, this was off the back of some work I was doing with the MonoGame team to help with some of the more tedious clean up tasks that needed doing and I had the time.

Back then, it was more of a dream, today it’s becoming a reality.

Note

At the time of writing, it seems that Microsoft has not implemented PCL support from the Visual Studio Express editions as they are aimed at single platform support only, if you don’t have access to a PRO edition or above, then I recommend you use XAMARIN Studio for now.

If you would like to see PCL support in the VS express editions, then vote with a click over on UserVoice.

Why Go Portable?

Image 1

Now I waffled on about the theory last time for what Portable (or PCL – Portable Class Libraries) can offer with frameworks like MonoGame, especially when you want to take your project to as many platforms as possible, the main thing you want to achieve is to only have to put into a specific platforms project that which is native to that platform and keep everything else simple and in one place.

So with this tutorial, I’ll walk you through the practice of what it takes to go portable and what it can do for you.

Two main things come to mind as to why you should use a PCL project for the core logic and state of your game projects. They are:

  • One core project that contains all code that is completely compatible for all platforms, changes are validated by the project instead of at build time
  • One library for all platforms instead of separate platform libraries to share code, only have to make a change once, especially useful if you add or remove classes from the core project

Starter for 10

I’ll start with the sample I’ve used for several of my previous tutorials so that you’re familiar with what we have to use, so download the code drop for stage 3 in the previous shocking tutorials here.

*Note, yes there is a portable version on the lightning demo codeplex site but it is out of date with the current works, so ignore that one for now.

What you will find in the source is a project which currently looks like this:

image

What we want to get to is a project that looks more like this:

image

Setting Up Your Environment

Creating and using PCL projects are simple enough so long as you have the right tools to hand:

Note

At the time of writing, it seems that Microsoft has not implemented PCL support from the Visual Studio Express editions as they are aimed at single platform support only. If you don’t have access to a PRO edition or above, then I recommend you use XAMARIN Studio for now.

If you would like to see PCL support in the VS express editions, then vote with a click over on UserVoice.

Visual Studio 2010 (Pro & Above Only)

Studio 2010 doesn’t have PCL support by default so you need to install it first, just launch NuGet and search for "Portable Library Tools" and you should locate Microsoft’s Portable Library Tools 2 package, install it and you’re ready to go.

If you haven’t got NuGet yet, then install this VSIX (visual Studio Extension) package and then "goto 1".

Visual Studio 2012 (Pro & Above Only)

Thankfully, VS 2012 already comes with PCL support out of the box so you are ready to go.

Xamarin Studio

From  version 4.03, PCL projects have been supported natively in Xamarin Studio

Lastly, you’ll need a copy of the current version of MonoGame.Portable, you can either download the current compiled DLL here, or just clone my MonoGame.Portable branch in my MonoGame fork (if / when the PR is merged into the main MonoGame source, I’ll update the above links accordingly) and build it yourself.

Starting Fresh

To keep things simple, let's just start a new project and re-use all the code from the above sample, we’ll even re-use the existing content builder project to keep it simple.

So to start off by creating a new MonoGame project of your choosing. Myself, I’m going to keep it simple and create a WindowsGL project (in Xamarin Studio :D), you can also do the same in Visual Studio as well the steps are the same.

Note: If you haven’t done so already and are using Xamarin Studio, be sure to grab the Xamarin Studio add-on for the MonoGame templates here (also available for MonoDevelop if that’s your fancy).

image

That’ll get you a brand new MonoGame Project, next up, let's add a Portable Library to the mix for our Engine, so add a new Project using the "Portable" template (in the C# folder or search for portable).

image

Just give the new PCL (Engine) library a simple name, in this case, I’ve named it "Lightning.Engine".

Next, we’ll add our original engine code into the portable library, so copy these over from the previous sample and then rename the namespaces in each class to "Lightning.Engine" to match our project:

image

Now if you build the Engine project at present, you will get a whole load of build errors.

At this point, you might think "well I have my PCL library, why don’t I just use one of my existing MonoGame references?", as well you should but just try it and see. You will either get:

  • No Error and No Reference added
  • No Error but the reference will be added
  • Your development environment will throw a wobbly and probably just crash (with or without an error)

None of which will result in a project that will build, in fact in the second case above, you might think it has worked but basically, it didn’t :D, the answer is of course to have a PCL compatible DLL to add, namely in this case MonoGame.Portable.

So by either adding MonoGame.Portable to the solution as source and mapping a reference to it from the engine PCL library (AND ONLY the Engine library, not the platform), or by referencing the DLL from the download above in the Engine Library, you should now be able to build the engine Lib.

*Note – well, with one exception :D, LINQ in a PCL does not have the ".Find" extension, so simply replace this with ".FirstOrDefault"

Getting On With the Game

Image 7

OK, so having our logic in a separate lib is nice but what about the rest of the game, well, let’s take this a bit further then. Copy over the original "Game.cs" class and embed that into the library as well and for good measure and rename it to something more meaningful for our engine like "LightningGame.cs".

To use our core game code in a PCL however (or any separate lib for that matter), there are a few things we need to take into account, first and foremost (to quote Highlander), "There can be only ONE", or in the case of XNA and MonoGame, there can only be one class in our game solution using the XNA GAME class as its base.

To this end, we need to:

  • Remove the base class from our game class
  • Remove any functions that initialise the graphics device (that’s up to the platform)
  • Remove any "override" statements, since this class is not inheriting anymore
  • Alter the scope of methods that need to be initialised or called (or add new ones) e.g., Protected –> Public or Internal
  • Satisfy any framework dependencies that are required for the class to function. e.g., if GraphicsDevice  is used, which was part of the base game class

As an example, here’s a side by side comparison of the updates I made to make the old game code ready for the PCL.

C#
public class Game1 : Game
{
C#
public class LightningGame
{
     Game game;

    public LightningGame(Game game)
    {
        this.game = game;
Old Game class inheriting from XNA Game New Game class with no inheritance but has a constructor that takes an XNA Game class as a parameter and stores it in a private variable
C#
public Game1()
{
    graphics = new GraphicsDeviceManager(this);
    Content.RootDirectory = "Content";

    IsMouseVisible = true;
    graphics.PreferredBackBufferWidth = 1280;
    graphics.PreferredBackBufferHeight = 720;

}
C#
public LightningGame(Game game)
{
    this.game = game;
}
Old game class constructor New game constructor, note does not initialise graphics now, that is the job of the platform
C#
protected override void LoadContent()
C#
public void LoadContent(ContentManager Content)
Old style XNA override from the XNA Game class implementation Updated methods whose scope has changed and accept the relevant types needed to perform function.
C#
spriteBatch = new SpriteBatch(GraphicsDevice);
Point screenSize = new Point(
                GraphicsDevice.Viewport.Width, 
                GraphicsDevice.Viewport.Height);
lastFrame = new RenderTarget2D(
                GraphicsDevice, 
                screenSize.X, 
                screenSize.Y, false, 
                SurfaceFormat.HdrBlendable, 
                DepthFormat.None);
C#
spriteBatch = new SpriteBatch(game.GraphicsDevice);
Point screenSize = new Point(
        game.GraphicsDevice.Viewport.Width, 
        game.GraphicsDevice.Viewport.Height);
lastFrame = new RenderTarget2D(
        game.GraphicsDevice, 
        screenSize.X, 
        screenSize.Y, false, 
        SurfaceFormat.Color, 
        DepthFormat.None);
Old style class using XNA dependencies from Game base class, like GraphicsDevice Updated functions deriving the necessary types from the game class sent with the constructor

Once you have finished updating the new game style class in the PCL should look like this: (note I also added a few bits from the Win 8 solution to cope with touch and multi-touch).

C#
#region Using Statements
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
#endregion
namespace Lightning.Engine
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class LightningGame
    {
        enum Mode { SimpleLightning, BranchLightning, LightningText }
        Mode mode;
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        Game game;
        SpriteFont lightningFont, infoFont;
        KeyboardState keyState, lastKeyState;
        MouseState mouseState, lastMouseState;
        TouchCollection touches, previousTouches;
        List<ILightning> bolts = new List<ILightning>();
        LightningText lightningText;
        RenderTarget2D lastFrame, currentFrame;
        public LightningGame(Game game)
        {
            this.game = game;
        }
        /// <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>
        public void Initialize()
        {
            // TODO: Add your initialization logic here
        }
        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        public void LoadContent(ContentManager Content)
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(game.GraphicsDevice);
            lightningFont = Content.Load<SpriteFont>("LightningFont");
            infoFont = Content.Load<SpriteFont>("InfoFont");
            Point screenSize = new Point
            (game.GraphicsDevice.Viewport.Width, game.GraphicsDevice.Viewport.Height);
            lastFrame = new RenderTarget2D(game.GraphicsDevice, screenSize.X, 
            screenSize.Y, false, SurfaceFormat.Color, DepthFormat.None);
            currentFrame = new RenderTarget2D(game.GraphicsDevice, screenSize.X, 
            screenSize.Y, false, SurfaceFormat.Color, DepthFormat.None);
            // Initialize lastFrame to be solid black
            game.GraphicsDevice.SetRenderTarget(lastFrame);
            game.GraphicsDevice.Clear(Color.Black);
            game.GraphicsDevice.SetRenderTarget(null);
            Art.Load(Content);
            lightningText = new LightningText
            (game.GraphicsDevice, spriteBatch, lightningFont, "Lightning");
        }
        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        public 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>
        public void Update(GameTime gameTime)
        {
            // TODO: Add your update logic here
            lastKeyState = keyState;
            keyState = Keyboard.GetState();
            lastMouseState = mouseState;
            mouseState = Mouse.GetState();
            if (WasPressed(Keys.Space))
                mode = (Mode)(((int)mode + 1) % 3);
            var screenSize = new Vector2
            (game.GraphicsDevice.Viewport.Width, game.GraphicsDevice.Viewport.Height);
            var mousePosition = new Vector2(mouseState.X, mouseState.Y);
            previousTouches = touches;
            touches = TouchPanel.GetState();
            for (int i = 0; i < touches.Count; i++)
            {
                if (touches[i].State != TouchLocationState.Pressed)
                {
                    continue;
                }
                if (touches.Count == 1)
                {
                    bolts.Add(new LightningBolt(screenSize / 2, touches[i].Position));
                }
                else
                {
                    if (i > 0)
                        bolts.Add(new LightningBolt(touches[i - 1].Position, touches[i].Position));
                }
            }
            switch (mode)
            {
                case Mode.SimpleLightning:
                    if (WasClicked())
                        bolts.Add(new LightningBolt(screenSize / 2, mousePosition));
                    break;
                case Mode.BranchLightning:
                    if (WasClicked())
                        bolts.Add(new BranchLightning(screenSize / 2, mousePosition));
                    break;
                case Mode.LightningText:
                    lightningText.Update();
                    break;
            }
            foreach (var bolt in bolts)
                bolt.Update();
            bolts = bolts.Where(x => !x.IsComplete).ToList();
        }
        // return true if a key was pressed down this frame
        bool WasPressed(Keys key)
        {
            return keyState.IsKeyDown(key) && lastKeyState.IsKeyUp(key);
        }
        // return true if the left mouse button was clicked down this frame
        bool WasClicked()
        {
            return mouseState.LeftButton == ButtonState.Pressed && 
            lastMouseState.LeftButton == ButtonState.Released;
        }
        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public void Draw(GameTime gameTime)
        {
            // TODO: Add your drawing code here
            // The lightning text is drawn a bit differently 
            // due to our optimization with the render targets.
            if (mode == Mode.LightningText)
                DrawLightningText();
            else
                DrawLightning();
            spriteBatch.Begin();
            spriteBatch.DrawString(infoFont, "" + mode, new Vector2(5), Color.White);
            spriteBatch.DrawString(infoFont, 
            "Press space to change mode", new Vector2(5, 30), Color.White);
            if (mode != Mode.LightningText)
                spriteBatch.DrawString(infoFont, 
                "Click to make lightning", new Vector2(5, 55), Color.White);
            spriteBatch.End();
        }
        void DrawLightningText()
        {
            game.GraphicsDevice.SetRenderTarget(currentFrame);
            game.GraphicsDevice.Clear(Color.Black);
            // draw our last frame at 96% of its original brightness
            spriteBatch.Begin(0, BlendState.Opaque, SamplerState.PointClamp, null, null);
            spriteBatch.Draw(lastFrame, Vector2.Zero, Color.White * 0.96f);
            spriteBatch.End();
            // draw the new lightning bolts
            spriteBatch.Begin(SpriteSortMode.Texture, BlendState.Additive);
            lightningText.Draw();
            spriteBatch.End();
            // draw currentFrame to the backbuffer
            game.GraphicsDevice.SetRenderTarget(null);
            spriteBatch.Begin(0, BlendState.Opaque, SamplerState.PointClamp, null, null);
            spriteBatch.Draw(currentFrame, Vector2.Zero, Color.White);
            spriteBatch.End();
            Swap(ref currentFrame, ref lastFrame);
        }
        void DrawLightning()
        {
            game.GraphicsDevice.Clear(Color.Black);
            // we use SpriteSortMode.Texture to improve performance
            spriteBatch.Begin(SpriteSortMode.Texture, BlendState.Additive);
            foreach (var bolt in bolts)
                bolt.Draw(spriteBatch);
            spriteBatch.End();
        }
        void Swap<T>(ref T a, ref T b)
        {
            T temp = a;
            a = b;
            b = temp;
        }
    }
}

Time For Some Platform Action

Image 8

So now that our entire game is hosted within the PCL project, let's update our platform project to run it, here’s where you really start to see the effort paying off, especially as you add more platforms to the mix.

Note: Don’t forget to build and add your Content to your platform project to avoid the dreaded "Could not load Content" error :D, see my previous post for how to build a content builder project using the original Lightning source, or (as I did) just use copy / use one of the existing projects in the Lightning source used by this tutorial

So opening up the Game1.cs file in the platform project, we just need to initialise our engine and call it from the relevant methods:

C#
GraphicsDeviceManager graphics;

SpriteBatch spriteBatch;

LightningGame lightningGame;
Add a property to store an instance of the game engine in the class properties
C#
public Game1()
    : base()
{
    graphics = new GraphicsDeviceManager(this);
    Content.RootDirectory = "Content";

    IsMouseVisible = true;
    graphics.PreferredBackBufferWidth = 1280;
    graphics.PreferredBackBufferHeight = 720;

    lightningGame = new LightningGame(this);
}
Instantiate a new instance in the game constructor
C#
protected override void LoadContent()
{
    // Create a new SpriteBatch, which can be used to draw textures.
    spriteBatch = new SpriteBatch(GraphicsDevice);

    // TODO: use this.Content to load your game content here
    lightningGame.LoadContent (Content);
}
Get the engine to load its content when the platform is loading content
C#
protected override void Update(GameTime gameTime)
{
    if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || 
            Keyboard.GetState().IsKeyDown(Keys.Escape))
        Exit();

    // TODO: Add your update logic here
    lightningGame.Update (gameTime);

    base.Update(gameTime);
}
Call the update class during update
C#
protected override void Draw(GameTime gameTime)
{
    GraphicsDevice.Clear(Color.CornflowerBlue);

    // TODO: Add your drawing code here
    lightningGame.Draw (gameTime);

    base.Draw(gameTime);
}
Finally, tell the engine to draw in the XNA draw loop

So now, implementing our game in each platform (in a very basic way) has been reduced to just 5 entries in our platform Game class.

Sure you could achieve something similar by using platform libraries but any change you make to the engine library could potentially break any of the platforms you support, with PCL projects you are guaranteed that all platforms will continue to work else the PCL project itself won’t build.

So now in each platform solution, you can manage just what’s needed for that platform, such as Share Contract and fly-outs on Windows 8, Notifications and NFC on Windows Phone plus whatever else they do on those other platforms (so shoot me I’m not an Android or iOS expert).

Going Further

Image 9

Now that isn’t the end of the story (although it is the end of this tutorial), you can go a lot further with PCL solutions and it’s nice to note that PCL solutions can also reference other PCL solutions as well to expand what is the core of your project.

You can also add some abstraction in to the mix so you can plug-in / out your favourite other frameworks if you so wish.

Just Keep That Core Clean

If you want to keep track of the different ways of building multi-platform / Cross platform solutions, keep an eye on my presentation repository where I’ll keep adding more code in the samples for use, alternatively there is a lot of work going on to clean up the samples for MonoGame in the MonoGame Samples repo’s so you’ll have another source of reference.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect ZenithMoon Studios
United Kingdom United Kingdom
Long-time game developer / IT maniac.
By day I architect, design, build and deliver enriching Mixed Reality solutions to clients, bringing the work of AR/VR to light in new and interesting ways, by night I Masquerade as the Master Chief of ZenithMoon Studios, my own game development studio.

At heart, I am a community developer breaking down lots of fun and curious technologies and bringing them to the masses.

I'm also a contributor to several open-source projects, most notably, the Reality Toolkit and all the services provided by the Reality Collective, The Unity-UI-Extensions project, as well as in the past the AdRotator advertising rotator project for Windows and Windows Phone.

Currently, I spend my time fulfilling contracts in the Mixed Reality space (primarily for an XR experience firm called Ethar), writing books, technically reviewing tons of material and continuing my long tradition of contributing to open-source development, as well as delivering talks, but that goes without saying Big Grin | :-D

Mixed Reality MVP, Xbox Ambassador, MS GameDevelopment Ambassador & Best selling author:

[Accelerating Unity Through Automation](https://www.amazon.co.uk/Accelerating-Unity-Through-Automation-Offloading/dp/1484295072/ref=rvi_sccl_3/262-0817396-1418043)
[Mastering Unity 2D Game Development] (https://www.packtpub.com/game-development/mastering-unity-2d-game-development)
[Unity 3D UI Essentials] (https://www.packtpub.com/game-development/unity-3d-gui-essentials)

Comments and Discussions

 
-- There are no messages in this forum --