一. 写在前面
最近由于业务需求,对Unity3D与Android,iOS平台交互有所了解,特此记录和分享。
二. 准备工作
1)我使用的Unity版本是4.6.3,eclipse+ADT开发环境,以及Xcode7.2;
2)unity脚本采用C#编写。
三. 开始
2.1 Unity脚本部分
若要在C#脚本中调用OC方法,须引入系统库:
using System.Runtime.InteropServices
要在C#脚本中调用Android部分方法,先定义如下方法:
#if UNITY_ANDROID
public AndroidJavaObject androidContext() {
return new AndroidJavaClass("com.unity3d.player.UnityPlayer").GetStatic<AndroidJavaObject>("currentActivity");
}
#endif
获取android当前Activity的引用,然后使用该引用调用其他方法。
获取引用还有一种写法,但个人认为以上方法更具普遍性,故不在这里讨论。
1)调用无参方法
iOS:
对要调用的方法进行定义:
[DllImport ("__Internal")]
private static extern void _showUI();
须注意的是,Internal前面是2个下划线,DllImport后面有个空格。
Android:无须其他操作。
此时,就可以在C#代码中使用该方法:
if (GUILayout.Button ("显示一个对话框", GUILayout.Height (100), GUILayout.Width (400))) {
#if UNITY_IOS
_showUI();
#elif UNITY_ANDROID
androidContext().Call("showUI");
#endif
}
android部分调用,第一个参数为要调用的参数名字,类型为string。
2)调用有参方法
iOS:
对方法进行定义:
[DllImport ("__Internal")]
private static extern void _passParameter(string name,int age);
Android:无须其他操作
在C#中调用该方法:
if (GUILayout.Button ("传递参数", GUILayout.Height (100), GUILayout.Width (400))) {
#if UNITY_IOS
_passParameter("Jerry",24);
#elif UNITY_ANDROID
androidContext().Call("passParameter","Jerry",24);
#endif
}
android部分,方法参数紧跟在方法名称参数之后。
目前应该只支持string 和int类型参数。
3)调用有返回值的方法
iOS:
对方法进行定义:
[DllImport ("__Internal")]
private static extern string _getReturnValue();
Android:无须其他操作
在C#中调用:
if (GUILayout.Button ("有返回值的方法", GUILayout.Height (100), GUILayout.Width (200))) {
string value = "";
#if UNITY_IOS
value = _getReturnValue();
#elif UNITY_ANDROID
value = androidContext().Call<string>("getReturnValue");
#endif
}
留意android部分,有返回值的方法调用,和无返回值的方法略有不同。
4)接收回调信息
iOS:
定义该方法:
[DllImport ("__Internal")]
private static extern void _receiveCallback(string gameObject,string callbackMethod);
Android:无须其他操作
在C#中调用:
if (GUILayout.Button ("有回调的方法", GUILayout.Height (100), GUILayout.Width (200))) {
#if UNITY_IOS
_receiveCallback(this.gameObject.name,"callback");
#elif UNITY_ANDROID
androidContext().Call("receiveCallback",this.gameObject.name,"callback");
#endif
}
往unity部分发送回调信息,需要知道回调方法名称,以及该方法所属的GameObject的名字,因此,从方法参数中传入这2个值。
注:也可以采用其他方式,如在配置文件中配置,或约定方法名称,预制体名称。
2.2 Android部分
在eclipse中新建一个android工程,从unity的安装路径下取出classes.jar放到libs目录下。
classes.jar目录
Mac:Unity.app右键show package contents,然后Contents->PlaybackEngines->AndroidPlayer->developement->bin目录下
Windows:unity安装目录->Editor->Data->PlaybackEngines->AndroidPlayer->development->bin目录下
1)新建一个Activity:
public class MainActivity extends UnityPlayerActivity {
@Override
protected void onCreate(Bundle arg0) {
super.onCreate(arg0);
}
public void showUI() {
AlertDialog.Builder builder = new Builder(this);
builder.setMessage("我是个对话框").show();
}
public void passParameter(String name,int age) {
Toast.makeText(this, name + "is" + age, Toast.LENGTH_LONG).show();
}
public String getReturnValue() {
return "hello";
}
public void receiveCallback(String gameObject,String callback) {
UnityPlayer.UnitySendMessage(gameObject, callback, "I am a callback");
}
}
使其继承UnityPlayerActivity,然后实现刚才在C#需要调用的方法;
以上代码简洁清楚,不再赘述。
须注意的是,向Unity部分发送回调信息,使用方法:
UnityPlayer.UnitySendMessage(String,String,String)
第一个参数为接收该信息的预制体GameObject名称,第二个参数为接收信息的方法,该方法所在脚本须与上述GameObject绑定,第三个参数为回调消息,即回调方法的参数。
2)配置AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.leaf.blogdemo"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="18"
android:targetSdkVersion="19" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.leaf.blogdemo.MainActivity"
android:screenOrientation="landscape">
<intent-filter >
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
3)导出jar包
将所有源码导出成jar包。
4)Unity工程修改
将3)导出的jar包放在Assets->Plugins->Android->bin目录下(目录没有时请创建),若android工程中libs下有引用其他jar包,请放在Android->libs目录下;
将配置好的AndroidManifest.xml文件放在Android目录下;
将assets目录复制到Android目录下;
将res目录复制到Android目录下。
5)导出apk
以上步骤操作无误后,先检查导出环境:
Mac:Unity->Preferences->External Tools->Android SDK Location
Windows:Edit->Preferences->External Tools->Android SDK Location
该配置项设置Android SDK目录。(须注意的是,不同的unity版支持的android sdk不同,若版本不匹配时,可能出现导出错误。)
SDK配置无误后,开始导出:File->Build Settins…
在Player Settings里可对导出的apk属性进行设置,不详述,点击Build,导出apk后,可安装进行测试。
2.3 iOS部分
1)导出Xcode工程
File->Build Settings…选择iOS平台,导出Xcode工程;
2)编写oc方法
在导出的Xcode工程中,新建一个.mm文件,开始coding,实现在C#中调用的方法,方法体需要采用C语言格式:
主要代码如下:
extern "C" {
void _showUI()
{
UIAlertView *view = [UIAlertView new];
view.message = @"我是一个对话框";
[view addButtonWithTitle:@"确定"];
[view dismissWithClickedButtonIndex:0 animated:YES];
[view show];
}
void _passParameter(const char* name,int age)
{
NSLog(@"%@ is %d",cStr2NStr(name),age);
}
const char* _getReturnValue()
{
return MakeStringCopy("hello");
}
void _receiveCallback(const char* gameObject,const char* callback)
{
UnitySendMessage(gameObject, callback, "I am a callback");
}
}
以上代码片段还有一个写法:
@interface iBlogDemo : NSObject
@end
@implementation iBlogDemo
void _showUI()
{
UIAlertView *view = [UIAlertView new];
view.message = @"我是一个对话框";
[view addButtonWithTitle:@"确定"];
[view dismissWithClickedButtonIndex:0 animated:YES];
[view show];
}
void _passParameter(const char* name,int age)
{
NSLog(@"%@ is %d",cStr2NStr(name),age);
}
const char* _getReturnValue()
{
return MakeStringCopy("hello");
}
void _receiveCallback(const char* gameObject,const char* callback)
{
UnitySendMessage(gameObject, callback, "I am a callback");
}
@end
但此时,文件后缀只能为.m,否则会报错。
_showUI方法是利用iOS API显示了一个简单的对话框;
_passParameter须注意的是,c#当中的string对应过来是const char*类型,二者可互相转换:
//convert const char* to NSString
NSString* cStr2NStr(const char* cstr) {
return [[NSString alloc] initWithCString:cstr encoding:NSUTF8StringEncoding];
}
_getReturnValue有返回值的方法,须注意的是,目前只能返回string值,并且,在返回时,需要进行特殊处理:
// By default mono string marshaler creates .Net string for returned UTF-8 C string
// and calls free for returned value, thus returned strings should be allocated on heap
char* MakeStringCopy (const char* string)
{
if (string == NULL)
return NULL;
char* res = (char*)malloc(strlen(string) + 1);
strcpy(res, string);
return res;
}
否则会出现空指针异常。
_receiveCallback在oc中向Unity发送回调信息时,是用于android类似的方法:
UnitySendMessage(const char* obj, const char* method, const char* msg);
各参数意义,与android一样。
3)步骤2)可以提前在导出Xcode工程之前进行
新建一个静态库(或动态库)工程,新建.mm(或.m)文件,实现方法;
#ifdef __cplusplus
extern "C" {
#endif
void UnitySendMessage(const char* obj, const char* method, const char* msg);
#ifdef __cplusplus
}
#endif
源码中的以上声明,其实就是为了在此种方式下,调用该方法而不报错。
在Unity导出的Xcode工程中,该项声明可以在iPhone_target_prefix.h中找到,该文件是Xcode的一个预编译文件。
因此,若按照方法2),其实以上声明可以去掉;
这种方式开发时,可以将编写完成的代码,编译成.a或者.framework文件,或者也可以保留成源码文件。
coding完成后,在Unity工程的Plugins目录下,新建iOS目录,将源文件放在该目录下,然后导出Xcode工程,源文件会出现在Xcode工程的Libraries目录下。
须注意,只能放在iOS下不可嵌套目录,否则无法识别。
若已编译成库文件,则只能在导出Xcode工程后,再引入。
在Unity官方文档中,都有这些说明,扔个链接:Getting Started with iOS Development
4)运行工程,就可以出现与android类似的效果。
unity与iOS存在编码问题,因此中文会乱码,此问题没有在Demo中解决。
三. 其他
有时运行会报找不到libiPhone-lib.a库的错误,这时,修改一下Build Settings里面的Library Search paths即可。
Demo链接地址:Demo