美文网首页
Android 与 Unity扯不清的关系

Android 与 Unity扯不清的关系

作者: JasonChen8888 | 来源:发表于2019-10-12 14:10 被阅读0次

    历史背景

    近期在游戏SDK接入的技术支持中,不断有游戏反馈希望有现成的unity插件可以直接接入,为了减轻和方便游游戏方接入,对SDK进行unity的插件开发

    用到的兵器

    1、Android studio 官方下载地址
    2、Unity 2017.4.2f2 官方下载地址
    官网有时候无法访问,这边提供百度
    unity2017中文安装包:链接:https://pan.baidu.com/s/1gW2tDhAiodKf0qRNUNCKWg 提取码:k8to
    unity2017的Android支持的插件:链接:https://pan.baidu.com/s/1tTCAYlZnDUtXIizz_70Gpg 提取码:0mtp

    Android方面的操作(这边提供的是aar的形式)

    1、新建一个Android Library的module


    创建插件module.png

    2、编译下对应的module,名称和包名(包名就是unity的项目的包名)


    填写包名.png
    3、原有的要接入SDK,已经达成aar包,将SDK的aar包,导入到我们这个plugin的module的libs下,配置一下gradle
    gradle的配置修改.png

    4、个人这边将SDK的内容进行了封装到一个类

    public class SinglePaySDK {
    
      private static final String TAG = "SDK";
    
      private SingleOperateCenter mOpeCenter;
    
      public SinglePaySDK(Activity context, String appKey, String gameName, int orientation,
          SingleOperateCenter.SingleRechargeListener singleRechargeListener) {
        initSDK(context, appKey, gameName, orientation, singleRechargeListener);
      }
    
      private void initSDK(Activity activity, String appKey, String gameName, int orientation,
          SingleOperateCenter.SingleRechargeListener singleRechargeListener) {
        Log.i(TAG, "appKey = " + appKey + ", orientation = " + orientation);
    
        if (isInitReady()) {
          return;
        }
    
        mOpeCenter = SingleOperateCenter.getInstance();
        new OperateCenterConfig.Builder(activity)
            .setDebugEnabled(true)  //发布游戏时,要设为false
            .setOrientation(orientation) //设置SDK界面方向,应与游戏设置一直
            .setSupportExcess(true) //设置是否支持超出金额充值
            .setGameKey(appKey)  //换成实际游戏的game key
            .setGameName(gameName)  //换成实际游戏的名字,原则上与游戏名字匹配
            .build();
    
        mOpeCenter.init(activity, singleRechargeListener);
      }
    
      public void doPay(Activity activity, final String money, final String productName) {
        doPay(activity, money, productName, null);
      }
    
      public void doPay(final Activity activity, final String money, final String productName,
          final String extra) {
        Log.d(TAG, "execute method doPay... money = " + money + ", productName = " + productName);
    
        if (mOpeCenter == null) {
          Log.d(TAG, "SDK need init first");
          return;
        }
        //extra是透传的字段
        if (TextUtils.isEmpty(extra)) {
          mOpeCenter.recharge(activity, money, productName);
        } else {
          mOpeCenter.recharge(activity, money, productName, extra);
        }
      }
    
      public boolean isInitReady() {
        return mOpeCenter != null;
      }
    
      public void doCheck(Activity activity, Callbacks.OnCheckFinishedListener listener) {
        Log.d(TAG, "execute doCheck...");
        if (!isInitReady()) {
          Log.d(TAG, "SDK need init first");
          return;
        }
    
        mOpeCenter.doCheck(activity, listener);
      }
    
      private void doDownload(Activity activity,  Callbacks.OnDownloadListener listener) {
        Log.d(TAG, "execute doDownload...");
        if (!isInitReady()) {
          Log.d(TAG, "SDK need init first");
          return;
        }
    
        mOpeCenter.doDownload(activity, listener);
      }
    
      private void doInstall(Activity activity) {
        Log.d(TAG, "execute doInstall...");
        if (!isInitReady()) {
          Log.d(TAG, "SDK need init first");
          return;
        }
    
        mOpeCenter.doInstall(activity);
      }
    }
    

    5、在unity的安装目录下:D:\Program Files\Unity\Data\PlaybackEngines\AndroidPlayer\Variations\mono\Release\Classes\classes.jar,将该文件拷贝到plugin 的module下的libs目录, 刷新gradle
    6、由于当前默认使用原有的UnityPlayerActivity,并没有继承该该类 继承UnityPlayerActivity的写法
    AndroidManifest.xml文件需要配置

    <activity android:name="com.unity3d.player.UnityPlayerActivity" android:label="@string/app_name" android:screenOrientation="portrait" android:launchMode="singleTask" android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale|layoutDirection|density" android:hardwareAccelerated="false">
              <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
                <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
              </intent-filter>
              <meta-data android:name="unityplayer.UnityActivity" android:value="true" />
        </activity>
                ............其他的代码.......
    

    7、打包成aar


    打包成aar.png

    8、由于我们SDK插件打包成aar的时候,会将libs的unity的classes的jar包一并打包进去,需要手动用压缩软件打开aar将里面的classes.jar的文件删掉

    Unity方面的操作

    1、新建Unity项目,创建Secne场景,添加了Canvas的画布,在画布下添加了,Button和Text

    新建unity场景.png
    2、在unity的project下的assert目录下新建plugins目,然后再plugins目录下,在新建Android目录,将项目的AndroidManifest.xml文件拷贝进去,然后再新家libs目录将aar包copy进去,这边有两个,一个是SDK的aar包,一个我们编写创建的aar包,要注意一个地方是plugin-release的AndroidManifest文件包名和主项目的AndroidManifest不能一样,在17版本编译会出现重复包名的错误
    需要用到的插件信息.png
    3、在Assert目录下创建Scripts目录(这个主要是这边用于存放C#的脚本文件,直接放Assert级的目录下也是可以的)
    创建的脚本文件.png
    4、上述的脚本文件,就需要选择一个主要脚本,将该脚本文件添加到component中,用于绑定到该场景,设置调用通过add component-->scripts, 然后选择所开发的脚本文件
    添加脚本.png
    5、编译这边有两个区分,一个是gradle编译,一个Internal编译,主要区别是在2017版本gradle编译,可以不进行包名设置,internal是需要设置。
    image.png
    Player settings.png
    6、注意PlatForm中Android 是需自己安装的
    unity2017的Android 平台支持的插件:链接:https://pan.baidu.com/s/1tTCAYlZnDUtXIizz_70Gpg 提取码:0mtp
    7、生成apk

    这边讲讲上面C#调用java的相关代码

    先贴代码
    --SinglePaySDKContext.cs文件

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    namespace SinglePay{
        public sealed class SinglePaySDKContext : MonoBehaviour
        {
    
            private AndroidJavaObject currentActivity;
    
            private static readonly SinglePaySDKContext _SinglePaySDKContext = new SinglePaySDKContext();
    
            /*
             * 获取当前实例       
             */
            public static SinglePaySDKContext GetInstance()
            {
                return _SinglePaySDKContext;
            }
    
    
            /*
             * 获取当前Activity       
             */
            public AndroidJavaObject GetActivity()
            {
                if (null == currentActivity)
                {
                    currentActivity = new AndroidJavaClass("com.unity3d.player.UnityPlayer")
                    .GetStatic<AndroidJavaObject>("currentActivity");
                }
    
                return currentActivity;
            }
    
            /*
             * 运行在主UI线程       
             */
            public void RunOnUIThread(AndroidJavaRunnable runnable)
            {
                GetActivity().Call("runOnUiThread", runnable);
            }
    
    
            /*
             * 获取根节点的布局 
             */
            public AndroidJavaObject GetRootLayout()
            {
                AndroidJavaClass R = new AndroidJavaClass("android.R$id");
                return currentActivity.Call<AndroidJavaObject>("findViewById", R.GetStatic<int>("content"));
    
            }
        }
    }
    

    --SinglePaySDK.cs文件

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.UI;
    using SinglePay;
    
    public class SinglePaySDK : MonoBehaviour {
    
        private Transform canvasForm;
        private Button initButton;
        private Button payButton;
        private Button checkButton;
        private Text ResultText;
    
        //表示横屏
        private const int SCREEN_ORIENTATION_LANDSCAPE = 0;
        //表示竖屏
        private const int SCREEN_ORIENTATION_PORTRAIT = 1;
        //表示反向横屏
        private const int SCREEN_ORIENTATION_REVERSE_LANDSCAPE = 8;
        //表示反向竖屏
        private const int SCREEN_ORIENTATION_REVERSE_PORTRAIT = 9;
    
        //获取全局的上下文件对象
        private SinglePaySDKContext singlePaySDKContext;
    
        //获取单机支付SDK的对象
        private AndroidJavaObject singlePaySDK;
    
        //是否开始计时
        private bool IsTiming;
        //倒计时
        private float CountDown; 
    
        // Use this for initialization
        void Start () {
    
            singlePaySDKContext = SinglePaySDKContext.GetInstance();
    
            canvasForm = GameObject.Find("Canvas").transform;
            //获取控件对象设置时间监听
            initButton = canvasForm.Find("InitButton").GetComponent<Button>();
            payButton = canvasForm.Find("PayButton").GetComponent<Button>();
            checkButton = canvasForm.Find("CheckButton").GetComponent<Button>();
            ResultText = canvasForm.Find("ResultText").GetComponent<Text>();
            initButton.onClick.AddListener(InitSDK);
            payButton.onClick.AddListener(doPay);
            checkButton.onClick.AddListener(doCheck);
            ResultText.text = "hello";
    
            //Input.backButtonLeavesApp = true;//设置返回键,是否退出程序。(系统默认为 false,所以不自己写方法是不会退出 App 的)
        }
    
        public void InitSDK()
        {
    
            //AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
            //AndroidJavaObject jo = jc.GetStatic<AndroidJavaObject>("currentActivity");
    
            //调用成员方法
            //传入appkey
            //SingleRechargeListenerProxy listenerProxy = new SingleRechargeListenerProxy(this);
            //jo.Call("init","70001", SCREEN_ORIENTATION_PORTRAIT, listenerProxy);
    
            //调用静态方法
            //ResultText.text = jo.CallStatic<string>("GetInformation");
        
            singlePaySDKContext.RunOnUIThread(new AndroidJavaRunnable(() =>
            {
                SingleRechargeListenerProxy listenerProxy = new SingleRechargeListenerProxy(this);
                singlePaySDK = new AndroidJavaObject("cn.m4399.plugin.SinglePaySDK", singlePaySDKContext.GetActivity(), "70001", "测试游戏",SCREEN_ORIENTATION_PORTRAIT, listenerProxy);
                ResultText.text = "init sdk";
    
            }));
        }
    
        public void doPay()
        {
    
            //AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
            //AndroidJavaObject jo = jc.GetStatic<AndroidJavaObject>("currentActivity");
    
            singlePaySDKContext.RunOnUIThread(new AndroidJavaRunnable(() =>
            {
                bool isInitReady = singlePaySDK.Call<bool>("isInitReady");
    
                //调用成员方法
                //传入金额和商品名称
                //对应java的方法 doPay(money, productName, extra) 注:extra是透传参数
                //jo.Call("doPay","10","砖石");
                if (isInitReady)
                {
                    singlePaySDK.Call("doPay", singlePaySDKContext.GetActivity(), "10", "砖石", "测试数据");
                    ResultText.text = "do pay";
                }
                else
                {
                    ResultText.text = "sdk init is not finished";
                }
    
            })); 
           
        }
    
        /*
         * 充值回调   
         */
        internal class SingleRechargeListenerProxy : AndroidJavaProxy
        {
    
            private SinglePaySDK sdk;
            public SingleRechargeListenerProxy(SinglePaySDK singlepay) : base("cn.m4399.operate.SingleOperateCenter$SingleRechargeListener")
            {
                sdk = singlepay;
            }
            /**
             * 加载成功
             */
            void onRechargeFinished(bool success, string msg)
            {
                Debug.Log("success = "+ success +",msg = "+msg);
                sdk.ResultText.text = "success = " + success + ",msg = " + msg;
            }
    
            /**
             * 加载失败
             * @param message
             */
            bool notifyDeliverGoods(bool shouldDeliver, AndroidJavaObject o)
            {
                Debug.Log("shouldDeliver = "+ shouldDeliver +", o "+ o.Call<string>("getJe"));
                return shouldDeliver;
            }
        }
    
    
        public void doCheck()
        {
            singlePaySDKContext.RunOnUIThread(new AndroidJavaRunnable(() =>
            {
                bool isInitReady = singlePaySDK.Call<bool>("isInitReady");
    
                //调用成员方法
                //传入金额和商品名称
                //对应java的方法 doPay(money, productName, extra) 注:extra是透传参数
                //jo.Call("doPay","10","砖石");
                if (isInitReady)
                {
                    OnCheckFinishedListenerProxy listenerProxy = new OnCheckFinishedListenerProxy(this);
                    singlePaySDK.Call("doCheck", singlePaySDKContext.GetActivity(), listenerProxy);
                    ResultText.text = "do check update";
                }
                else
                {
                    ResultText.text = "sdk init is not finished";
                }
    
            }));
        }
    
        /*
        * 充值回调   
        */
        internal class OnCheckFinishedListenerProxy : AndroidJavaProxy
        {
    
            private SinglePaySDK sdk;
            public OnCheckFinishedListenerProxy(SinglePaySDK singlepay) : base("cn.m4399.operate.model.callback.Callbacks$OnCheckFinishedListener")
            {
                sdk = singlepay;
            }
    
            void onCheckResponse(AndroidJavaObject o)
            {
                //这个回调参数对象是java的 UpgradeInfo,查看demo文档
                int code = o.Call<int>("getResultCode");
                if(code == o.GetStatic<int>("APK_CHECK_NO_UPDATE"))
                {
                    sdk.ResultText.text = "已经是最新版本";
                }
                else if(code == o.GetStatic<int>("APK_CHECK_NEED_UPDATE"))
                {
                    sdk.ResultText.text = "新版本号:"+o.Call<string>("getVersionCode");
                    sdk.doDownload();
                }
                else
                {
                    sdk.ResultText.text = "检查更新失败";
                }
            }
        }
    
        public void doDownload()
        {
            singlePaySDKContext.RunOnUIThread(new AndroidJavaRunnable(() =>
            {
    
                OnDownloadListenerProxy listenerProxy = new OnDownloadListenerProxy(this);
                 singlePaySDK.Call("doDownload", singlePaySDKContext.GetActivity(), listenerProxy);
                 ResultText.text = "do download";
               
            }));
        }
    
        internal class OnDownloadListenerProxy : AndroidJavaProxy
        {
    
            private SinglePaySDK sdk;
            public OnDownloadListenerProxy(SinglePaySDK singlepay) : base("cn.m4399.operate.model.callback.Callbacks$OnDownloadListener")
            {
                sdk = singlepay;
            }
    
            void onDownloadStart()
            {
                sdk.ResultText.text = "开始下载...";
            }
    
            void onDownloadProgress(long progress, long max)
            {
                sdk.ResultText.text = "总大小:"+max+", 已下载:"+progress;
            }
    
            void onDownloadSuccess()
            {
                sdk.ResultText.text = "下载成功...";
                sdk.doInstall();
            }
    
            void onDownloadFail(int resultCode, string message)
            {
                sdk.ResultText.text = "下载失败,resultCode = " + resultCode + ", message:" + message;
            }
        }
    
        public void doInstall()
        {
            singlePaySDKContext.RunOnUIThread(new AndroidJavaRunnable(() =>
            {
                singlePaySDK.Call("doInstall", singlePaySDKContext.GetActivity());
                ResultText.text = "do install";
    
            }));
        }
    
    
        // Update is called once per frame (每一帧刷新)
        void Update () {
            if (Application.platform == RuntimePlatform.Android && Input.GetKeyDown(KeyCode.Escape)) // 返回键
            {
                if (CountDown == 0)                          //当倒计时时间等于0的时候
                {
                    CountDown = Time.time;                   //把游戏开始时间,赋值给 CountDown
                    IsTiming = true;                        //开始计时
                    ToastUtils.showToast("再按一次退出"); //显示提示信息 —— 这里的提示方法,需要根据自己需求来完成(用你自己所需要的方法完成提示)
                    ResultText.text = "再按一次退出";
                }
                else
                {
                    Application.Quit();                      //退出游戏
                }
            }
            if (IsTiming) //如果 IsTiming 为 true 
            {
                if ((Time.time - CountDown) > 2.0)           //如果 两次点击时间间隔大于2秒
                {
                    CountDown = 0;                           //倒计时时间归零
                    IsTiming = false;                       //关闭倒计时
                }
    
            }
            if (Application.platform == RuntimePlatform.Android && Input.GetKeyDown(KeyCode.Home)) // Home键
            {
                //Code
            }
        }
    }
    

    --ToastUtils.cs文件

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    namespace SinglePay
    {
        public class ToastUtils
        {
            public static void showToast(string text)
            {
                SinglePaySDKContext.GetInstance().RunOnUIThread(new AndroidJavaRunnable(() =>
                {
                    AndroidJavaClass Toast = new AndroidJavaClass("android.widget.Toast");
                    Toast.CallStatic<AndroidJavaObject>("makeText", SinglePaySDKContext.GetInstance().GetActivity(), text, Toast.GetStatic<int>("LENGTH_SHORT")).Call("show");
                }));
            }
        }
    }
    

    上面主要的语法知识点:
    1、AndroidJavaClass 这个就是相当于C#到AndroidJava的类的映射
    ---- 调用静态方法Android中类的静态方法 和获取对应的静态字段

    AndroidJavaClass jc = new AndroidJavaClass("完整的路径类名");
    静态方法调用:jc.CallStatic<返回类型>("java对应的方法名",object[] args); //args:参数,类型没有传默认就是void
    静态字段获取:jc.GetStatic<返回类型>("字段名称")). //public类型的字段
    例子
     AndroidJavaClass Toast = new AndroidJavaClass("android.widget.Toast"); Toast.CallStatic<AndroidJavaObject>("makeText", SinglePaySDKContext.GetInstance().GetActivity(), text, Toast.GetStatic<int>("LENGTH_SHORT")).Call("show");
    

    2、AndroidJavaObject 这个就是相当于C#到AndroidJava的对象的映射
    ---- 调用Android中对的方法 和获取对应的字段

    AndroidJavaObject  jo= new AndroidJavaObject ("完整的路径类名",object[] args); //args:构成方法的参数
    方法调用:jo.Call<返回类型>("方法名", object[] args); //args:参数,//类型没有传默认就是void
    成员变量调用:jo.Get<int>("字段名称"));
    例子:
    AndroidJavaObject  singlePaySDK = new AndroidJavaObject("cn.m4399.plugin.SinglePaySDK", singlePaySDKContext.GetActivity(), "70001", "测试游戏",SCREEN_ORIENTATION_PORTRAIT, listenerProxy);
    singlePaySDK.Call("doPay", singlePaySDKContext.GetActivity(), "10", "砖石", "测试数据");
    

    3、AndroidJavaProxy接口的语法调用
    ----unity对应java接口
    java的接口代码:

    public  interface SDKCallbackListener
    {
        void OnSDKInited(String msg);
        void OnAuthSuccess(String token);
        void OnCreatedLive(String url);
        void OnDeletedLive(String id);
    }
    

    Unity中C#的代码:
    Unity C#代码实现Android Java 代码必须要完全一致,但是允许在UnityC#代码中实现多次

    class SDKCallbackListener : AndroidJavaProxy
    {
            // 这句话很重要!!!C#找到Jar中接口的引用
            public SDKCallbackListener() : base("包名.SDKCallbackListener") { }
    
            public void OnSDKInited(string msg)
            {
                ATrace.Log( "OnSDKInited:" + msg);
            }
            public void OnAuthSuccess(string token)
            {
                ATrace.Log("OnAuthSuccess:" + token);
            }
            public void OnCreatedLive(string url)
            {
                ATrace.Log("OnCreatedLive:" + url);
            }
            public void OnDeletedLive(string id)
            {
                ATrace.Log("OnDeletedLive:" + id);
            }
    }
    

    4、AndroidJavaRunnable其实就是对应java的 java.lang.Runnable.
    直接看Unity Api

    总结,Android和Unity的交互就是一方导出插件的形式给一方使用,本文写法是提供了Android导出aar给Unity使用,以上就本文的全部内容,如果有什么错误的地方,欢迎大家指出

    参考文献:

    https://docs.unity3d.com/ScriptReference
    https://www.jianshu.com/p/ceaac83808f2
    https://www.jianshu.com/p/b5e3cfcdf081

    相关文章

      网友评论

          本文标题:Android 与 Unity扯不清的关系

          本文链接:https://www.haomeiwen.com/subject/taxfuctx.html