Porting your Windows XNA game to MacOS using MonoGame and MonoMac Part 3

If you still have build errors at this point, read on. I’m going to cover some runtime tweaks that might deal with your issue. If you have no build errors then I’m sure you’re dying to run the app. And you probably should to get a feel for what’s going on. But chances are that trying to Run the app is going to give you lots of runtime errors. So maybe take a look at some changes I had to make before you try to Run. But please only apply these changes if you encounter problems and always debug and break your original game on Windows to confirm correct values if necessary:


If you want to change your game window background color

Just add this as the first line of you Game1() constructor:

Window.Window.BackgroundColor = NSColor.Black;

This will make the window be black from the very beginning instead of showing up gray at first


If you are using XACT

As I stated in Part 1 XACT is not implemented yet and if you used it on your Windows game, you’ll have to use SoundEffect SoundEffectInstance classes instead. Just look them up on MSDN.com and modify your code accordingly.


If you have video

As stated in Part 1 acceptable video formats are mp4, mov, avi, m4v, 3gp. So make sure to add your movies to the Content folder, Right-click->Build Action->Content, then follow the Video sample in MonoGameSamples to modify your video code.


Loading textures from a background thread

Calling Content.Load() from a background thread if done the traditional XNA way will most likely cause your program to crash. But fret not! You just have to modify your loading code a bit. A perfect example on how to do this can be found in the MonoGame Samples project “BackgroundThreadTester”. Thanks Ken!


Changing the version number of your project

The current version of your mac project right now is 0.1 if you need to change this, Right-click on your project->Options->General->MainSettings->General Tab-> and set the version to match the version in your AssemblyInfo.cs file (you may have to Uncheck “Get version from parent solution)


Using .NET SpecialFolder

*If you used .NET’s Environment.SpecialFolder.MyDocuments, for example, in Windows, it will now equal “/Users/Username” so modify anything relating to this accordingly. Also remember that Mac’s use “/” in their path names so look for any paths with “\” or “\\” and modify accordingly.


Moving and resizing your window

MonoGame currently centers your gamewindow at startup. But when I came on board it didn’t have this functionality yet. So I wrote this CenterWindow() method to accomplish that. Like I said you don’t need to worry about centering the window anymore, but this method still illustrates how to move and resize the window, so it’s still worth looking at.

        private void CenterWindow()
        {
            int index;
            int upperBound;
            float fScreenWidth, fScreenHeight, fNewX, fNewY, fWindowWidth, fWindowHeight, fTitleBarHeight;
	        Screen[] screens = Screen.AllScreens;

	        fScreenWidth = fScreenHeight = 0;

            upperBound = screens.GetUpperBound(0);
            for (index = 0; index <= upperBound; index++)
            {
                if (screens[index].Primary)
                {
			    fScreenWidth = (float)screens[index].Bounds.Width;
                    fScreenHeight = (float)screens[index].Bounds.Height;
                    index = upperBound;
                }//if
            }//for

            fWindowWidth = graphics.PreferredBackBufferWidth;
            fWindowHeight = graphics.PreferredBackBufferHeight;

            fNewX = (fScreenWidth - fWindowWidth) / 2;
            fNewY = (fScreenHeight - fWindowHeight) / 2;

            fTitleBarHeight = this.Window.Window.Frame.Height - fWindowHeight;

			System.Drawing.PointF pfLocation = new System.Drawing.PointF(fNewX,fNewY);
			System.Drawing.PointF pfSize = new System.Drawing.PointF(fWindowWidth, fWindowHeight + fTitleBarHeight);
			System.Drawing.SizeF sfSize = new System.Drawing.SizeF(pfSize);
			System.Drawing.RectangleF rectTemp = new System.Drawing.RectangleF(pfLocation, sfSize);
			this.Window.Window.SetFrame(rectTemp, true);
        }//CenterWindow


If you use PresentationParameters

If you use PresentationParameters, check all the values you get from this because they were different on Mac than PC so instead of something like:

PresentationParameters presentation = graphics.GraphicsDevice.PresentationParameters;
x = presentation.BackBufferHeight

some suggestions to use instead are:

x = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height

or

x = graphics.PreferredBackBufferHeight;


Making your window miniaturizable

-By default MonoGame does not make the GameWindow Miniaturizable. If you need this, you can fix it easily:

Go into Solution Explorer->MonoGame.Framework.MacOS->MacOS->Game.cs file

*In the Game() method find the line that says:

_mainWindow = new MacGameNSWindow (frame, NSWindowStyle.Titled | NSWindowStyle.Closable, NSBackingStore.Buffered, true);

and add the Miniaturizable style mask so it would look like this:

_mainWindow = new MacGameNSWindow (frame, NSWindowStyle.Titled | NSWindowStyle.Closable | NSWindowStyle.Miniaturizable, NSBackingStore.Buffered, true);

Then go to the GoWindowed() method and change

_mainWindow.StyleMask = NSWindowStyle.Titled | NSWindowStyle.Closable;

to

_mainWindow.StyleMask = NSWindowStyle.Titled | NSWindowStyle.Closable | NSWindowStyle.Miniaturizable;


If you are using a custom cursor image

If you are using a custom cursor image you may notice that when the gamewindow loses focus or resizes, sometimes the image and actual mouse position don’t match until you’ve moved or clicked the mouse. You can fix this easily by doing the following:

Go into Solution Explorer->MonoGame.Framework.MacOS->MacOS->GameWindow.cs file

*Make the SetMousePosition() method public

then put this at the beginning of Game1.Update(),

        	try
        	{
	        	if(Window.Window.IsKeyWindow)
					Window.SetMousePosition(new System.Drawing.PointF(Window.Window.MouseLocationOutsideOfEventStream.X, Window.Window.MouseLocationOutsideOfEventStream.Y));
        	}//try
        	catch(Exception ex){/*Leave blank*/}//catch


If you hook into Form events like FormClosing

If you do things like:

Form f = Form.FromHandle(Window.Handle) as Form;
f.FormClosing += f_FormClosing;

…to hook into window events, it won’t work that way. Instead you could

*Right-Click on your project->Add->New File…->General->Empty Class->Name it WinDelegate->New button

*Replace existing code with something like this:

using System;
using MonoMac.AppKit;

namespace MyNameSpace
{
	public class WinDelegate : NSWindowDelegate
	{
		public WinDelegate ()
		{
		}

		public override bool WindowShouldClose (MonoMac.Foundation.NSObject sender)
		{
			NSAlert alert = NSAlert.WithMessage("Warning!", "Yes", "No", null, "Are you sure you want to close program?");

			var button = alert.RunModal();

			if ( button == 0 )
			{
				return false;
			}//if
			else
			{
				return true;
			}//else
		}//WindowShouldClose
	}//class WinDelegate
}

*Then in Game1.LoadContent() add

this.Window.Window.Delegate = new WinDelegate();


If you want to add a functional menu bar

-According to Apple’s Mac app review guidelines https://developer.apple.com/appstore/mac/resources/approval/guidelines.html
which will supply a link to the User Interface guidelines (https://developer.apple.com/library/mac/#documentation/UserExperience/Conceptual/AppleHIGuidelines/Intro/Intro.html)
The menu bar always contains:

The Apple menu at the far left end (provided by the operating system)
The Spotlight icon at the far right end (provided by the operating system)
The application menu (titled with the active app’s name or an appropriate abbreviation if space is limited)
A Window menu

-Although I have not submitted an app to the Mac App Store yet, it seems that you at least need a Window menu. I think most Mac users are also used to having an Application Menu with About and Quit items. They also may want to quit the application by hitting command+q or minimize by hitting command+m. All of this can be added very easily by doing the following:

*Open XCode4->Create a new XCode Project->Mac OSX->Other->Empty->Next button

*Name it whatever->Next button->choose destination folder->Create button

*File->New->New File…->Mac OSX->User Interface->Main Menu->Next button

*Save as “MainMenu”->Save button

*You can just change the names of or delete any menu items you want and then just save and close XCode. I just left About, Quit and Window->Minimize

*Replace your game’s existing MainMenu.xib file with this new one. This just automatically works. It’s great.


If you want to create a custom About window and/or a custom application icon

You won’t actually create them in this step, but you will prepare your project to read them properly when they are created.

-In Solution Explorer, double click on the Info.plist file

-Right click on a blank area and select “New Key” ->Rename “newNode” to “NSHumanReadableCopyright” and put the string you want to appear in About Window under the Value field.

-Right click on a blank area and select “New Key” ->Rename “newNode” to “CFBundleIconFile” and in the value field put the name of your future icon file (which you haven’t created yet). The file will be, for example, MyGame.icns but in this value field just put “MyGame” DON’T put the extension.


Some Mac users only have a mouse with one button

-You have to also take into account that some Mac user may still have a mouse with only one button. So you have to remember to make ctrl+leftClick equal rightClick. I’m sure you’ll be able to figure this out. Just wanted to remind ya’ll.


If you want to hook into the ApplicationShouldClose event

-If you added the Quit menu item in the previous step, you may now also have to worry about capturing the application being closed and probably apply the same logic that you did when you captured the window being closed. If so, just override this method in your Program.cs file in the AppDelegate class:

public override NSApplicationTerminateReply ApplicationShouldTerminate (NSApplication sender)

but instead of returning false or true like in WindowShouldClose(), return

NSApplicationTerminateReply.Cancel;

or

NSApplicationTerminateReply.Now;


Localization

If your game has more than one .resx file used for localized strings, you have a lot of extra work to do. The only way I got it to work was by creating .strings files. You will have to take all the values from your .resx files and pass them over to the .strings files. It wasn’t that hard. You can figure out your own way to do it. Personally, I passed the information column by column from the .resx file viewer in Visual C# Express 2010 to an Excel file. Then to a regular .txt file. Then I wrote a little tool in C# to convert the strings to the proper format required by the .strings files. .strings files are nothing more than regular text files with the .strings extension that have key value pairs written out like this:
“MyKeyName1″ = “This is the string value for MyKeyName1 in English”;
“MyKeyName2″ = “This is the string value for MyKeyName2 in English”;
that’s it. Make sure to include all the double quotes. Then save it to a file called, for example, MyStringsFile.strings

*Create a separate file for all other languages you require, but all the files must be named the same, so keep them in separate folders for now.

*Once you have your complete .strings files you have to add new folders to your project, one for each language you plan to support including the default one, and they have to have specific names. For example my default language is English but I also want to support Spanish, so in your Solution Explorer->Right click on your project->Add->New Folder-> and create 2 folders:
en.lproj
es.lproj

*the “lproj” ending is required

*the 2 character language prefix has to be specific. According to http://developer.apple.com/library/mac/#documentation/MacOSX/Conceptual/BPInternational/Articles/LanguageDesignations.html#//apple_ref/doc/uid/20002144-BBCEGGFF
the complete list of 2 character language codes can be found on the link they posted, but that link is dead. And I couldn’t find a really good one so it’s up to you to find the code on your own.

*Paste your completed MyStringsFile.strings files (ha!) into their respective folders and add them to the project.

*In solution explorer->Right click on the .strings files ->Build Action->Content

*Now the final, but most time consuming, part is that you have to get the value for all your strings like this:

MonoMac.Foundation.NSBundle.MainBundle.LocalizedString("MyKeyName1", "", "MyStringsFile");



Well, aside from the customized About Window and the Application Icon which I cover in the next part, the changes I listed were all I needed to make my game run without any problems. If you still have problems running the game, great places for research are the MonoGame and MonoMac samples, also their respective message boards, developer.apple.com, or you can post a comment here and I can try to help (time permitting!).

4 thoughts on “Porting your Windows XNA game to MacOS using MonoGame and MonoMac Part 3

    • Hey. If you look at Part 3 of this guide, near the top the section titled “Moving and resizing your window” it details a method for centering the window. Hope it helps.

  1. Hello there — first off, your tutorials are a big help and totally worth the time you spent to write them up. You did a great job and have helped me a lot — THANKS!

    Question about Full Screen Mode

    When I set full screen at 640 x 480 it seems to be keeping the native res of my monitor (1920 x 1200), and displaying the smaller 640 x 480 content in the top left of the screen. I want a true resolution switch to 640 x 480. This works on PC under VC2010 Express, but not on Mac under MonoDevelop/MonoGame (so far).

    Any idea why full screen mode isn’t really changing to proper full 640 x 480?

    I am setting the resolution to run full screen 640 x 480 like so:

    public Game1()
    {
    graphics = new GraphicsDeviceManager(this);
    content = new ContentManager(Services);
    graphics.PreferredBackBufferWidth = 640;
    graphics.PreferredBackBufferHeight = 480;
    graphics.PreferMultiSampling = false;
    graphics.IsFullScreen = true;
    content.RootDirectory = “Content”;
    }

    Thanks again for your help!

    Sincerely,
    Joshua

    • Hey Joshua, I did a bit of research on this and couldn’t really figure it out. I found this which seems like what you need. But I don’t know exactly how to implement a MonoMac version of this. While I was developing my game I ran into lots of issues that I resolved by looking at the Apple / Cocoa documentation like the one I’ve supplied. There is almost always a MonoMac version of every Cocoa method. You could try asking your question on the MonoGame forums but I think your best best is asking this on StackOverflow. Don’t even mention MonoGame when posting your question. Because this is something that, if possible, will be solved through MonoMac. Even if MonoGame addresses this issue, they will just end up making a MonoMac call anyway. I wish I had more time to research this for you, but I’m so busy at the moment. Good luck!

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>