Avoid loss of hair while coding Unity3D plugins for mobile
1. Valerio “Lotti” Riva – Interactive Project
valerio.riva@gmail.com
@ValerioRiva
http://it.linkedin.com/in/valerioriva/
Avoid loss of hair while coding
Unity3D plugins for mobile
ROME 24 June 2014 – Valerio Riva
Codemotion Tech Meetup #4 – Roma Summer Edition
2. Nice to meet you!
• Web / Game developer
• Recent works @ Interactive Project
• World Cup Juggler
• OverVolt: crazy slot cars
ROME 24 June 2014 – Valerio Riva
2
3. Why develop a Unity3D mobile plugin?
• Access to device native features
• Implement third-party SDK (analytics, advertising,
in-app purchases, game services, etc.)
• Save/Earn money
• Personal growth
ROME 24 June 2014 – Valerio Riva
3
4. Extending Unity 4.x
• Unity supports C/C++ libraries. “extern”-alized
functions/methods can be called from C#
• All plugins must be placed inside “Assets/Plugins”
folder
• Platform-dependent plugins must be placed inside
specific folders (x86, x86_64, Android, iOS, WP8, Metro,
etc.)
• Available only on Unity3D Pro/Mobile
ROME 24 June 2014 – Valerio Riva
4
5. Extending Unity (iOS)
• Call externalized Object-C methods
• Must wraps third-party SDK if their methods are not
externalized
• Gameobjects can receive messages from native code
• Receiver methods declared on GO’s components must
have only 1 string parameter as signature
ROME 24 June 2014 – Valerio Riva
5
6. Extending Unity (Android)
• Use JNI (Java Native Interface), Unity provides Helper
classes
• Call native methods directly from Unity
• Gameobjects can receive messages from native code
• Receiver methods declared on GO’s components must have
only 1 string parameter as signature
• On specific cases, Unity Player activity must be extended
• Android Manifest editing is often required
ROME 24 June 2014 – Valerio Riva
6
7. Extending Unity (WP8)
• Access native code directly from Unity
• Use of callbacks to return data from native code
• Unity’s Mono (v2.6) doesn’t support .NET >= 4.0
• Artefacts are needed to use .NET >= 4.0 libaries
• “Always” needs a fake and a real plugin – Unity will
overwrite fake one with the real one automatically
• In specific cases, write a plugin is a monkey job
ROME 24 June 2014 – Valerio Riva
7
8. Extending Unity (remarks)
• Scripting define symbols are your friends
• Native calls are CPU intensive
• Provide fake results for in-editor usage
• Every native UI call must run inside native UI thread
• Every callback must run inside Unity thread (WP8)
• Save time by testing plugin on a native app
• Remember to include Unity library if needed
• classes.jar
• UnityEngine.dll
ROME 24 June 2014 – Valerio Riva
8
9. Case study: Flurry plugin
• Wrap Flurry SDK to made it accessible from Unity
• Flurry SDK is simple to use, just call static methods
(Advertising, In-App Purchase, …, are more complex plugin)
• We have to code wrappers for each platform
• Place platforms SKDs on the right directories
• FlurryAnalytics-4.0.0.jar -> Plugins/Android/
• libFlurry_5.0.0.a -> Plugins/iOS/
• FlurryWP8SDK.dll -> Plugins/WP8/
• FlurryWP8SDK.dll -> Plugins/ (the WP8 fake one)
ROME 24 June 2014 – Valerio Riva
9
10. Flurry plugin (iOS)
• Flurry SDK is not “extern”-alized
• Dictionary<string, string> must be translated somewhat to
NSMutableDictionary
• Each KeyValuePair<string,string> are concatenated to form a single
string
ROME 24 June 2014 – Valerio Riva
10
//FlurryiOS.h - created by PRADA Hsiung on 13/3/8.
extern "C" {
void FlurryiOS_startSession(unsigned char* apiKey);
void FlurryiOS_setEventLoggingEnabled(BOOL bEnabled);
void FlurryiOS_logEventWithParameters(unsigned char* eventId,unsigned
char *parameters);
}
14. Flurry plugin (WP8)
• Flurry SDK is compiled with .NET 4.5, import it and Unity will go
mad!
• We need the “fake & real” library approach, but…
• Using Flurry SDK doesn’t involve use of complex logic or complex
user defined classes…
• … we can use the downloaded FlurryWP8SDK.dll as “real”
• and code only the fake dll!
Yes, this is the particular case
where you can bring in monkeys!
ROME 24 June 2014 – Valerio Riva
14
15. Flurry plugin (WP8)
using FlurryWP8SDK.Models;
using System;
using System.Collections.Generic;
namespace FlurryWP8SDK.Models {
public enum Gender { Unknown = -1, Female = 0, Male = 1 }
public class Parameter {
public Parameter(string name, string value) {
Name = name;
Value = value;
}
public string Name { get; set; }
public string Value { get; set; }
}
}
namespace FlurryWP8SDK {
public sealed class Api {
private static Api instance = null;
public static Api Current { get { return instance; } }
public void DummyInitiator() {}
public static void EndSession() {}
public static void EndTimedEvent(string eventName) {}
public static void EndTimedEvent(string eventName, List<Parameter> parameters) {}
public static void LogEvent(string eventName) {}
public static void LogEvent(string eventName, List<Parameter> parameters, bool timed) {}
public static void SetAge(int age) {}
public static void SetGender(Gender gender) {}
public static void SetLocation(double latitude, double longitude, float accuracy) {}
public static void SetSessionContinueSeconds(int seconds) {}
public static void StartSession(string apiKey) {}
}
}
ROME 24 June 2014 – Valerio Riva
15
That was easy, now
give me peanuts!
16. Flurry plugin (Android)
• As said before, Flurry doesn’t involve complex logic…
• … so we save time and wrote plugin directly in Unity using JNI
Helper classes
ROME 24 June 2014 – Valerio Riva
16
public Flurry StartSession(string apiKey) {
#if !UNITY_EDITOR && UNITY_WP8
Api.StartSession(apiKey);
#elif !UNITY_EDITOR && UNITY_ANDROID
using(AndroidJavaClass cls_UnityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
using(AndroidJavaObject obj_Activity = cls_UnityPlayer.GetStatic<AndroidJavaObject>("currentActivity"))
using(AndroidJavaClass cls_FlurryAgent = new AndroidJavaClass("com.flurry.android.FlurryAgent")) {
cls_FlurryAgent.CallStatic("onStartSession", obj_Activity, apiKey);
}
#elif !UNITY_EDITOR && UNITY_IPHONE
FlurryiOS_startSession(apiKey);
#endif
return this;
}
17. Flurry plugin (Android)
ROME 24 June 2014 – Valerio Riva
17
public Flurry LogEvent(string eventId, Dictionary<string,string> parameters, bool timed) {
#if !UNITY_EDITOR && UNITY_WP8
List<Parameter> p = new List<Parameter>();
foreach(KeyValuePair<string,string> i in parameters) {
p.Add(new Parameter(i.Key,i.Value));
}
Api.LogEvent(eventId, p, timed);
#elif !UNITY_EDITOR && UNITY_ANDROID
using(AndroidJavaObject obj_HashMap = new AndroidJavaObject("java.util.HashMap")) {
// Call 'put' via the JNI instead of using helper classes to avoid: "JNI: Init'd AndroidJavaObject with null ptr!"
IntPtr method_Put = AndroidJNIHelper.GetMethodID(obj_HashMap.GetRawClass(), "put“,
"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
object[] args = new object[2];
foreach(KeyValuePair<string, string> kvp in parameters) {
using(AndroidJavaObject k = new AndroidJavaObject("java.lang.String", kvp.Key))
using(AndroidJavaObject v = new AndroidJavaObject("java.lang.String", kvp.Value)) {
args[0] = k;
args[1] = v;
AndroidJNI.CallObjectMethod(obj_HashMap.GetRawObject(), method_Put, AndroidJNIHelper.CreateJNIArgArray(args));
}
}
cls_FlurryAgent.CallStatic("logEvent", eventId, obj_HashMap, timed);
}
#elif !UNITY_EDITOR && UNITY_IPHONE
FlurryiOS_logEventWithParametersTimed(eventId, dictionaryToText(parameters));
#endif
return this;
}
18. and for more complex plugins…?
• Do most of logic on native side to minimize native calls from Unity
• Messages examples (Android)
UnityPlayer.UnitySendMessage("gameObjectName", "methodName", "message");
• Use “Action<…>” delegates to pass callbacks on WP8, Unity supports
Action with max 4 parameters as .NET 3.5 does
• Structure plugin as a wrapper (mainly for third-party SDKs)
• Hide user defined classes (e.g.: instantiate them with methods) so Unity can’t see
them
• Wrap API calls with ones that use just primitive or built-in data type and the convert
them to user defined classes inside plugin!
ROME 24 June 2014 – Valerio Riva
18
19. Run on UI Thread
• iOS
dispatch_async(dispatch_get_main_queue(), ^{
// Your code to run on the main queue/thread
});
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// Your code to run on the main queue/thread
}];
• Android
UnityPlayer.currentActivity.runOnUiThread(new Runnable() {
public void run() {
//your code to run on the UI thread
}
});
ROME 24 June 2014 – Valerio Riva
19
20. Run on UI Thread (WP8)
• MainPage.xaml.cs
public partial class MainPage : PhoneApplicationPage
{
public void InvokeOnAppThread ( Action callback ) {
UnityApp.BeginInvoke ( () => { callback (); } );
}
public void InvokeOnUIThread ( Action callback ) {
Dispatcher.BeginInvoke ( () => { callback (); } );
}
private void Unity_Loaded() {
…
MyDispatcher.InvokeOnAppThread = InvokeOnAppThread;
MyDispatcher.InvokeOnUIThread = InvokeOnUIThread;
…
}
}
• MyPlugin.cs
MyDispatcher.InvokeOnAppThread(() => {
//your Unity callbacks execution must be placed here
});
MyDispatcher.InvokeOnUIThread(() => {
//your code to run on UI Thread
});
ROME 24 June 2014 – Valerio Riva
20
22. Thank you!
ROME 24 June 2014 – Valerio Riva
22
Question Time
No animals were harmed in the making of this talk
valerio.riva@gmail.com
@ValerioRiva
http://it.linkedin.com/in/valerioriva/