Saturday, September 21, 2013

A Playstation Mobile/MonoGame Experience!

I said I'd post more details about the issues I'd had with the PSM/Monogame port and here they are!

In this post I'm going to try and outline some of the problems I faced when working with the PSM port of MonoGame.

First thing you'll want to do is read over barnard666's post in this thread. It was very helpful in getting set up, and this post assumes you've at least skimmed the numbered items :) 

The steps given in the forum thread linked above went a long way to getting the game "running" on the emulator, but to get content to actually deploy to the Vita, you'll also want to check Include in Deployment and Use Relative Path on each content items properties. I also changed the location dropdown to the shared application path root, but don't know if that was necessary.

One important caveat, is that the emulator and the Vita behave a bit differently with content loading. Specifically, you must match the case of your files in order for them to work on the Vita (it'll happily just crash), even though they load fine on the emulator. The lions share of the issues I had getting things working at all on the Vita was with content casing (Loading "archer" when the filename is "Archer").

The next, and easily most annoying issue, is with non-standard content loading. I use XML pretty substantially so this was sort of a big issue. The problem, as I understand it, is that Mono does some very aggressive optimisations in the Linker, happily removing any code that it doesn't see being used... Like, ContentReaders. The solution for simple, existing .Net types (Dictionaries and such) is to go into the ContentTypeReaderManager file in the Monogame PSMobile project. Find the if statement under this comment:

// Trick to prevent the linker removing the code, but not actually execute the code

And instantiate a new instance of whatever type of reader you're trying to use, e.g.:

var hDictionaryContentJunkReader = new DictionaryReader();

To resolve this issue for custom ContentTypeReaders that are part of your project, I've found instantiating one and calling ToString() on it somewhere seems to be enough for the optimiser to leave it alone.

The next issue I had was with loading folders full of content. Something like:

var d = new DirectoryInfo(game.Content.RootDirectory + "/Audio/Effects");
foreach (var e in d.EnumerateFiles("*", SearchOption.TopDirectoryOnly))
   var effect = game.Content.Load("Audio/Effects/" + e.Name.Replace(e.Extension, ""));

is no good. Why? Because the root folder on Vita doesn't contain your Content folder. The fix? Easy, prepend they path with "Application/" See? Easy!

Finding all these issues turned out to be quite difficult however. The problem was, I was never able to actually get the debugger to attach in a loading crash scenario (and honestly, only fleetingly at other times), and when it crashes it just terminates with no logging, no history or anything, it's just gone. Luckily though, the PSM SDK offers us some tools to get some level of debug info.

With just a little hackery, we can leverage Sce.PlayStation.HighLevel.UI to get the MessageDialog functionality right within the game.

The first thing to do is to expose PSM's own GraphicsContext object from within the GraphicsDevice. I just went into the GraphicsDevice class in the Monogame PSMobile  project and changed _graphics to public, but there's obviously cleaner ways to go about it.
The next steps are a lot simpler In your core game's class (Game1.cs for many of you):
  1. Add the reference to Sce.PlayStation.HighLevel.UI;
  2. Add usings for the above and also Input
    1. using Sce.PlayStation.HighLevel.UI;
    2. using PSI = Sce.PlayStation.Core.Input; //I added the alias to avoid collisions
  3. In your initialize method, add the following line:
    1. UISystem.Initialize(graphics.GraphicsDevice._graphics); //Replace the _graphics with whatever you came up with to expose the context
  4. In your update method:
    1. UISystem.Update(new List()); //You can also use PSM's input code to pass the actual state here if desired.
  5. In your Draw Method, toward the end:
    1. UISystem.Render();
You can also go through the motions to setup proper Touch input handling so you can dismiss the error, but I didn't bother, if a dialog popped up, it meant the games state was wrecked anyway (because, exception).

Now to actually display an error is very simple:
try{ //YourCode }
catch(Exception ex){
    MessageDialog.CreateAndShow(MessageDialogStyle.Ok, "ERROR!", ex.Message);

This little hack helped immeasurably in running down bad casing, broken importers and anything else that blew up (note, you'll need to have your game continue to run without exception while the dialog is showing, which, depending on your games flow can be difficult).

No comments:

Post a Comment