Extending UnityAppController on iOS

For my next trick: the same thing as in the previous tutorial, but this time for iOS. The rules remain the same, so it wouldn’t hurt to repeat a description of what it is I’m trying to build:

“So the idea is to build a game that can be triggered from an url on a webpage, twitter message, email, the works…

The url will pass some parameters to the game (usually a token generated by a server but in this tutorial I’ll use good old value pairs a blind monkey could change.)

The game must respond to this URL whether it’s already running or not. And this is the crux of the matter. I need access to the delegate methods the OS so helpfully dispatches.”

If you did the previous tutorial you could use the same project you created for it. But I’ll start from scratch.

Here are the steps.

– Create an AwesomeGame project in Unity.

– Set Build Settings to iOS, and enter the name and bundle id com.rengelbert.AwesomeGame.

– It will have one scene and one GameObject called MyAwesomeGameLinkLoader.

– That GameObject will have a C# script called LinkLoader.

– That LinkLoader script will have one method called LoadLink with one string parameter called message.

1
2
public void LoadLink (string message) {
}

– Run a build.

– Create the folder Assets/Editor.

– Create a script inside the Editor folder called InfoListEditor.cs.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using UnityEngine;
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEditor.iOS.Xcode;
using System.IO;
 
public class InfoListEditor {
 
	[PostProcessBuild]
	public static void EditInfoPlist (BuildTarget buildTarget, string pathToBuiltProject) {
 
		if (buildTarget == BuildTarget.iOS) {
 
			var path = pathToBuiltProject + "/Info.plist";
			var plist = new PlistDocument();
			plist.ReadFromString(File.ReadAllText(path));
 
			PlistElementDict root = plist.root;
			PlistElementArray urls = rootDict.CreateArray ("CFBundleURLTypes");
			PlistElementDict dic =  urls.AddDict ();
			PlistElementArray scheme = dic.CreateArray ("CFBundleURLSchemes");
			scheme.AddString ("awesomegame");
			dic.SetString ("CFBundleURLName", "com.rengelbert.awesomegame");
 
			File.WriteAllText(plistPath, plist.WriteToString());
		}
	}
}

– This will setup the iOS equivalent to a browsable intent in Android. It’s a script used in the post build process, once the Xcode project is generate. It loads the brand new Info.plist file and adds elements to it. I’m adding these elements:

1
2
3
4
5
6
7
8
9
10
11
<key>CFBundleURLTypes</key>
<array>
<dict>
	<key>CFBundleURLSchemes</key>
	<array>
		<string>wordface</string>
	</array>
	<key>CFBundleURLName</key>
	<string>com.gamesys.wordface</string>
</dict>
</array>

Which will appear inside Xcode as a URLTypes key in the Info.plist dictionary.

– Open/Create the folder Assets/Plugins/iOS.

– Create two text files called AwesomeAppController.h and AwesomeAppController.mm (it’s possible of course to have the interface inside the implementation, so it’s up to you if you want to create two files here or not).

– Open those files in a text editor, or inside Xcode.

– The interface (the .h file) will look like this:

1
2
3
4
5
6
7
#import "UnityAppController.h"
 
@interface AwesomeAppController : UnityAppController
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions;
- (BOOL)application:(UIApplication*)application openURL:(NSURL*)url sourceApplication:(NSString*)sourceApplication annotation:(id)annotation;
 
@end

– The implementation like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#import "AwesomeAppController.h"
 
@implementation AwesomeAppController
 
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
    NSArray *keyArray = [launchOptions allKeys];
    if ([launchOptions objectForKey:[keyArray objectAtIndex:0]]!=nil) {
        NSURL *url = [launchOptions objectForKey:[keyArray objectAtIndex:0]];
        NSString *urlString = url.absoluteString;
        if (urlString != NULL) {
            UnitySendMessage("MultiplayerManager", "GetMultiplayerData", [urlString UTF8String]);
        }
    }
 
    return [super application:application didFinishLaunchingWithOptions:launchOptions ];
}
 
- (BOOL)application:(UIApplication*)application openURL:(NSURL*)url sourceApplication:(NSString*)sourceApplication annotation:(id)annotation
{
 
    NSString *urlString = url.absoluteString;
    if (urlString != NULL) {
        UnitySendMessage("MultiplayerManager", "GetMultiplayerData", [urlString UTF8String]);
    }
 
    return [super application:application openURL:url sourceApplication:sourceApplication annotation:annotation];
}
@end
 
IMPL_APP_CONTROLLER_SUBCLASS(AwesomeAppController)

– Once again I’m only overriding the delegate methods I need. The last line is the key one:

1
IMPL_APP_CONTROLLER_SUBCLASS(AwesomeAppController)

It makes the switch between controllers, dropping the default UnityAppController to the new one.

– Objective-c classes saved inside Plugins/iOS will appear in the XCode project inside PROJECT_ROOT/Libraries/Plugins.

– In the extended AppController I grab the URL data in two delegates. The didFinishLaunchingWithOptions for when the app was started by clicking the link, and the application:openURL for when the app returns to the foreground by clicking the link.

– If everything works, you should be able to load the game from a link like this:

1
<a href="awesomegame://loadthis/">PLAY GAME</a>

Or with extra parameters:

1
<a href="awesomegame://loadthis/?myParam=1&otherParam=false&">PLAY GAME</a>

– The method LoadLink in Unity, will receive the URL and you can look for parameter with something like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var dchar = new char[] { '?' };
var args = message.Split (dchar);
var values = args [1];
dchar = new char[] { '=', '&' };
var dictionaryValues = values.Split (dchar);
var i = 0;
var dataDictionary = new Dictionary<string, string> ();
 
while (i < dictionaryValues.Length) {
	if (i % 2 == 0)
		dataDictionary.Add (dictionaryValues [i], "");
	else 
		dataDictionary [dictionaryValues [i <p>- 1]] = dictionaryValues [i];
	i++;
}

– And this should work. Create a web page somewhere, like inside your public dropbox folder, add a link the the game. Inside the same phone where you have the app installed, click on that link and…. magic!

One thought to “Extending UnityAppController on iOS”

  1. Hi!
    I have successfully used the info from this page for a personal project, really awesome help.

    But I was wondering how that .h and .mm script would be for a MacOS build? I tried to simply copy it to plugins/x86_64 (along with URL schemes in the plist for macos) but that didnĀ“t do the trick unfortunately. Any tips on how this would work on MacOS (and perhaps other OSses)?

Comments are closed.