• Bugs
  • MonoGame / XNA - Lacks android support

For a bit of background, we use your application to create animations for our games, which primarily are made in HTML5 / Javascript. However, we are looking to port one of our existing games over to MonoGame in order to build an Android and iOS app. I haven't got as far as looking into iOS, to start I'd just like to get a spine animation displaying on Android.

I ran into a few problems with loading the files, which wasn't too bad to get around by simply extending Atlas and SkeletonJson and ensuring the load functions used the correct API for Android. All good with a spine animation from the game, it loaded my files no problem, but nothing displayed. I decided to try using the example spine files from the MonoGame example (worked on PC no problem when I tested). So I made sure I used the same code and files as the example (with the exception of my extended classes).

I now get an error on json.readSkeletonData() , which is identical except for an overridden readSkeleton method, which then calls the base method.

public override SkeletonData ReadSkeletonData(string path)
{
    using (StreamReader reader = new StreamReader(Game.Activity.Assets.Open(path)))
    {
        SkeletonData skeletonData = ReadSkeletonData(reader);
        skeletonData.Name = Path.GetFileNameWithoutExtension(path);
        return skeletonData;
    }
}

The error log :

System.Exception: Error reading attachment: coin-front-shine-logo, skin: default

If I did something wrong in extending the base classes I don't see where because it fails in the method in the base class and uses very little custom code. It seems to fail at some point in the overloaded method. With some files it throws the exception on reading the attachment as here (seems to be related to reading the skins), and on others it will fail else where such as on reading the first animation.

I'm sure it must be pretty common for people to build native mobile games with spine outside of Unity / Unreal. MonoGame being cross-platform it seems to be one of the main alternatives. Does anyone have any experience or can point me to an example?

Also should we expect the XNA/MonoGame runtime to support mobile out the box? I see in the source code Windows mobile support (which of course hardly anyone uses), yet no android / ios. I appreciate this is probably due to the runtime actually being for XNA and not MonoGame, it just doesn't seem very useful.

Related Discussions
...

We haven't setup iOS/Android examples for MonoGame yet, as there weren't any requests for it until now. I've extended your issue here: [monogame] iOS/Android sample · #1256

Regarding the errors you are seeing when trying to load skeleton files: that indicates that the Spine Runtimes version you are using is not the same as the version of the skeleton files you are trying to read. Which runtime version are you using (should be the latest from the 3.7 branch on GitHub)?

Your extension of the base class looks correct to me. Could you post the full code you used to load both the atlas and the .json file?

For platforms like iOS/Android that don't support normal file access via FileStream (like here spine-runtimes/SkeletonJson.cs at 3.7), the public SkeletonData ReadSkeletonData (TextReader reader) is the preferred way to load data, since the TextReader can come from anywhere (Android assets, iOS bundle, etc.). Similarly for Atlas, you want to use this constructor: spine-runtimes/Atlas.cs at 3.7 together with an XNATextureLoader. For the XNATextureLoader to work, you might have to modify the Utils class: spine-runtimes/Util.cs at 3.7.

I'm not too familiar with MonoGame. Does it have a cross-platform API for loading files/textures? From your code above, it appears that you need separate APIs for each platform, .e.g. Game.Activity.Assets.Open().

I am so sorry! I'm such an idiot... I'd copy pasted code over in order to extend the SkeletonJson class, and forgot to change this(class constructor) to base :x

Big thanks for taking the time though, it kicked me into going through every bit with a fine tooth comb when you said version mismatch 🙂

Regarding cross-platform stuff, I think the way you have it is probably right for MonoGame. For cross-platform your meant to have a shared project for shared game code, then cross-platform projects for each target to handle platform specific things. So I don't really see a problem having AndroidAtlas in the Android project with some small tweaks to load the files correctly, then for example a separate iAtlas in an iOS project. I've only used it for PC in the past though, so someone else could probably say better if this is the right way.

One minor thing, I did change the ReadSkeletonData to virtual so I could override it, I think this was probably necessary? So I'm currently using a customised .dll

Hah, no worries, such things happen to me regularly 🙂

The MonoGame setup is pretty similar to libGDX (a Java based thing like MonoGame). In libGDX, the file reading/writing APIs are abstracted across platforms, so we don't need to do anything special on desktop, iOS, and Android. It's all the same method. E.g. you'd do something like:

SkeletonJson().read(Gdx.files.internal("skeleton.json")

The respective backend (desktop, iOS, Android) would implement the actual internal() method and look the file up in the appropriate place.

For the MonoGame (or C# runtime in general), the abstraction is different. The constructor of classes requiring a file will take various (stream) readers, and the call-site (i.e. you 😃) are responsible to open that reader in the platform specific way. I'd love to have an abstraction like in libGDX for MonoGame, but it seems that's not easily achieved.

In your case, I'd strongly suggest not to have per-platform SkeletonJson/Atlas implementations, but instead create a small utility class that can return a reader for a file. You can then override the methods of that file i/o class per platform (single class with platform specific #ifdefs, or simple interface -> implementation hierarchy). When creating a SkeletonJson/Atlas, call the file i/o utility with some path, and it will magically return a reader constructed using the platform mechanism for file i/o.

This way, you can update the Spine Runtimes without having to backport your derrived classes all the time.

Anyways, thanks for all your feedback!