avoid loss of hair while coding unity3d plugin for mobile

22
ROME 11-12 april 2014 Valerio “Lotti” Riva – Interactive Project [email protected] @ 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

Upload: valerio-riva

Post on 10-May-2015

1.133 views

Category:

Technology


1 download

TRANSCRIPT

Page 1: Avoid loss of hair while coding Unity3D plugin for mobile

Valerio “Lotti” Riva – Interactive Project

[email protected] @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

Page 2: Avoid loss of hair while coding Unity3D plugin for mobile

Nice to meet you!

• Web / Game developer• Recent works @ Interactive Project

• World Cup Juggler• OverVolt: crazy slot cars

ROME 24 June 2014 – Valerio Riva

Page 3: Avoid loss of hair while coding Unity3D plugin for mobile

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

Page 4: Avoid loss of hair while coding Unity3D plugin for mobile

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

Page 5: Avoid loss of hair while coding Unity3D plugin for mobile

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

Page 6: Avoid loss of hair while coding Unity3D plugin for mobile

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

Page 7: Avoid loss of hair while coding Unity3D plugin for mobile

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

Page 8: Avoid loss of hair while coding Unity3D plugin for mobile

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

Page 9: Avoid loss of hair while coding Unity3D plugin for mobile

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

Page 10: Avoid loss of hair while coding Unity3D plugin for mobile

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

//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);

}

Page 11: Avoid loss of hair while coding Unity3D plugin for mobile

Flurry plugin (iOS)// FlurryiOS.m - created by Faizan Naqvi on 1/10/13.#import <stdio.h>#include "Flurry.h" //Flurry SDK headersvoid FlurryiOS_startSession(const char* apiKey) { NSString *str = [NSString stringWithUTF8String:apiKey]; [Flurry startSession:str];}void FlurryiOS_setEventLoggingEnabled(BOOL bEnabled){ [Flurry setEventLoggingEnabled:bEnabled];}

ROME 24 June 2014 – Valerio Riva

Page 12: Avoid loss of hair while coding Unity3D plugin for mobile

Flurry plugin (iOS)void FlurryiOS_logEventWithParameters(const char* eventId,const char *parameters) {

NSString *params = [NSString stringWithUTF8String:parameters];

NSArray *arr = [params componentsSeparatedByString: @"\n"];

NSMutableDictionary *pdict = [[[NSMutableDictionary alloc] init] autorelease];

for(int i=0;i < [arr count]; i++) {

NSString *str1 = [arr objectAtIndex:i];

NSRange range = [str1 rangeOfString:@"="];

if (range.location!=NSNotFound) {

NSString *key = [str1 substringToIndex:range.location];

NSString *val = [str1 substringFromIndex:range.location+1];

//NSLog(@"kv %@=%@\n",key,val);

[pdict setObject:val forKey:key];

} }

if([pdict count]>0) {

[Flurry logEvent:[NSString stringWithUTF8String:eventId] withParameters:pdict timed:false];

} else FlurryiOS_logEvent(eventId);

}

ROME 24 June 2014 – Valerio Riva

Page 13: Avoid loss of hair while coding Unity3D plugin for mobile

Flurry plugin (iOS)Meanwhile on Unity side… #region FlurryiOS_Imports

[DllImport("__Internal", CharSet = CharSet.Ansi)]

private static extern void FlurryiOS_startSession([In, MarshalAs(UnmanagedType.LPStr)]string apiKey);

[DllImport("__Internal")]

private static extern void FlurryiOS_setEventLoggingEnabled(bool bEnabled);

[DllImport("__Internal", CharSet = CharSet.Ansi)]

private static extern void FlurryiOS_logEventWithParameters([In, MarshalAs(UnmanagedType.LPStr)]string evendId, [In, MarshalAs(UnmanagedType.LPStr)]string parameters);

#endregion

ROME 24 June 2014 – Valerio Riva

Page 14: Avoid loss of hair while coding Unity3D plugin for mobile

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

Page 15: Avoid loss of hair while coding Unity3D plugin for mobile

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

That was easy, now give me peanuts!

Page 16: Avoid loss of hair while coding Unity3D plugin for mobile

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

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;

}

Page 17: Avoid loss of hair while coding Unity3D plugin for mobile

Flurry plugin (Android)

ROME 24 June 2014 – Valerio Riva

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;}

Page 18: Avoid loss of hair while coding Unity3D plugin for mobile

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

Page 19: Avoid loss of hair while coding Unity3D plugin for mobile

Run on UI Thread

• iOSdispatch_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 }];

• AndroidUnityPlayer.currentActivity.runOnUiThread(new Runnable() { public void run() { //your code to run on the UI thread }});

ROME 24 June 2014 – Valerio Riva

Page 20: Avoid loss of hair while coding Unity3D plugin for mobile

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.csMyDispatcher.InvokeOnAppThread(() => { //your Unity callbacks execution must be placed here});

MyDispatcher.InvokeOnUIThread(() => { //your code to run on UI Thread});

ROME 24 June 2014 – Valerio Riva

Page 21: Avoid loss of hair while coding Unity3D plugin for mobile

Resources and examplesResources

• http://docs.unity3d.com/Manual/Plugins.html• http://docs.unity3d.com/Manual/wp8-plugins.html• http://docs.unity3d.com/Manual/PluginsForAndroid.html• http://docs.unity3d.com/Manual/PluginsForIOS.html

Examples• https://github.com/playgameservices/play-games-plugin-for-unity• https://

github.com/googleads/googleads-mobile-plugins/tree/master/unity• https://github.com/guillermocalvo/admob-unity-plugin• https://github.com/mikito/unity-admob-plugin• https://github.com/bearprada/flurry-unity-plugin• https://github.com/mikito/unity-flurry-plugin• https://github.com/faizann/unity3d_flurry

ROME 24 June 2014 – Valerio Riva

Page 22: Avoid loss of hair while coding Unity3D plugin for mobile

Thank you!

ROME 24 June 2014 – Valerio Riva

Question Time

No animals were harmed in the making of this talk

[email protected] @ValerioRiva

http://it.linkedin.com/in/valerioriva/