Plugins

优质
小牛编辑
132浏览
2023-12-01

Unity has extensive support for C, C++ or Objective-C based Plugins. Plugins allow your game code (written in Javascript, C# or Boo) to call into native code libraries. This allows Unity to integrate with other middleware libraries or legacy game code.

Unity支持基于C,C++或Objctive-C语言的插件。插件允许你的代码(以Javascript,C#或Boo所写)调用本地代码库。这使得Unity可以整合其他中间件库或者遗留的游戏代码。

Note: Plugins are a Pro-only feature. For desktop builds, plugins will work in standalones only. They are disabled when building a Web Player for security reasons.

注:插件是Pro版独有特性。对于桌面系统的构建,插件仅仅能在独立版中运行。而构建网页版本时由于安全问题会被禁用。

In order to use a plugin you need to do two things:

要使用插件,你需要做两件事情:

  • Write a plugin in a C based language and compile it.
    基于C系语言编写并且编译插件。
  • Create a C# script which calls functions in the plugin to do something.
    创建一个C#脚本来调用插件里的方法从而实现功能。

So the plugin provides a simple C interface. The script then invokes the functions exposed by the plugin.

所以插件要提供一个简单的C接口。之后脚本就可以调用插件所暴露出来的方法。

Here is a very simple example:

这是一个非常简单的例子:

float FooPluginFunction () 
	{ 
		return 5.0F; 
	}

The C File of a Minimal Plugin:
一个极为简单的插件的C文件:

A C# Script that Uses the Plugin:
使用这个插件的C#脚本:

Desktop
using UnityEngine;
using System.Runtime.InteropServices;

class SomeScript : MonoBehaviour {
   // This tells unity to look up the function FooPluginFunction
   // inside the plugin named "PluginName"
   [DllImport ("PluginName")]
   private static extern float FooPluginFunction ();

   void Awake () {
      // Calls the FooPluginFunction inside the PluginName plugin
      // And prints 5 to the console
      print (FooPluginFunction ());
   }
}
iOS
using UnityEngine;
using System.Runtime.InteropServices;

class SomeScript : MonoBehaviour {
   // This tells unity to look up the function FooPluginFunction
   // inside the static binary
   [DllImport ("__Internal")]
   private static extern float FooPluginFunction ();

   void Awake () {
      // Calls the native FooPluginFunction
      // And prints 5 to the console
      print (FooPluginFunction ());
   }
} 
Android
using UnityEngine;
using System.Runtime.InteropServices;

class SomeScript : MonoBehaviour {
   // This tells unity to look up the function FooPluginFunction
   // inside the plugin named "PluginName"
   [DllImport ("PluginName")]
   private static extern float FooPluginFunction ();

   void Awake () {
      // Calls the FooPluginFunction inside the PluginName plugin
      // And prints 5 to the console
      print (FooPluginFunction ());
   }
} 
Desktop

Building a Plugin for Mac OS X
为Mac OS X构建插件

If you are building a plugin for Mac OS X, you have to create a bundle. The easiest way to do this is using XCode. Use File->NewProject... and select the Bundle - Carbon/Cocoa Loadable Bundle.

如果你在为MacOSX构建插件,你需要创建一个bundle。最简单的方法是使用XCode。File->NewProject… 然后选择Bundle – Carbon/Cocoa Loadable Bundle.

If you are using C++ (.cpp) or Objective-C (.mm) to implement the plugin you have to make sure the functions are declared with C linkage to avoid name mangling issues.

如果你在使用C++ (.cpp)或Objective-C (.mm)来实现插件,你需要确保方法是以C linkage方式声明的,以避免名称重整问题

extern "C" {
  float FooPluginFunction ();
} 

Building a Plugin for Windows
为Windows构建插件

Plugins on Windows are DLL files with exported functions. Practically any language or development environment that can create DLL files can be used to create plugins.
Again, if you use C++, declare functions with C linkage to avoid name mangling issues.

Windows的插件是暴露了方法的DLL文件。事实上任何能够创建DLL文件的语言或者开发环境都可以用来创建插件。 再说一次,如果你在使用C++,以C linkage方式声明方法以避免名称重整问题

Using your plugin from C#
以C#语言使用插件

Once you have built your bundle you have to copy it to Assets->Plugins folder. Unity will then find it by its name when you define a function like this:

构建好了bundle之后,拷贝到Assets->Plugins文件夹。在你像这样定义了方法之后,Unity便能够根据名字找到相应插件:

[DllImport ("PluginName")]
private static extern float FooPluginFunction (); 

Please note that PluginName should not include the extension of the filename. Be aware that whenever you change code in the Plugin you will have to recompile scripts in your project or else the plugin will not have the latest compiled code.

请注意"PluginName"不要包含文件后缀名。另外当你改变了插件的代码后,你需要重新编译Unity工程中的脚本,否则插件内容不会更新。

Deployment 部署

For cross platform plugins you have to include both .bundle (for Mac) and .dll (for Windows) files in Plugins folder. Once you have placed your plugins in the Plugins folder there is no more work required on your side. Unity automatically picks the right plugin for the right deployment platform and includes it with the player.

如果要跨平台,你需要把 .bundle (for Mac)和 .dll (for Windows)都复制进Plugins文件夹。当你将他们放进去以后,Unity会自动为不同的系统选用不同的插件。

iOS

Building an Application with Native Plugin for iOS
创建一个有本地插件的iOS应用

  1. Define your extern method like:
    定义extern方法如下:
    [DllImport ("__Internal")]
    private static extern float FooPluginFunction ();
    
  2. Switch the editor to iOS platform
    切换编辑器为iOS平台
  3. Add your native implementation to the generated XCode project's "Classes" folder (this folder is not overwritten when project is updated, but don't forget to backup your native code).
    将你的本地实现加入到Unity所生成的XCode项目中的"Classes"文件夹中(这个文件夹不会在工程更新的时候被复写,但是还是别忘了备份你的本地代码)。

If you are using C++ (.cpp) or Objective-C (.mm) to implement the plugin you have to make sure the functions are declared with C linkage to avoid name mangling issues.

如果你在使用C++ (.cpp) 或Objective-C (.mm)来实现插件,你需要确保方法是以C linkage方式声明的,以避免名称重整问题

extern "C" {
  float FooPluginFunction ();
} 

Using Your Plugin from C#
以C#语言使用插件

iOS native plugins can be called only when deployed on the actual device, so it is recommended to wrap all native code methods with an additional C# code layer. This code could check Application.platform and call native methods only when running on the actual device and return mockup values when running in the Editor. Check the Bonjour browser sample application.

iOS本地插件只有在部署到真机上时才能够被调用,所以建议把所有的本地方法打包到一个额外的C#语言层里(不懂…)。这样代码会在检查了Application.platform之后确保只在使用真机时才会调用本地方法,而在编辑器里的模拟运行则会返回样机的值。参阅Bonjour browser示例应用。

Calling C# / JavaScript back from native code
从本地代码回调C#/JavaScript

Unity iOS supports limited native->managed callback functionality via UnitySendMessage:

Unity iOS通过UnitySendMessage提供了有限的本地->托管回调方法。

UnitySendMessage("GameObjectName1", "MethodName1", "Message to send");

This function has three parameters : game object name, game object script method to call, message to pass to the called method. Limitations to know:

这个方法有三个参数:game object名,要调用的game object脚本,要发送的消息。有以下限制:

  1. only script methods that correspond to the following signature can be called from native code: function MethoName(message:string)
    只有符合以下结构的脚本方法才能被从本地方法调用:function MethoName(message:string)
  2. calls to UnitySendMessage are asynchronous and have a one frame delay.
    调用UnitySendMessage是异步的,会有1帧的延迟。

Automated plugin integration 自动插件集成

Unity iOS supports limited automated plugin integration.

Unity iOS支持有限的自动插件集成。

  1. All the .a,.m,.mm,.c,.cpp located in Assets/Plugins/iOS will be automatically merged into produced Xcode project.
    所有位于Assets/Plugins/iOS目录下的.a,.m,.mm,.c,.cpp文件会被自动合并到Unity所生成的Xcode工程中。
  2. Merging is done by symlinking files from Assets/Plugins/iOS to the final destination and this might affect some workflows.
    从Assets/Plugins/iOS到目标文件夹的合并工作由symlinking文件完成,这可能会影响到某些工作流程。
  3. .h files are not included into Xcode project tree, but they appear on final destination file system, thus allowing compilation of .m/.mm/.c/.cpp.
    .h文件不会被包括在Xcode项目树之中,但是他们会在最终产生的文件系统中出现,这样能支持.m/.mm/.c/.cpp的编译。

Note: subfolders are not supported.

注意:不支持子目录。

iOS Tips 小贴士

  1. Managed -> unmanaged calls are quite expensive on iOS. Try to avoid calling multiple native methods per frame.
    在iOS上的托管->非托管调用的代价很高。请尽量避免每帧调用多个本地方法。
  2. As mentioned above wrap your native methods with an additional C# layer that would call actual code on the device and return mockup values in the Editor.
    刚才已经提到了,把你的本地方法以一个额外C#层来打包能够在使用真机时调用真代码,而模拟时反馈样机值。
  3. String values returned from a native method should be UTF-8 encoded and allocated on the heap. Mono marshaling calls are free for them.
    从本地方法返回的字符串应当使用UTF-8编码,并且必须分配在堆(heap)上。Mono为它们自由汇集调用。
  4. As mentioned above the XCode project's "Classes" folder is a good place to store your native code, because it is not overwritten when the project is updated.
    上面提到过,XCode项目的"Class"文件夹很适合存放本地代码,因为它不会在项目更新时被复写。
  5. Another good place for storing native code is the Assets folder or one of its subfolders. Just add references from the XCode project to the native code files: right click on the "Classes" subfolder and choose "Add->Existing files...".
    另一个适合存储本地代码的地方是Asset文件夹或者它的子文件夹。只需要创建一个从XCode项目到本地代码文件的引用即可:右键单击"Class"子文件夹,然后选择"Add->Existing files…"。
Android

Building a Plugin for Android
为Android创建插件

To build a plugin for Android, you first need the Android NDK. Please familiarize yourself with the steps how to build a shared library.

为了创建Android的插件,你首先需要Android NDK。请首先熟悉一下创建共享库的步骤。

If you are using C++ (.cpp) to implement the plugin you have to make sure the functions are declared with C linkage to avoid name mangling issues.

如果你在使用C++ (.cpp)来实现插件,你需要确保方法是以C linkage方式声明的,以避免名称重整问题

extern "C" {
  float FooPluginFunction ();
} 

Using Your Plugin from C#
以C#语言使用插件

Once you have built your shared library you have to copy it to Assets->Plugins->Android folder. Unity will then find it by its name when you define a function like this:

当你建好了你的共享库之后,你需要把它拷贝到Assets->Plugins->Android文件夹。当你按照下述方式定义了一个方法之后,Unity会根据名字找到正确的插件。

[DllImport ("PluginName")]
private static extern float FooPluginFunction ();

Please note that PluginName should not include the prefix ('lib') nor the extension ('.so') of the filename. It is recommended to wrap all native code methods with an additional C# code layer. This code could check Application.platform and call native methods only when running on the actual device and return mockup values when running in the Editor. You can also use platform defines to control platform dependent code compilation.

请注意”PluginName”里所写的文件名不要包含前缀”lib”,也不要有后缀”.so”。建议把你的本地方法以一个额外C#层来打包。这样代码会在检查了Application.platform之后确保只在使用真机时才会调用本地方法,而在编辑器里的模拟运行则会返回样机的值。你也可以使用平台定义来控制与平台相关的代码编译。

Deployment 部署

For cross platform plugins, your Plugins folder may include plugins for several different platforms (i.e. libPlugin.so for Android, Plugin.bundle for Mac and Plugin.dll for Windows). Unity automatically picks the right plugin for the right deployment platform and includes it with the player.

对于跨平台插件,你的插件文件夹里可能会包含为了各个不同平台的插件文件(比如Android用的libPlugin.so,Mac用的Plugin.bundle,Windows用的Plugin.dll)。Unity会自动为不同的部署平台选择正确的插件。

Using Java Plugins 使用Java插件

The Android plugin mechanism also allows Java to be used to enable interaction with the Android OS. Java code can not be called directly from C#, so you'd need to write a native plugin to 'translate' the calls between C# and Java.

Android的插件机制允许使用Java来与Android OS交互。Java代码不可以直接由C#调用,所以你需要写一个本地插件来在C#和Java之间进行"翻译"。

Building a Java Plugin for Android
为Android构建一个Java插件

There are several ways to create a Java plugin. What they have in common is that you will end up with a .jar file containing the .class files for your plugin. One way is to start off with downloading the JDK, then compile your .java files from command line with javac to create .class files and package them into a .jar with the jar command line tool. Another way is to use Eclipse IDE together with the ADT.

创建Java插件有几种方法。他们的共同点是最终产物都是包含有插件.class文件的.jar包。一种办法是下载JDK,然后以javac命令在命令行下编译.java文件,从而创建.class文件。然后用jar命令行命令将他们打包到一个.jar中。另一个方法是使用Eclipse IDE和ADT工具。

Using Your Java Plugin from Native Code
从本地代码使用你的Java插件

Once you have built your Java plugin (.jar) you have to copy it to Assets->Plugins->Android folder. Unity will package your .class files together with the rest of the Java code, and then call it through something called Java Native Interface (JNI). JNI works both ways; calling native code from Java, and interacting with Java (or the JavaVM) from native code.

当你建立好Java插件(.jar)之后,你要把它拷贝到Assets->Plugins->Android文件夹。Unity会把你的.class文件和其他Java代码打包到一起,然后通过Java本地接口(JNI)来调用。JNI可以实现以下两种功能:从Java调用本地代码,从本地代码与Java(或Java虚拟机)互动。

To find your Java code from the native side you need to have access to the Java VM. Fortunately that is quite easy; just add a function like this to your C(++) code:

要从本地这边找到你的Java代码,你需要能够访问Java虚拟机。幸运的是这很容易;只要把这个方法加入你的C(++)代码中:

jint JNI_OnLoad(JavaVM* vm, void* reserved) {
  JNIEnv* jni_env = 0;
  vm->AttachCurrentThread(&jni_env, 0);
} 

This is all that is needed to start using Java from C(++). Completely explaining JNI is somewhat beyond the scope of this document, but usually it includes finding the class definition, resolving the constructor (<init>) method and create a new object instance, as shown here:

这就足够从C(++)使用Java了。完整的解释JNI已经超出了本文档的范畴,但是通常它包含了寻找类定义,解决构造方法(<init>)和创建新对象实例的功能,如:

jobject createJavaObject(JNIEnv* jni_env) {
  jclass cls_JavaClass = jni_env->FindClass("com/your/java/Class");			// find class definition
  jmethodID mid_JavaClass = jni_env->GetMethodID (cls_JavaClass, "<init>",  "()V");		// find constructor method
  jobject obj_JavaClass = jni_env->NewObject(cls_JavaClass, mid_JavaClass);		// create object instance
  return jni_env->NewGlobalRef(obj_JavaClass);						// return object with a global reference
} 

Using Your Java Plugin with helper classes
通过辅助类使用你的Java插件

AndroidJNIHelper together with AndroidJNI can be used to ease a bit of pain with raw JNI.

在AndroidJNI上联合使用AndroidJNIHelper有助于减轻使用原始JNI的痛苦。

AndroidJavaObject and AndroidJavaClass automate a lot of things and also use caches to make calls to Java faster. The combination of AndroidJavaObject and AndroidJavaClass builds on top of AndroidJNI and AndroidJNIHelper, but also has a lot of logic in itself (to handle the automation). These classes also come in a 'static' version to access static members of Java class.

AndroidJavaObject和AndroidJavaClass能自动处理很多事情,并且会使用缓存来加速对Java的调用。AndroidJavaObject和 AndroidJavaClass的组合建立在 AndroidJNI和 AndroidJNIHelper之上,但其中还是有很多逻辑(用于进行自动处理)。这些类也有静态版本来访问Java类的静态成员。

You can opt for any approach you like, be it a raw JNI through AndroidJNI class methods, or AndroidJNIHelper together with AndroidJNI and eventually AndroidJavaObject/AndroidJavaClass for the maximum automation and convenience.

你可以选择任何你喜欢的方式,通过AndroidJNI类的方法来使用原始JNI,或者使用AndroidJNIHelper和AndroidJNI的组合,甚至是使用最为自动化和最为便利的AndroidJavaObject/AndroidJavaClass。

  • UnityEngine.AndroidJNI is a wrapper for the JNI calls available in C (as described above). All methods in this class are static and have a 1:1 mapping to the Java Native Interface.
    UnityEngine.AndroidJNI是一个为了能在C里对JNI调用的包(如上所述)。这个类里的所有方法都是静态的,并且与JNI一一对应。
  • UnityEngine.AndroidJNIHelper provides helper functionality used by the next level, but is exposed as public methods because they may be useful for some special cases.
    UnityEngine.AndroidJNIHelper提供高一级别的有用的方法,但是它的方法都暴露为public,因为在特定情况下可能有好处。
  • Instances of UnityEngine.AndroidJavaObject and UnityEngine.AndroidJavaClass have a 1:1 mapping to an instances of java.lang.Object and java.lang.Class (or subclasses thereof) on the Java side, correspondingly. They essentially provide 3 types of interaction with the Java side:
    相应的, UnityEngine.AndroidJavaObjectUnityEngine.AndroidJavaClass的对象与Java那边的java.lang.Object和java.lang.Class (或子类) 一一对应。他们实际上提供了3种与Java的互动方式:
    • Call a method 调用一个方法
    • Get the value of a field 获取一个域的值
    • Set the value of a field 为一个域赋值

The Call is separated into two categories: Call to a 'void' method, and Call to a method with non-void return type. A generic type is used to represent the return type of those methods which return a non-void type.The Get/Set always take a generic type representing the field type.

所谓调用分为两类:调用一个无返回值(void)方法,或者调用一个有返回值的方法。泛型被用于代表有返回值的方法的返回值。Get/Set方法总是使用泛型来表示域类型。

Example 1

//The comments is what you would need to do if you use raw JNI
 AndroidJavaObject jo = new AndroidJavaObject("java.lang.String", "some_string"); 
 // jni.FindClass("java.lang.String"); 
 // jni.GetMethodID(classID, "<init>", "(Ljava/lang/String;)V"); 
 // jni.NewStringUTF("some_string"); 
 // jni.NewObject(classID, methodID, javaString); 
 int hash = jo.Call<int>("hashCode"); 
 // jni.GetMethodID(classID, "hashCode", "()I"); 
 // jni.CallIntMethod(objectID, methodID);

Here we're creating an instance of java.lang.String, initialized with a of our choice and retrieving the hash value for that string.

这里我们创建一个java.lang.String的实例,初始化,并且拿到它的hash值

The AndroidJavaObject constructor takes at least one parameter: the name of class for which we want to construct an instance. Everything after the class name are parameters for the constructor call of the object, in this case a string "some_string". Then the Call to hashCode() that returns an 'int' which is why we use that as the generic type parameter to the Call method.

AndroidJavaObject的构造方法至少有一个参数:要实例化的类的名字。之后的所有参数都将传给它实际的构造方法,在此例中是字符串”some_string”。然后对hashCode()的调用会返回一个”int”,因此我们要对这个调用方法使用泛型。

Note: You cannot instantiate nested Java class using dotted notation. Inner classes must use $ separator, and it should work in both dotted and slash'd format. So android.view.ViewGroup$LayoutParams or android/view/ViewGroup$LayoutParams can be used, where LayoutParams class is nested in ViewGroup class.

:你不能使用点号来初始化嵌套的Java类。内部类必须使用$分隔,而包分隔可以用点或者反斜杠(/)。所以android.view.ViewGroup$LayoutParamsandroid/view/ViewGroup$LayoutParams都可以使用,其中LayoutParamsVewGroup类的嵌套类。

Example 2

One of the plugin samples above shows how to get the cache dir for the current application. This is how you would to it from C# without any plugins:

上面的一个插件例子展示了如何从当前程序获取缓存路径。这个则展示如何不使用插件用C#做到:

AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer"); 
 // jni.FindClass("com.unity3d.player.UnityPlayer"); 
 AndroidJavaObject jo = jc.GetStatic<AndroidJavaObject>("currentActivity"); 
 // jni.GetStaticFieldID(classID, "Ljava/lang/Object;"); 
 // jni.GetStaticObjectField(classID, fieldID); 
 // jni.FindClass("java.lang.Object"); 

 Debug.Log(jo.Call<AndroidJavaObject>("getCacheDir").Call<string>("getCanonicalPath")); 
 // jni.GetMethodID(classID, "getCacheDir", "()Ljava/io/File;"); // or any baseclass thereof! 
 // jni.CallObjectMethod(objectID, methodID); 
 // jni.FindClass("java.io.File"); 
 // jni.GetMethodID(classID, "getCanonicalPath", "()Ljava/lang/String;"); 
 // jni.CallObjectMethod(objectID, methodID); 
 // jni.GetStringUTFChars(javaString);

Here we start with AndroidJavaClass instead of AndroidJavaObject, because we want access a static member of com.unity3d.player.UnityPlayer, not create a new object (there is already one created as part of the Android UnityPlayer). Then we access the static field "currentActivity" but this time we use AndroidJavaObject as the generic parameter. This is because the actual field type (android.app.Activity) is a subclass of java.lang.Object, and any non-primitive type must be accessed as AndroidJavaObject (strings are the exception to that rule; they can be accessed directly even though they don't represent a primitive type in Java).

这里我们首先以AndroidJavaClass替代AndroidJavaObject,因为我们想要访问com.unity3d.player.UnityPlayer的静态成员,而不是创建一个新对象(已经有一个了,是Android UnityPlayer的组件)。然后我们访问静态域”currentActivity”,使用AndroidJavaObject标注泛型。这是因为实际的类型(android.app.Activity)是java.lang.Object的子类,而任何的非基本类型都应当以AndroidJavaObject来访问(string是个特例,尽管他们在Java里不是基本类型,但仍然可以直接访问)。

After that it's just a matter of simply traversing the Activity through getCacheDir(), to get the File object representing the cache directory, and call getCanonicalPath() to get a string representation.

然后就只是简单的通过Activity调用getCacheDir(),拿到表示缓冲路径的File对象,然后调用getCanonicalPath()来得到其字符串表达形式。

Of course nowadays you don't need to do that to get the cache directory; we have provided access to the application's cache and file directory through Application.temporaryCachePath, Application.persistentDataPath.

当然,如今你已经不需要这样去拿缓存路径了;我们可以通过Application.temporaryCachePath, Application.persistentDataPath来访问程序缓存和文件目录。

Example 3

Finally a small trick to show how we can pass data from Java to script code using UnitySendMessage.

最后给出一个小技巧来展示我们如何使用UnitySendMessage来从Java向脚本发送数据。

using UnityEngine; 
public class NewBehaviourScript : MonoBehaviour { 

	void Start () { 
		JNIHelper.debug = true; 
		using (JavaClass jc = new JavaClass("com.unity3d.player.UnityPlayer")) { 
			jc.CallStatic("UnitySendMessage", "Main Camera", "JavaMessage", "whoowhoo"); 
		} 
	} 

	void JavaMessage(string message) { 
		Debug.Log("message from java: " + message); 
	}
} 

The Java class com.unity3d.player.UnityPlayer now has a static method UnitySendMessage, equivalent to the iOS' UnitySendMessage on the native side; it can be used in Java to pass data to script code.

Java类com.unity3d.player.UnityPlayer现在有一个静态方法UnitySendMessage,等价于iOS在本地代码端的UnitySendMessage;它可以用于从Java传输数据到脚本代码。

But here we call it directly from script code. That essentially relays the message on the Java side, which then calls back in the native/Unity to deliver the message to the object named "Main Camera", with a script attached to it which as a method called "Java Message".

但是这里我们是直接从脚本代码调用的它。这本质上是在Java端传递消息,然后再在本地/Unity端递送消息给名为”Main Camera”的物体,这个物体上附带了一个含有名为”JavaMessage”的方法的脚本。

The best practices of using the Java plugins with Unity
Unity上使用Java插件的最佳实践

As this section is manly targeted at people who don't have comprehensive JNI, Java and Android experience, we assume that AndroidJavaObject/AndroidJavaClass approach has been taken to interact with Java code from Unity.

鉴于这个章节所面对的是并不精通JNI,Java和Android的人群,我们假定您是用AndroidJavaObject/AndroidJavaClass来从Unity与Java代码交互的。

The very first thing that should be noted is that any operation you do on a AndroidJavaObject / AndroidJavaClass is expensive (as is a raw JNI approach though). It's very much recommended to keep the number of transitions between managed and native/java to a minimum. Both for the sake of performance and also complexity.

最先要注意的是所有用AndroidJavaObject / AndroidJavaClass进行的操作都是高代价的(和使用原始JNI差不多)。强烈建议尽量少的进行托管<->本地/Java交互。既为了性能考虑,也为了尽量降低复杂度。

Basically you could have a Java method to do all the actual work and then use AndroidJavaObject / AndroidJavaClass to communicate with that method and get the result. That's said, it may be beneficial to know, that with JNI helper classes we try to cache as much data as possible.

基本上你可以以一个Java方法来做所有的实际工作然后使用AndroidJavaObject / AndroidJavaClass来与之互动,拿到其返回值。也就是说,你应该知道,我们要通过JNI帮助类来尽量缓冲所有的数据。

//第一次调用一个Java方法
AndroidJavaObject jo = new AndroidJavaObject("java.lang.String", "some_string");  // 是很高代价的
int hash = jo.Call<int>("hashCode");  // 第一次 - 高代价
int hash = jo.Call<int>("hashCode");  // 第二次 - 对于程序已知的方法,代价就不那么高了,我们可以直接调用

It's the same way as a JIT: first time you call it, it will be slow because the code doesn't exist. Next time is much cheaper. In other words, you pay for every .Call /.Get / .Set, but you pay less after the first time you call it, per object. You pay for creating a AndroidJavaClass / AndroidJavaObject.

这和JIT的方式相同:第一次调用时,由于代码还不存在,运行会很慢。以后就会快得多。换句话说,当对一个对象调用.Call /.Get / .Set时要花很多时间,但是在第一次调用它们之后,以后对这个对象的相同方法的再调用就会快速很多。另外创建AndroidJavaClass / AndroidJavaObject也很费时。

Mono garbage collector should release all the created instances of AndroiJavaObject / AndroidJavaClass, yet it is strongly advised to keep them in a using(){} statement to ensure they are deleted as soon as possible. Otherwise there is no real guarantee when they will be destroyed. If you set AndroidJNIHelper.debug = true; you will see the debug output exposing the Garbage Collector behavior.

Mono垃圾收集器会释放所有的AndroiJavaObject / AndroidJavaClass对象,但是还是强烈建议把他们放入using(){}结构中来确保它们被尽快删除。否则不能保证它们会被删除。如果你设置AndroidJNIHelper.debug = true;你会看到在调试输出窗口中有垃圾收集器的行为信息。

//Getting the system language with the safe approach
//安全的获取系统语言的方法

void Start () { 
	using (AndroidJavaClass cls = new AndroidJavaClass("java.util.Locale")) { 
		using(AndroidJavaObject locale = cls.CallStatic<AndroidJavaObject>("getDefault")) { 
			Debug.Log("current lang = " + locale.Call<string>("getDisplayLanguage")); 

		} 
	} 
}

You can also call the .Dispose() method directly to ensure there are no Java objects lingering. The actual C# object might live a bit longer though, but will be garbage collected by mono eventually.

你也可以调用.Dispose()方法来直接确保没有Java对象存在。尽管实际的C#对象还会存活的久一点,但是最终肯定会被mono的垃圾收集器处理掉。

Extending the UnityPlayerActivity Java Code
扩展UnityPlayerActivity Java代码

With Unity Android it is also possible to extend the regular UnityPlayerActivity (primary Java class for the Unity Player on Android, similarly to AppController.mm on Unity iOS).

通过Unity Android,可以扩展常规UnityPlayerActivity(Android上的Unity应用的主类(入口类),类似在Unity iOS上的AppController.mm)。

An application can, by creating a new Activity which derives from UnityPlayerActivity (UnityPlayerActivity.java can be found at /Applications/Unity/Unity.app/Contents/PlaybackEngines/AndroidPlayer/src/com/unity3d/player on Mac and usually at C:\Program Files\Unity\Editor\Data\PlaybackEngines\AndroidPlayer\src\com\unity3d\player), override any and all of the basic interaction between Android OS and Unity Android.

程序可以通过建立派生自UnityPlayerActivity类的新Activity,来重载任何、甚至所有的Android操作系统与Unity Android之间的基本交互。(UnityPlayerActivity.java在Mac机上的位置是在/Applications/Unity/Unity.app/Contents/PlaybackEngines/AndroidPlayer/src/com/unity3d/player,在Windows上在C:\Program Files\Unity\Editor\Data\PlaybackEngines\AndroidPlayer\src\com\unity3d\player)

To do that first locate the classes.jar shipped with Unity Android. It is found under the installation folder (usually C:\Program Files\Unity\Editor\Data (on Windows) or /Applications/Unity (on Mac)) in a folder called PlaybackEngines/AndroidPlayer/bin. Then add that classes.jar file to the classpath used to compile the new activity. The resulting .class file(s) should be compressed into a .jar file and placed under Assets->Plugins->Android. As the manifest dictates which activity to launch it's also necessary to create a new AndroidManifest.xml. The AndroidManifest.xml should also be placed under Assets->Plugins->Android.

首先找到Unity Android所自带的classes.jar。它就在安装目录(通常是C:\Program Files\Unity\Editor\Data(Windows中)或者/Applications/Unity(Mac中))下的PlaybackEngines/AndroidPlayer/bin文件夹内。然后把这个classes.jar加到用于编译新activity的类路径中。生成的.class文件应当压缩为.jar文件,然后放置到Assets->Plugins->Android里。由于menifest文件指示了哪个activity会被启动,所以也需要创建一个新的AndroidManifest.xml,并把它放到Assets->Plugins->Android里。

The new activity could look like OverrideExample.java:
新的activity应当看上去和 OverrideExample.java相似:

package com.company.product;

import com.unity3d.player.UnityPlayerActivity;

import android.os.Bundle;
import android.util.Log;

public class OverrideExample extends UnityPlayerActivity {

  protected void onCreate(Bundle savedInstanceState) {

    // call UnityPlayerActivity.onCreate()
    super.onCreate(savedInstanceState);

    // print debug message to logcat
    Log.d("OverrideActivity", "onCreate called!");
  }

  public void onBackPressed()
  {
    // instead of calling UnityPlayerActivity.onBackPressed() we just ignore the back button event
    // super.onBackPressed();
  }
} 

And this is what the matching AndroidManifest.xml would look like:

而这是一个相配套的 AndroidManifest.xml 应有的样子:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.company.product">

  <application android:icon="@drawable/app_icon" android:label="@string/app_name">
	<activity android:name=".OverrideExample"
			  android:label="@string/app_name">
        <intent-filter>

			<action android:name="android.intent.action.MAIN" />
			<category android:name="android.intent.category.LAUNCHER" />
		</intent-filter>
	</activity>

  </application>
</manifest>

Examples 示例

Desktop

Simplest Plugin 最简单的插件

This is a basic example of a project and a plugin that only do basic operations (prints a number, prints a string, adds two floats and adds two integers). Check this example if this is your first time learning plugins in Unity.
The project can be found here.
This project includes both Windows and Mac project files.

这是一个最简单的示例工程,它有一个插件,只做一点简单的操作(打印个数,打印个字符串,两个浮点数相加,两个整数相加)。如果这是你第一次学习Unity的插件,请查阅这个示例。
可以在这里下载这个工程。
这个工程包括了Windows版和Mac版的工程文件。

Midi Plugin
Midi插件

A complete example of the Plugin interface can be found here.

可以在这里下载这个完整的插件接口示例。

This is a complete Midi plugin for OS X which uses Apple's CoreMidi API. It provides a simple C API and a C# class using the C API. The C# class contains a high level API, with easy access to NoteOn and NoteOff events and their velocity.

这是一个使用苹果的CoreMidi API的OS X使用的完整的Midi插件。它提供了一个简单的C语言API和一个使用了这个API的C#类。这个C#类包括一个高级API,用它可以轻松的访问NoteOn和NoteOff事件以及它们的速度。

Texture Plugin
纹理插件

An example of how to assign image data to a texture from C++ directly to OpenGL (note that this will only work when Unity is using an OpenGL renderer). This example includes both XCode (for Mac) and Visual Studio (for Windows) project files. The plugin with accompanying Unity project can be found here.

这个示例展示了如何从C++直接向OpenG指定图片数据到贴图(只有在你的Unity使用OpenGL渲染器时才有效)。这个例子同时包含了XCode(Mac使用)和Visual Studio(Windows使用)的工程文件。含有这个的插件的Unity工程可以在这里下载。

iOS

Bonjour Browser Sample

A simple example of the use of native code plugin can be found here

这个简单的展示如何使用本地代码插件的例子可以在这里下载。

This sample demonstrates how objective-C code can be invoked from a Unity iOS application. This application implements a very simple Bonjour client. The application consists of a Unity iOS project (Plugins/Bonjour.cs: C# interface to the native code, BonjourTest.js: JS script that implements application logic) and native code (Assets/Code) that should be added to built XCode project.

这个例子演示objective-C代码如何从Unity iOS应用程序调用。这个程序实现一个非常简单的Bonjour客户端。这个程序包含Unity iOS项目(插件/Bonjour.cs:C#接口到本地代码,BonjourTest.js:JS脚本实现程序逻辑)和本地代码(资源/代码),可以添加到构建XCode工程。

Android

Native Plugin Sample 本地插件例子

A simple example of the use of native code plugin can be found here

一个简单的使用本地代码插件的例子,在这里找到。

This sample demonstrates how C code can be invoked from a Unity Android application. The package includes a scene which displays the sum to two values, calculated by the native plugin. Please note that you will need the Android NDK to compile the plugin.

这个例子演示,如何用C代码调用Untiy Android应用程序。package包含一个场景,显示通过本地插件计算,两个值的和。请注意,需要安装Android NDK来编译插件。

Java Plugin Sample
Java插件例子

An example of the use of Java code can be found here
使用Java代码的例子,在这里找到。

This sample demonstrates how Java code can be used to interact with the Android OS and how C++ creates a bridge between C# and Java. The package includes a scene which displays a button that when pressed fetches the application cache directory, as defined by the Android OS. Please note that you will need both the JDK and the Android NDK to compile the plugins.

这个例子演示Java代码如何与Android OS进行交互和C++如何创建一个C#和Java之间的桥接。package包含一个场景,显示一个按钮,当被按下,提取Android OS定义的应用程序缓存目录。请注意,需要安装JDK和Android NDK来编译插件。

The same example but based on a prebuilt JNI library to wrap the native code into C# can be found here

同样的例子,但是基于预构建JNI库,来封装本地代码到C#,例子在这里

More Information 更多信息

Mono Interop with native libraries.

P-invoke documentation on MSDN.