Unity安卓插件教程(3)

声明 转载原文链接:https://blog.csdn.net/z3465875/article/details/81298566

系列教程请看原地址Unity Android plugin tutorial (3/3) Class architecture by Geri Borbás at blog.eppz.eu.
经原作者同意,由Dftty翻译 

和其他本地插件(IOS)部分一起创建Unity安卓插件最终都可以在一个单独的Unity c#类中使用,可以被应用程序的其余部分连接。因此除了一些Unity安卓插件脚本Fragment的实现细节,这个文章的其余部分主要关注Unity部分。

这里写图片描述

创建一个跨平台的本地Unity插件,像在上图中调用本地Altert视图,需要一个通用的平台无关的原始抽象特性。

前期教程

虽然这篇教程会在开头有点繁重,但我会保证代码简洁明了。

虽然我认为最好了解这些底层设计的原理,但想要直接阅读工程源码的人可以在GitHub上获取eppz! Alert 的整个Unity工程源码。

Checkout eppz! Alert at github

插件结果接口

当设计多平台支持的本地Unity插件,我们意识到本地特性需要一个单独的接口。在现在的Unity插件示例工程中我们通过两个按钮暴露出alert视图,Unity中对插件的调用应该像下面这样。

...
public void Show()
{
    // Call plugin (of whichever platform).
    plugin.ShowAlertWithAttributes(
        text.title,
        text.message,
        text.buttonTitle,
        text.cancelButtonTitle
    );
}
...

就像是包含了一个本地的代理回调方法,我设计的是使用UnityEvent作为回调(可以在没有代码的情况下使用prefab),在这里我没有将回调包含进去(更多细节之后会讲)

Unity安卓插件类的组织

在实现本地特性之前,还有一些必要的 Android Studio 插件工程的设置,你可能已经在Unity安卓插件教程(2/3)工程设置和开发流程中完成这些操作。

为了在Android Studio 工程中接入Unity java类(像UnityPlayerUnityPlayerActivity),我们需要将库连接到工程中。在Unity 5 之后,对应的JAR文件可以在下图中的位置找到

/Applications/Unity/PlaybackEngines/AndroidPlayer/Variations/mono/Release/Classes/classes.jar

 

这里写图片描述

因此除了将Classes.jar文件复制到插件模块的libs文件夹下(我更倾向于将这个文件重命名为比classes.jar更有意义的名字),我们还需要添加一个依赖,并且指出这个jar包会最终由应用本身提供(这样classes就不用包含到我们的插件包中了)。将这个依赖添加到插件模块的Gragle文件中(更多关于Gradle文件的知识请看Unity安卓插件教程(2/3)工程设置和开发流程)。

...
dependencies {
    provided files('./libs/UnityPlayer.jar') // Avoid redundant Unity Player in final application APK
}
...

有了上面的导入,你就可以将import com.unity3d.player.UnityPlayer; 这句代码放到源文件顶端然后使用Unity安卓Java类了。获取UnityPlayer中的静态方法或者获取currentActivity,还有UnitySendMessage这个必要的类。

在你导入这个库之后,你可以查看Android Studio如何为你反编译Unity Android player Java文件,你可以查看UnityPlayer和UnityPlayerActivity的源码。内容和我分享的Decompiled Java sources of Unity Player for Android类似。

创建Unity 安卓插件类

完成上面的步骤后,你可以开始创建自己的插件类了。除非你需要特殊的Activity特性,我强烈建议使用Fragment这个基础类,在开发Unity 安卓插件的时候有数不清的好处(更多细节请看 Unity 安卓插件开发教程(1/3) 基础)。下面的代码片段创建了一个在插件中初始化的静态start() 方法,然后将它添加到主应用的Activity中。

请注意这里我仅展示了部分代码,你还可以在下面的GitHub链接上查看这个文件的所有内容。

package com.eppz.plugins;
import android.app.Fragment;
import com.unity3d.player.UnityPlayer;
public class EPPZ_Alert extends Fragment
{
    // Constants.
    public static final String TAG = "EPPZ_Alert";
    // Singleton instance.
    public static EPPZ_Alert instance;
    // Unity context.
    String gameObjectName;
    public static void start(String gameObjectName)
    {
        // Instantiate and add to Unity Player Activity.
        instance = new EPPZ_Alert();
        instance.gameObjectName = gameObjectName; // Store 'GameObject' reference
        UnityPlayer.currentActivity.getFragmentManager().beginTransaction().add(instance, EPPZ_Alert.TAG).commit();
    }
...
}

See full EPPZ_Alert.java at github

静态的TAG常量可以方便的在你想要从其他类中解析这个Fragment时使用(使用FragmentManager.findFragmentByTag())。

静态的instance字段持有这个插件实例的引用,可以在Unity中使用AndroidJavaClass.GetStatic()轻易获取。

持有一个在Unity场景中的挂有该脚本实例GameObject的名字变量,可以方便的使用UnitySendMessage发送回调。有了这个字段,你可以在Unity场景中通过多个GameObject持有和管理多个插件fragment实例。

为了在安卓配置改变时保存这个Fragment实例(例如设备旋转),我们可以在实现的onCreate() 模板方法中让它保持。

...
@Override
public void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    setRetainInstance(true); // Retain between configuration changes (like device rotation)
}
...

See full EPPZ_Alert.java at github

有了这些,你可以在Unity中解析类和实例。我还倾向于使用AndroidJavaClass.GetStatic()给插件实例提供一个getter实例方法,获取方法就是后面的一行代码。

public class EPPZ_Alert_Android
{
  ...
  AndroidJavaClass _class;
  AndroidJavaObject instance { get { return _class.GetStatic<AndroidJavaObject>("instance"); } }
  void Setup()
  {
    _class = new AndroidJavaClass("com.eppz.plugins.EPPZ_Alert");
    _class.CallStatic("start", gameObjectName);
  }
  ...
}

See full EPPZ_Alert_Android.cs at github

添加特性

Fragment类中,你可以仅创建一个公开实例方法提供插件特性。这个插件的单独特性是显示本地的alert对话框,因此只需要一个单独的方法,和一个这个alert的回调方法。我不会再这里展示alert的所有细节(如果有兴趣,可以在这里EPPZ_DialogFragment.java查看), 这里只展示接入入口。

...
public void showAlertWithAttributes(String title, String message, String positiveButtonTitle, String negativeButtonTitle)
{
  // Create and show dialog.
  EPPZ_DialogFragment dialogFragment ...
}
public void dialogDidFinishWithResult(EPPZ_DialogFragment dialog, String selectedButtonTitle)
{
    UnityPlayer.UnitySendMessage(gameObjectName, "AlertDidFinishWithResult", selectedButtonTitle);
}
...

See full EPPZ_Alert.java at github

当你在一个Unity类中设置好这个插件的实例后,你可以在Unity中使用AndroidJavaObject.Call()调用这个的方法。

instance.Call("showAlertWithAttributes", title, message, buttonTitle, cancelButtonTitle);

 

回调会被送到相应名字的GameObject中,我们在上面的教程中的AlertDidFinishWithResult()实现了这个回调。同时也用一个字符串变量指示了选中的按钮。

...
public void AlertDidFinishWithResult(string selectedButtonTitle)
{
  Debug.Log("Button '"+selectedButtonTitle+"' selected.");
}
...

接收这个实例的回调脚本并不是必须需要的。如果你支持多平台和扩展开发特性集,我建议你用不同的文件实现平台代码,然后提供一个通用接口。

给平台创建通用接口

如果你看Google Analytics plugin for Unity工程的源码,你可以在GoogleAnalyticsV4.cs这个类中找到为不同平台实例化的追踪类(看第107行)

我们的观点和这个类似,但不是每次使用#if UNITY_ANDROID,我倾向于使用一个包含静态工厂方法的类,这样可以只使用一次#if UNITY_ANDROID,然后在不同的平台类中实现其余的特性(包括编辑回调)。

在我们的Unity 本地提醒插件中,抽象类包含一个会在构造器中初始化的gameObjectName字段,还有两个虚模板方法

public class EPPZ_Alert
{
    ...
    protected string gameObjectName;
    public EPPZ_Alert(string gameObjectName)
    {
        this.gameObjectName = gameObjectName;
        Setup();
    }
    virtual protected void Setup() { }
    virtual public void ShowAlertWithAttributes(string title, string message, string buttonTitle, string cancelButtonTitle) { }
    ...
}

See full EPPZ_Alert.cs at github

构造器非常明了,它调用了模板方法SetUp(),子类实现后可以在SetUp方法中设置自己的物体名称。我将gameObjectName定义成protected,这样子类可以获取它,并且不会暴露给其他的类。

上面的ShowAlertWithAttributes()方法也是抽象方法,每个该方法都会有 title ,messagebuttonTitlecancelButtonTitle四个参数。这里有足够的空间来进行平台化提示视图的实现,但这个教程的单个方法并不是很完美。外部的用户可以像我开头代码中那样直接调用到这个方法

现在你可以为每个平台来实现子类并覆盖模板方法。安卓实现应该就像我之前提到的,但是现在你可以看到整个抽象类模板的实现。

public class EPPZ_Alert_Android : EPPZ_Alert
{

    public EPPZ_Alert_Android(string gameObjectName) : base(gameObjectName) { }
    #region Native setup
    AndroidJavaClass _class;
    AndroidJavaObject instance { get { return _class.GetStatic<AndroidJavaObject>("instance"); } }
    override protected void Setup()
    {
        // Start plugin <span class="eppz inlineCode">Fragment`.
        _class = new AndroidJavaClass("com.eppz.plugins.EPPZ_Alert");
        _class.CallStatic("start", gameObjectName);
    }
    #endregion
    #region Features
    override public void ShowAlertWithAttributes(string title, string message, string buttonTitle, string cancelButtonTitle)
    {
        Debug.Log("EPPZ_Alert_Android.ShowAlertWithAttributes(`"+title+"`, `"+message+"`, `"+buttonTitle+"`, `"+cancelButtonTitle+"`, )");
        instance.Call("showAlertWithAttributes", title, message, buttonTitle, cancelButtonTitle);
    }
    // ...
    #endregion
}

See full EPPZ_Alert_Android.cs at github

在代码开头,表示是EPPZ_Alert的一个子类实现,并且继承了构造方法。通过重写Setup()方法,本地设置区域在Java类和该类之间架起了桥梁(会在创建的时候调用)。

这里的特性只是通过之前创建的Java instance实例,然后使用AndroidJavaObject.Call()方法来调用插件方法。

IOS端的EPPZ_Alert实现几乎完全一样,它在每次调用还传递GameObject的name引用(这里仅使用了静态调用方法),如果你查看完整代码,你可以看到它的实现方法和Android对应部分相同。

...
override public void ShowAlertWithAttributes(string title, string message, string buttonTitle, string cancelButtonTitle)
{
    Debug.Log("EPPZ_Alert_iOS.ShowAlertWithAttributes(`"+title+"`, `"+message+"`, `"+buttonTitle+"`, `"+cancelButtonTitle+", )");
    _showAlertWithAttributes(
        title,
        message,
        buttonTitle,
        cancelButtonTitle,
        gameObjectName
    );
}
...

See full EPPZ_Alert_iOS.cs at github

编辑部分只是为了模拟本地功能(点击“Ok”按钮),同时也为了在编辑运行模式下允许调试。这个特性模拟了UnitySendMessage给这个物体发送回调后的状态。

...
override public void ShowAlertWithAttributes(string title, string message, string buttonTitle, string cancelButtonTitle)
{ GameObject.Find(gameObjectName).SendMessage("AlertDidFinishWithResult", buttonTitle); }
...

我们完成不同类的实现之后,我们可以实现抽象类中的工厂方法了。这是一个简单的静态方法,根据当前平台设置决定现在该使用哪个类

public class EPPZ_Alert
{
    ...
    public static EPPZ_Alert pluginWithGameObjectName(string gameObjectName)
    {
        EPPZ_Alert plugin;
        // Get plugin class for the actual platform.
        #if UNITY_IPHONE
        plugin = (Application.isEditor)
            ? (EPPZ_Alert)new EPPZ_Alert_Editor(gameObjectName)
            : (EPPZ_Alert)new EPPZ_Alert_iOS(gameObjectName);
        #elif UNITY_ANDROID
        plugin = (Application.isEditor)
            ? (EPPZ_Alert)new EPPZ_Alert_Editor(gameObjectName)
            : (EPPZ_Alert)new EPPZ_Alert_Android(gameObjectName);
        #endif
        return plugin;
    }
    ...
}

See full EPPZ_Alert.cs at github

同时这是一个很大的进步,在不同的文件中维护不同平台的实例,减少了许多#if条件判断,最大的好处是把插件当做一个客户端来唤醒。

使用通用插件接口

这个例子中午决定使用UnityEvents创建一个以提醒视图为基础的Prefab,这个插件的用户可以不用编码直接运行它(现在可以在Unity Asset Stroe 获取),全由自己来决定。使用插件的方法是执行Alert.cs脚本。

这里写图片描述

标签可以在Inspector中设置,调用prefab上的Show()方法后,alert视图会出现(添加到按钮上的OnClick()事件中),并且触发AlertDidFinishWithResult(String)回调方法,参数是直接在Text实例中这是的一个字符串实例。

...
private EPPZ_Alert plugin;
void Start()
{
    // Create plugin instance (of whichever platform).
    plugin = EPPZ_Alert.pluginWithGameObjectName(gameObject.name);
}
...
public void Show()
{
    // Call plugin (of whichever platform).
    plugin.ShowAlertWithAttributes(
        text.title,
        text.message,
        text.buttonTitle,
        text.cancelButtonTitle
    );
}
...
public void AlertDidFinishWithResult(string selectedButtonTitle)
{
    // Trigger event.
    alertDidFinishWithResult.Invoke(selectedButtonTitle);
}
...

​​​​​​​

See full EPPZ_Alert.cs at github

要使用这个插件,首先需要向工厂请求一个实例,然后使用接口获取已知的本地特性。

在插件使用GameObject的名称初始化之后,插件回调AlertDidFinishWithResult()也应该在这里实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值