Tuesday, September 15, 2015

Candy Kid iOS Port

In the previous post, we introduced Candy Kid: a simple maze chase video game re-written in XNA 4.0.
We outlined the tasks to port Candy Kid to Android platform using MonoGame. Now we will focus on iOS.

Let's check it out!

Setup
This post assumes you have Mac OS/X and XCode installed. Also, that you have an Apple Developer account and relevant Certificate(s) and Provisioning Profiles installed to deploy code to iOS devices.

MonoGame
Download and install MonoGame for MacOS. Here is tutorial to create cross platform MonoGame.

Xamarin Studio
Download and install Xamarin Studio. Setup an account; a trial version can be used just to get started!
Launch Xamarin Studio | Click Login. Enter your email + password and click the "Create Account" link.

From here you will be prompted to create a Xamarin account: enter name, email address and password. Check "Start 30-day Xamarin trial immediately". Click Accept. Log in to Xamarin Studio to activate a/c.

Choose File menu | New | Solution. Expand Other | Miscellaneous | MonoGame iPhone/iPad Application.
Project Name: CandyKid.iOS. Choose project Location. Click Create button. Update changes to Main.cs
using System;
using Foundation;
using UIKit;

namespace WindowsGame
{
  [Register ("AppDelegate")]
  class Program : UIApplicationDelegate 
  {
    private WindowsGame.AnGame game;

    public override void FinishedLaunching (UIApplication app)
    {
      game = new WindowsGame.AnGame();
      game.Run();
    }

    static void Main (string [] args)
    {
      UIApplication.Main (args,null,"AppDelegate");
    }
  }
}
Before writing any new code or porting any existing, ensure the following tasks are completed:
  • Rename Game1.cs to AnGame.cs
  • Rename all references from Game1 to AnGame
  • Use default root namespace of WindowsGame
  • Delete Default.png and GameThumbnail.png
Build solution. Error: Entitlements.plist template 'Entitlements.plist' not found
Right click CandyKid.iOS project | Options | Build | iOS Bundle Signing. Remove "Entitlements.plist"
Build solution. Error: The minimum deployment for Unified application is 5.1.1, current target is '4.2'
Right click CandyKid.iOS project | Options | Build | iOS Application. Change Deployment Target to 5.2.
Rebuild solution fine. Attach iOS device. Click Play button. The default template code should deploy ok.

Libraries
Candy Kid references the following libraries: Ninject for IoC Container and Xml.Linq for Serialization.
Right click References | Edit References... "All" tab | Scroll to bottom. Check "System.Xml.Linq". OK.

Packages
Right click Packages folder. Right click "MonoGame.Framework.iOS" node. Choose Update as necessary.

Update: at the time of this writing the latest MonoGame.Framework.iOS package is version 3.4.0.459.
However, if you would like to target iOS 9.0 then you can manually download version 3.5.0.628 here.

Right click Packages folder | Add Packages... Enter "Portable Ninject" into Search bar on top right.
Select "Ninject for Portable Class Libraries" | Add Package. Portable Ninject reference also added.

Project Properties
Right click CandyKid.iOS project. Choose Options. Ensure the following options are set on sub tabs:

iOS Application
 Application Name Candy Kid
 Bundle Identifier Enter bundle identifier
 Version 1.0.0
 Build 1.0.0
 Devices Universal
 Deployment Target 5.2
 Device Orientation Landscape Left | Landscape Right
 Status Bar Style Hide during application launch
Also, update AssemblyInfo.cs file under Properties folder. Synchronize "1.0.0" as the AssemblyVersion.

Compiler
 Define Symbols IOS
Repeat all settings for both Debug and Release build configurations!

Code
Import all C#/.NET code from original XNA 4.0 project. Here are some options to exit game on iOS:
// Option #1. P/Invoke exit().
#if IOS
  [DllImport("__Internal", EntryPoint = "exit")]
  public static extern void exit(int status);

  exit(0);
#endif

// Option #2. NSThread.exit().
#if IOS
  Foundation.NSThread.exit();
#endif

// Option #3. Throw an exception.
#if IOS
  throw new System.DivideByZeroException();
#endif
Also, if you'd like to upsell full version of your game to unlock then open the url in the native browser:
#if IOS
  string url = @"Enter URL to full version of game";
  UIKit.UIApplication.SharedApplication.OpenUrl(Foundation.NSUrl.FromString(url));
#endif
Content
MonoGame iPhone/iPad Application templates add a "Content" folder as default location for all Content.
Right click "Content" folder | Add New Folder. Candy Kid uses the 4x following subfolders (listed below):

Data
Contains flat TXT + XML data files to be consumed by the game. Do not need to be built as XNB files!
Right click each data file | Properties | Build action: Content | Copy to output directory: Copy if newer
Fonts
Font XNB files may need to be rebuilt for Mac OS/X using the MonoGame Pipeline; instructions below:
Right click each font file | Properties | Build action: Content | Copy to output directory: Copy if newer

Sound
Sound effect XNB files may need to be rebuilt for Mac OS/X using the MonoGame Pipeline; see below:
Right click sound effect | Properties | Build action: Content | Copy to output directory: Copy if newer

Song source MP3 files can usually be used as original format; should not need be rebuilt for Mac OS/X.
Right click song file | Properties | Build action: BundleResource | Copy to output directory: Do not copy
Note: there may be issues MP3 songs on low-end devices: MediaPlayer API may cause game to crash!
Also, it may be required to use alias with MediaPlayer API; for example, you may need to write code:
using Microsoft.Xna.Framework.Media;
using MediaPlayerX = Microsoft.Xna.Framework.Media.MediaPlayer;

public class SoundManager
{
  public void StartMusic()
  {
    if (MediaState.Playing != MediaPlayerX.State)
    {
      MediaPlayerX.Play(Assets.Song);
      MediaPlayerX.IsRepeating = true;
    }
  }
  public void PauseMusic()
  {
    if (MediaState.Playing == MediaPlayerX.State)
    {
      MediaPlayerX.Pause();
    }
  }
  public void ResumeMusic()
  {
    if (MediaState.Paused == MediaPlayerX.State)
    {
      MediaPlayerX.Resume();
    }
  }
  public void StopMusic()
  {
    if (MediaState.Playing == MediaPlayerX.State)
    {
      MediaPlayerX.Stop();
    }
  }
}
Finally, it has been noted that the MediaPlayer.Play(Song) API may cause delay when looping MP3 songs;
Alternative: replace MediaPlayer(Song) with SoundEffectInstance, however XNB file will be much larger!

Textures
Copy all source texture images, for example: BMP, JPG, PNG files from the original XNA game project.
Right click texture | Properties | Build action: BundleResource | Copy to output directory: Do not copy

Resources
Update all relevant Universal Icons, Launch Images and iTunes Artwork for iOS device compatibility.
Right click CandyKid.iOS project. Choose Options | iOS Application | Set all icons and images here:

App Icons
 iPhone iOS 5,6  57x57 Icon.png
 iPhone @2x iOS 5,6  114x114 Icon@2x.png
 iPhone @2x iOS 7  120x120 Icon-60@2x.png
 iPad iOS 5,6  72x72 Icon-72.png
 iPad @2x iOS 5,6  144x144 Icon-72@2x.png
 iPad iOS 7  76x76 Icon-76.png
 iPad @2x iOS 7  152x152 Icon-76@2x.png

Spotlight & Settings Icons
 iPhone Spotlight iOS 5,6  29x29 Icon-Small.png
 iPhone Spotlight @2x iOS 5,6  58x58 Icon-Small@2x.png
 iPad Spotlight iOS 5,6  50x50 Icon-Small-50.png
 iPad Spotlight @2x iOS 5,6  100x100 Icon-Small-50@2x.png
 Spotlight iOS 7  40x40 Icon-Small-40.png
 Spotlight @2x iOS 7  80x80 Icon-Small-40@2x.png

iPhone Launch Images
 Standard 320x480 Default.png
 Retina (3.5-inch) 640x960 Default.png
 Retina (4.0-inch) 640x1136 Default-568h@2x.png
Important: if Retina (4.0-inch) launch image is not set then iPhone 5 resolution will not be supported!

iPad Launch Images
 Portait 768x1024 Default-Portrait.png
 Landscape 1024x768 Default-Landscape.png
 Retina Portait 1536x2048 Default-Portrait@2x.png
 Retina Landscape 2048x1536 Default-Landscape@2x.png

iTunes Artwork
 Standard 512x512  iTunesArtwork.png
 Retina 1024x1024  iTunesArtwork@2x.png

MonoGame Pipeline
If you require MonoGame to build platform specific XNB content files then use the MonoGame Pipeline
Choose Finder | Applications | Pipeline. MonoGame Pipeline launches. Choose File | New. Save project.

Right click project | Add | Existing Item. Add an existing content file e.g. Emulogic.spritefont | Open.
Right click file | Rebuild. An exception may be thrown but the XNB file is still available in bin folder.

Right click project | Add | Existing Item. Add an existing content file e.g. Celebrate.wav file | Open.
Right click file | Rebuild. Assuming Pipeline is installed correctly, the XNB file available in bin folder.

Access Denied
When using MonoGame Pipeline tool, you may receive Access Denied error, especially with MP3 files.
This may be because the ffmpeg binary (and ffprobe binary) needs executable permissions to be set.

Launch Terminal window to manually change executable permissions. Type the following (as root):
cd /Applications/Pipeline.app/Contents/MonoBundle
chmod +x ffmpeg
chmod +x ffprobe

Deployment
Attach iOS device to Mac OS/X. Xamarin Studio, click arrow next to build configuration | Select device.
Click Play button. iOS device launches game as "Evaluation Software". Build will expire within 24 hours.

Alternative: right click CandyKid.iOS project | Archive for Publishing. Click Sign and Distribute button.
Select iOS Distribution Channel as "AdHoc" | Next. Select Signing Identity and Provisioning Profile.
Choose Next to Publish as Ad Hoc. Click Publish button. Choose Location | Save. Now reveal in Finder.
Double click IPA binary file. iTunes launches ok. Select device | Apps | Candy Kid. Click Install | Apply.
Summary
That concludes the Candy Kid port to iOS platform. Outstanding: Candy Kid published on iOS / Android!