Unity Android Plugin 开发教程
2017-07-28 18:04
507 查看
开发环境Windows平台(Unity2017.1.0f3 Personal ,Android Studio 2.3.3)
在Unity项目中构建和使用Android Plugin
Unity支持几种类型的Android plug-ins:AAR 插件 和 Android Library
JAR 插件
继承UnityPlayerActivity
Native(C++) 插件
Unity Android runtime
Untiy Android runtime通过继承自FrameLayout的UnityPlayer实现,UnityPlayer实现了触控,键盘输入,相机,位置等特性。虽然这个UnityPlayer实现了大部分的native功能,但它不是应用程序的入口。
在通用的Android Unity应用程序中,程序的入口是UnityPlayerActivity。如果你看一下APK文件反编译后的AndroidManifest.xml文件,可以看到它是如何标记UnityPlayerActivity作为应用程序的Launcher的。
查看Unity安装目录发现,UnityPlayerActivity的源码可以在C:\Program Files\Unity\Editor\Data\PlaybackEngines\AndroidPlayer\Source中查看。
public class UnityPlayerActivity extends Activity { protected UnityPlayer mUnityPlayer; // don't change the name of this variable; referenced from native code // Setup activity layout @Override protected void onCreate (Bundle savedInstanceState) { requestWindowFeature(Window.FEATURE_NO_TITLE); super.onCreate(savedInstanceState); getWindow().setFormat(PixelFormat.RGBX_8888); // <--- This makes xperia play happy mUnityPlayer = new UnityPlayer(this); setContentView(mUnityPlayer); mUnityPlayer.requestFocus(); } ......... // For some reason the multiple keyevent type is not supported by the ndk. // Force event injection by overriding dispatchKeyEvent(). @Override public boolean dispatchKeyEvent(KeyEvent event) { if (event.getAction() == KeyEvent.ACTION_MULTIPLE) return mUnityPlayer.injectEvent(event); return super.dispatchKeyEvent(event); } ......... }
可以看到UnityPlayerActivity继承自Activity,并且UnityPlayerActivity持有一个UnityPlayer实例。UnityPlayerActivity通过UnityPlayer分派native 事件。
通用的Android插件开发,通过继承UnityPlayerActivity,并使子类成为整个应用程序的LAUNCHER Activity,接下来将介绍继承UnityPlayerActivity方式的Android Plugin 。
继承UnityPlayerActivity方式的Android Plugin
Android Plugin需要包含Android项目中build后得到的app-debug.aar和Manifest.xml以及资源文件等,提供给Unity项目使用。文件存放在Unity项目中的/Assets/Plugins/Android。Unity项目中的代码通过app-debug.aar与封装在其中的Android代码进行交互。For more details about .aar, see Android Developer Doc. And for more information about “How Unity produces the Android Manifest”, see Unity Developer Doc
那么,接下来新建Android项目,进而生成app-debug.aar文件和Manifest.xml文件。
Android端的操作
新建Android 项目
1.将项目切换成project的视图,打开app目录下的build.gradle文件,1.将apply plugin: ‘com.android.application’,改成apply plugin: ‘com.android.library’
2.然后删除applicationId
2.修改Manifest.xml文件
在activity中添加
<meta-data android:name="unityplaer.UnityActivity" android:value="true"/>
引入Unity的classes.jar包
从Unity 的安装目录找到unity的classes.jar包。Windows目录:C:\ProgramFiles\Unity\Editor\Data\PlaybackEngines\AndroidPlayer\Variations\mono\Release\classes.jar
Mac下目录:/Applications/Unity/PlaybackEngines/AndroidPlayer/Variations/mono/Release/Classes\classes.jar
将其拷贝到UnityAndroid项目app目录下的libs目录下,右键Add as library,导入之后可以发现在build.gradle中就有他的引入了。
编写Android项目与Unity项目交互的代码
首先需要让MainActivity继承UnityPlayerActivity,同时删除onCreate方法中的setContentView()public class MainActivity extends UnityPlayerActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } //示例1: //Unity端调用该函数 public void ShowToast(final String msg){ // 需要在UI线程下执行 runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(getApplicationContext(),msg, Toast.LENGTH_SHORT).show(); new AlertDialog.Builder(MainActivity.this).setMessage(msg).show(); } }); } //示例2: //Unity端调用该函数 public void setUnityText(){ runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(getApplicationContext(),"Android 端调用setText", Toast.LENGTH_SHORT).show(); //调用Unity端函数 UnityPlayer.UnitySendMessage("Canvas","setText","Android 端调用setText"); } }); } }
Build得到app-debug.aar文件和Manifest.xml文件
分别在/app/build/outputs/aar和/app/src/main目录下接下来需要将app-debug.aar用解压软件打开,并且删除libs目录下的classes.jar
Unity端的操作
创建Unity项目
新建Unity项目,并新建如下目录将Android端得到的app-debug.aar文件和Manifest.xml文件放在/Plugins/Android目录下,同时在Hierarchy下按照图示新建Canvas,Button和Text:编写C#脚本
同时新建名为AndroidTest.cs的C#脚本:using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class AndroidTest : MonoBehaviour { // Use this for initialization void Start () { } // Update is called once per frame void Update () { } public void BtnShowMessage() { using (AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) { using(AndroidJavaObject jo = jc.GetStatic<AndroidJavaObject>("currentActivity")) { // 调用Android端方法 jo.Call("ShowToast", "Unity调用了Android中的AlertDialog"); } } } public void BtnSetText() { using (AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) { using (AndroidJavaObject jo = jc.GetStatic<AndroidJavaObject>("currentActivity")) { // 调用Android端方法 jo.Call("setUnityText"); } } } //Android端调用该方法 public void setText(string result){ Text text = GameObject.Find ("UnityText").GetComponent<Text> (); text.text = result; } }
编译运行
按照标号步骤进行1. 选择Build的平台->->2. 添加Scenes ->-> 3. 设置Identification ->-> 4. 设置包名和 API Level ->-> 5. 编译运行效果展示
使用AAR或JAR方式的Android Plugin
Android端的操作
在Android Studio中新建项目
1.选择Add No Activity新建Modue,选择Android Library
添加AndroidPlugin.java
import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.BatteryManager; import java.text.SimpleDateFormat; import java.util.Date; public class AndroidPlugin { // Needed to get the battery level. private Context context; public AndroidPlugin(Context context) { this.context = context; } // Return the battery level as a float between 0 and 1 (1 being fully charged, 0 fulled discharged) public float GetBatteryPct() { Intent batteryStatus = GetBatteryStatusIntent(); int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1); float batteryPct = level / (float)scale; return batteryPct; } // Return whether or not we're currently on charge public boolean IsBatteryCharging() { Intent batteryStatus = GetBatteryStatusIntent(); int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1); return status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL; } private Intent GetBatteryStatusIntent() { IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); return context.registerReceiver(null, ifilter); } //Get System Time public String getSysTime(){ return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()).toString(); } }
Build Module之后,在模块的子目录/build/outputs/arr中得到.aar 和 .jar
Unity端的操作
创建Unity项目
将得到的.arr(将.arr解压就可以得到.jar) 放在Assert目录下,可以放在任意的目录下,官方推荐使用.aar 。并且按照如下目录新建Canvas和Text。编写C#脚本
编写BatteryLevelPlugin.cs :Unity提供了两种方式与java进行交互
AndroidJNI 和 AndroidJNIHelper
AndroidJavaClass , AndroidJavaObject 和 AndroidJavaProxy
同时,官方推荐如下调用方式:
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; public class BatteryLevelPlugin{ public static float GetBatteryLevel() { if (Application.platform == RuntimePlatform.Android) { using (var javaUnityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) { using (var currentActivity = javaUnityPlayer.GetStatic<AndroidJavaObject>("currentActivity")) { using (var androidPlugin = new AndroidJavaObject("com.hiscene.androidsysinfo.AndroidPlugin", currentActivity)) { return androidPlugin.Call<float>("GetBatteryPct"); } } } } return 1f; } public static string GetSysTime() { AndroidJNI.AttachCurrentThread(); IntPtr javaClass = AndroidJNI.FindClass("com/unity3d/player/UnityPlayer"); IntPtr fid = AndroidJNI.GetStaticFieldID(javaClass, "currentActivity", "Landroid/app/Activity;"); IntPtr obj = AndroidJNI.GetStaticObjectField(javaClass, fid); IntPtr pluginClass = AndroidJNI.FindClass("com/hiscene/androidsysinfo/AndroidPlugin"); IntPtr initMethod = AndroidJNI.GetMethodID(pluginClass, "<init>", "(Landroid/content/Context;)V"); jvalue[] jv = new jvalue[1]; //TODO IntPtr pobj = AndroidJNI.NewObject(pluginClass, initMethod,jv); IntPtr enableMethod = AndroidJNI.GetMethodID(pluginClass, "getSysTime", "()Ljava/lang/String;"); return AndroidJNI.CallStringMethod(pobj, enableMethod, new jvalue[1]); } //方式一: public static string GetSysTimeAndroidJNI() { AndroidJNI.AttachCurrentThread(); IntPtr javaClass = AndroidJNI.FindClass("com/hiscene/androidsysinfo/SysTime"); IntPtr initMethod = AndroidJNI.GetMethodID(javaClass, "<init>", "()V"); IntPtr pobj = AndroidJNI.NewObject(javaClass, initMethod, AndroidJNIHelper.CreateJNIArgArray(new object[1])); IntPtr enableMethod = AndroidJNI.GetMethodID(javaClass, "getSysTime", "()Ljava/lang/String;"); return AndroidJNI.CallStringMethod(pobj, enableMethod, new jvalue[1]); } //方式二: public static string GetSysTimeAndroidJavaClass() { if (Application.platform == RuntimePlatform.Android) { using (var javaUnityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) { using (var currentActivity = javaUnityPlayer.GetStatic<AndroidJavaObject>("currentActivity")) { using (var androidPlugin = new AndroidJavaObject("com.hiscene.androidsysinfo.AndroidPlugin", currentActivity)) { return androidPlugin.Call<string>("getSysTime"); } } } } return "Time yyyy-MM-dd HH:mm:ss"; } }
编写BatteryMonitor.cs :
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class BatteryMonitor : MonoBehaviour { public Text batteryLevelText; public Text batteryLevelIcon; public Text sysTime; static readonly string BATTERY_LEVEL_100 = Char.ConvertFromUtf32(0xf240); static readonly string BATTERY_LEVEL_75 = Char.ConvertFromUtf32(0xf241); static readonly string BATTERY_LEVEL_50 = Char.ConvertFromUtf32(0xf242); static readonly string BATTERY_LEVEL_25 = Char.ConvertFromUtf32(0xf243); static readonly string BATTERY_LEVEL_0 = Char.ConvertFromUtf32(0xf244); // Update is called once per frame void Update () { UpdateStatusIndicators(); sysTime.text = BatteryLevelPlugin.GetSysTime(); } void UpdateStatusIndicators() { var currentBatteryLevel = BatteryLevelPlugin.GetBatteryLevel() * 100f; batteryLevelText.text = String.Format("{0}%", currentBatteryLevel); // Show the icon that matches the current level most closely. if (currentBatteryLevel >= 88) { batteryLevelIcon.text = BATTERY_LEVEL_100; } else if (currentBatteryLevel >= 63) { batteryLevelIcon.text = BATTERY_LEVEL_75; } else if (currentBatteryLevel >= 38) { batteryLevelIcon.text = BATTERY_LEVEL_50; } else if (currentBatteryLevel >= 13) { batteryLevelIcon.text = BATTERY_LEVEL_25; } else { batteryLevelIcon.text = BATTERY_LEVEL_0; } } }
按照步骤编译运行
步骤:1. 选择Build的平台->->2. 添加Scenes ->-> 3. 设置Identification ->-> 4. 设置包名和 API Level ->-> 5. 编译运行效果展示
源代码
https://github.com/Rolyyu/UnityAndroidPlugin参考文献
[1] https://docs.unity3d.com/Manual/PluginsForAndroid.html[2] http://www.voidcn.com/blog/Silk2018/article/p-6632911.html
[3] http://blog.csdn.net/zhangdi2017/article/details/65629589
[4] https://www.yangzhenlin.com/unity-android-plugin/
[5] https://medium.com/@datdeveloper/how-to-make-android-plugin-for-unity-take-photo-from-camera-and-gallery-c12fe247c770
[6] http://addcomponent.com/android-native-plugin-unity/
相关文章推荐
- Unity Android Plugin开发指南
- Unity Android Plugin开发指南
- Unity Android Plugin 开发指南
- Unity开发基于Android平台的脑波游戏教程
- Unity iPhone Touch Animation Tutorial,untiy3d iphone简单动画开发教程
- Android应用程序开发教程 - 实现一个登录对话框
- Android 1.5 SDK与SDK开发教程
- Android / OPhone开发系列教程正在发布中……
- Android 1.5 SDK与SDK开发教程
- Android程序开发初级教程(一)
- Google 手机操作系统 Android 开发教程 转载
- android开发教程--学习记录1
- Android高手进阶教程(八)之----Android Widget开发案例(世界杯倒计时!)
- Windows 7部署Android开发环境教程
- Android高手进阶教程(八)之----Android Widget开发案例(世界杯倒计时!)
- Android 3D游戏开发教程
- Android程序开发初级教程(一)
- Android程序开发初级教程(一)
- Android程序开发初级教程
- Windows系统部署 Android 开发环境以及验证之傻瓜式教程(SDK+Eclipse+ADT)