草庐IT

Unity3D工程作为库内嵌到安卓原生开发指南

墨磨人 2023-08-28 原文

前言

本案例使用Unity 2020.3.39f1c1与Android Studio 2021.3.1
前提条件
1.你的Unity已经安装好Android平台模块,可以在UnityHub中查看。
2.Android Studio IDE已经安装好了,测试工程项目能正常运行。(如果未安装Android Studio 可以查看我的另外一篇文章《Android Studio IDE安装指南》)

一、创建Unity项目并导出

首先创建一个Unity空项目,在空场景中创建一个空物体名字为UnityGameDataMgr

注意:这个名字是等会再Android中向Unity发送消息时的API中必须要传的参数

然后在UnityGameDataMgr空物体上添加一个脚本,脚本中的代码如下:

using UnityEngine;
using System;
using UnityEngine.UI;

public class GameDataMgr : MonoBehaviour
{
    public Button Btn_UnityExit;
    public Button Btn_ShowMainActivityQuit;
    public Button Btn_ShowMainActivityUnload;
    public Text Txt_FormAndroidMessage;
    string userData = "用户信息(UseData):张三,22,男";

    private void Awake()
    {
        DontDestroyOnLoad(this);
    }
    
    private void Start()
    {
        Btn_UnityExit.onClick.AddListener(OnQuitUnity);
        // 点击ShowMainActivityUnload按钮调用
        Btn_ShowMainActivityUnload.onClick.AddListener(() =>
        {
            CallAndroidMethod("showMainActivity", false, ("UnloadSend:" + userData));
        });
        // 点击ShowMainActivityQuit按钮调用
        Btn_ShowMainActivityQuit.onClick.AddListener(() =>
        {
            CallAndroidMethod("showMainActivity", true, ("QuitSend:" + userData));
        });
    }

    /// <summary>
    /// 调用UnityGameActivity的方法
    /// </summary>
    /// <param name="methodName">Android方法名称</param>
    /// <param name="isFinish">方法参数1:是否结束UnityGameActivity</param>
    /// <param name="data">方法参数2:具体数据</param>
    private void CallAndroidMethod(string methodName, bool isFinish, string data)
    {
#if UNITY_ANDROID
        try
        {
            AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
            AndroidJavaObject jo = jc.GetStatic<AndroidJavaObject>("currentActivity");
            jo.Call(methodName, isFinish, data);
        }
        catch (Exception e)
        {
            Debug.LogError(e.Message);
        }
#endif
    }

    /// <summary>
    /// Android调用方法 在安卓UnityGameActivity OnCreate调用
    /// </summary>
    /// <param name="str"></param>
    public void sendMessageToUnity(string str)
    {
        Debug.Log("Unity获取Android MainActivity发送过来的Token信息: " + str);
        Txt_FormAndroidMessage.text = "来自Android MainActivity信息:" + str;
    }

    #region Unity程序原生方法
    void OnQuitUnity()
    {
        Application.Quit();
    }
    #endregion
}

Unity UGUI Canvas布局层级结构如下:

然后在File->Build Settings中将平台切换到Android

按照上图的指示操作然后在Player Settings中配置Other Settings其中的选项
1.设置Package Name。这个必须要与Android中的包名一致(不一致好像也可以)。
2.设置Minimum API Level 。这个一定要与Android Studio中保持一致,如果不一致,在Android Studio中发布时会报错。
3.设置Scripting BackedIL2CPP,设置Api Compatibility Level 为**.NET Standard 2.0**。
4.4.设置Target Architectures。选择 ARMv7ARM64(可选)

4.设置签名(非必要)。我这里没有设置,但是之前设置了也能正常运行,这里跟签名应该没有太大关系,我的都没有设置,看下图(如果使用IL2CPP打包APK出错可能就需要设置)

5.最后就是点击Build Settings中的Export导出Android中需要的项目,导出的文件夹与Unity项目的关系,如果你是第一次看着我的博客教程跟着做,那么请按照我的文件夹的命名与结构来做,因为在Android Studio中需要根据路径引入Unity发布出来的安卓项目。

Unity项目目录与安卓项目目录之间的关系

至此,Unity中设置已经完成。

二、创建Android项目并引入

创建安卓项目,选择Empty Activity->Next

注意:Minimum SDK版本与Unity两者保持一致

注意:创建出来的Android项目下方没有任何报错才可以,像下图一样

创建完成后,开始导入Unity Build出来的包

三、导入Unity项目(模块)

这里有两种做法,

第一种,直接打开刚刚用Unity导出的项目文件夹做为一个Android项目;
第二种,把导出的项目里面的unityLibrary文件夹做为一个模块来用;

这里选择第二种方式导入
1.导入unityLibrary模块
File -> New -> Import Module


不出意外这个时候应该会报错

导入之后会出现一系列的错误提示,不过不要慌,问题不大,下面开始进行配置。

2.unityLibrary模块配置
2.1.项目settings.gradle文件配置
在项目settings.gradle文件里添加两行代码

include ':app',':unityLibrary'
rootProject.name = "AndroidAPP"
//--上面两句应该本身就有不需要添加--

//括号里的是你的unityLibrary所在的路径
project(':unityLibrary').projectDir=new File('..\\UnityAndroidBuild\\unityLibrary')


在项目settings.gradle文件里添加

flatDir {
	dirs "${project(':unityLibrary').projectDir}/libs"
}

项目settings.gradle文件最终配置就是上面这样的。

2.2.app模块文件配置
在app模块下的build.gradle文件里面加上

implementation project(':unityLibrary')
implementation fileTree(dir: project(':unityLibrary').getProjectDir().toString() + ('\\libs'), include: ['*.jar'])


同时还是在app模块下的build.gradle文件里面添加引入SO库架构,如下代码

注意:添加引入SO库架构,如果不添加,构建出来的app运行时会闪退,并且报错:can not find ‘libmain.so’

ndk {
      // 设置支持的SO库架构,第三方给的so库哪几种架构,就配置这几种架构
      abiFilters 'armeabi', 'armeabi', 'x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a'
    }


app模块下的strings.xml文件里加上

<string name="game_view_content_description">Game view</string>


点击蓝色字体 Sync Now按钮,结果

如果出现上面这个错误在上图序号①所指 项目gradle.properties文件加入一下代码(没有报错的不用加)

unityStreamingAssets=.unity3d, google-services-desktop.json, google-services.json, GoogleService-Info.plist


继续点击 Sync Now 按钮,编译成功,无报错!

4.简单交互Demo创建与运行
首先更改app模块MainActivity.java类

MainActivity类完整代码如下:

package com.test.androidapp;//自己的APP包名
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity  {

    private Button btn;
    private TextView textView;
    private String tokenStr="这是一个tokenStr数据";
    private String unityDataStr="";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);
        btn = findViewById(R.id.button);
        textView=findViewById(R.id.textView);
        //注册监听器
        btn.setOnClickListener(this::onClickStartGameBtn);
        handleIntent(getIntent());
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);

        handleIntent(intent);
        setIntent(intent);
    }

    int i=0;
    //按键点击进入UNITY
    private void onClickStartGameBtn(View view) {
        System.out.println("触发点击进入游戏的按钮事件");
        Intent intent=new Intent(this, UnityGameActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
        i=i+1;//模拟点击进入Unity次数 每次点击+1
        intent.putExtra("TokenCode", tokenStr+"{"+i+"}");
        //startActivity(intent);
        startActivityForResult(intent, 1);//requestCode >=0即可
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == 1) {
            Bundle extras = data.getExtras();
            if (extras!=null){
                unityDataStr=extras.getString("UnityData");
            }
            textView.setText(unityDataStr);
            System.out.println(unityDataStr);
        }
    }

    //当UnityActivityGame结束后返回
    //这个是处理来自UnityGameActivity传过来的信息
   private void handleIntent(Intent intent) {
       if(intent == null || intent.getExtras() == null)  return ;

        if(intent.getExtras().containsKey("UnityData")) {

            unityDataStr = intent.getStringExtra("UnityData");
            textView.setText(unityDataStr);
        }
    }
}

在activity_main.xml里面新建一个Button按钮和TextView组件

MainActivity类中的R.id.buttonactivity_main.xml中的Button控件id属性的值,R.id.textView同理。

app模块新建UnityGameActivity类作为进入Unity的入口

UnityGameActivity类完整代码如下:

package com.test.androidapp;

import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import com.unity3d.player.UnityPlayer;
import com.unity3d.player.UnityPlayerActivity;

//进入到这个类就进入了unity游戏画面
public class UnityGameActivity extends UnityPlayerActivity {

    private String tokenStr;
    private static final String TAG = "UnityGameActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    protected void onStart() {
        super.onStart();
        GetTokenFromMainActivity();
    }

    //进入UnityGameActivity就调用sendMessageToUnity给Unity Text组件赋值
    private void  GetTokenFromMainActivity(){
        Bundle bundle=getIntent().getExtras();
        if (bundle!=null)
            tokenStr= bundle.getString("TokenCode");
        sendMessageToUnity(tokenStr);
    }

    //发送给Unity的数据 Android-》Unity
    private void sendMessageToUnity(String str) {

        Log.i(TAG, "Android传给unity的信息: " + str);
        //其中GameDataMgr是unity生成的gameobject,脚本要挂在上面。AndroidToUnity是unity里实现的方法。str是传过去的值。
        UnityPlayer.UnitySendMessage("UnityGameDataMgr", "sendMessageToUnity", str);
    }

    //返回MainActivity并将unityData数据传递给MainActivity显示( Unity-》Android)
    //unity 退出应用之前将Unity的数据传递给MainActivity
    //isFinish为false 直接返回MainActivity;当为true,结束此活动返回MainActivity
    public void showMainActivity(boolean isFinish,String unityDataStr) {

        if (!isFinish){
            Intent intent=new Intent(this,MainActivity.class);
            intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP);
            intent.putExtra("UnityData",unityDataStr);
            startActivity(intent);
        }
        else{
            Bundle bundle=new Bundle();
            bundle.putString("UnityData",unityDataStr);
            Intent intent=new Intent();
            intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP);
            setResult(1,intent);
            intent.putExtras(bundle);
            finish();
        }
    }

}

还需要配置,才能找到这个类,在app模块AndroidManifest.xml文件添加这段代码

 <activity android:name="com.test.androidapp.UnityGameActivity"
            android:theme="@style/UnityThemeSelector"
            android:screenOrientation="userPortrait"
            android:launchMode="singleTask"
            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale|layoutDirection|density"
            android:hardwareAccelerated="false">
            <meta-data android:name="unityplayer.UnityActivity" android:value="true" />
            <meta-data android:name="android.notch_support" android:value="true" />
 </activity>

然后运行到自己的手机上就行了(不会运行的可以找找看android基础教程学习下)
如果是游戏模拟器运行的话,先打开模拟器,再点击下图标识①按钮同步下gradle文件,再点击②运行按钮,就可以在模拟器上运行了。

运行结果如下:
演示步骤:
点击 进入UNITY->跳转到Unity界面同时传递数据显示在Unity中;
点击Unity中的 ShowMainActivity(Quit)或者 ShowMainActivity(Unload)按钮返回APP首页并同时将Unity数据传递给TextView组件;


其他问题:
1.NDK路径配置问题

项目的Local.proporties文件下添加

我这里是用的Unity3D编辑器下载的NDK路径

ndk.dir=自己ndk路径


2.构建出包出现两个APP图标
找到unityLibrary模块的AndroidManifest.xml文件,删除里面的代码

3.从Unity返回其他APP页面闪退应用的问题
操作:点击Exit按钮( Application.Quit();)
app模块AndroidManifest.xml文件添加如下代码

android:process=":UnityActivity"

项目工程仓库地址

参考文章:
1.(Unity官方)Unity作为库的方式嵌入原生Android/IOS/tvOS案例
2.Unity2020.2.6导出项目到Android Studio4.1.2中
3.Android内嵌Unity并实现互相跳转的实例代码
4.Unity游戏嵌入Android应用(融合为一个应用)

十分感谢上述博客作者的帮助!~

有关Unity3D工程作为库内嵌到安卓原生开发指南的更多相关文章

  1. ruby - RSpec - 使用测试替身作为 block 参数 - 2

    我有一些Ruby代码,如下所示:Something.createdo|x|x.foo=barend我想编写一个测试,它使用double代替block参数x,这样我就可以调用:x_double.should_receive(:foo).with("whatever").这可能吗? 最佳答案 specify'something'dox=doublex.should_receive(:foo=).with("whatever")Something.should_receive(:create).and_yield(x)#callthere

  2. ruby - 字符串文字中的转义状态作为 `String#tr` 的参数 - 2

    对于作为String#tr参数的单引号字符串文字中反斜杠的转义状态,我觉得有些神秘。你能解释一下下面三个例子之间的对比吗?我特别不明白第二个。为了避免复杂化,我在这里使用了'd',在双引号中转义时不会改变含义("\d"="d")。'\\'.tr('\\','x')#=>"x"'\\'.tr('\\d','x')#=>"\\"'\\'.tr('\\\d','x')#=>"x" 最佳答案 在tr中转义tr的第一个参数非常类似于正则表达式中的括号字符分组。您可以在表达式的开头使用^来否定匹配(替换任何不匹配的内容)并使用例如a-f来匹配一

  3. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  4. Unity 热更新技术 | (三) Lua语言基本介绍及下载安装 - 2

    ?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------

  5. FOHEART H1数据手套驱动Optitrack光学动捕双手运动(Unity3D) - 2

    本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01  客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02  数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit

  6. unity---接入Admob - 2

    目录1.AdmobSDK下载地址2.将下载好的unityPackagesdk导入到unity里​编辑 3.解析依赖到项目中

  7. Unity 3D 制作开关门动画,旋转门制作,推拉门制作,门把手动画制作 - 2

    Unity自动旋转动画1.开门需要门把手先动,门再动2.关门需要门先动,门把手再动3.中途播放过程中不可以再次进行操作觉得太复杂?查看我的文章开关门简易进阶版效果:如果这个门可以直接打开的话,就不需要放置"门把手"如果门把手还有钥匙需要旋转,那就可以把钥匙放在门把手的"门把手",理论上是可以无限套娃的可调整参数有:角度,反向,轴向,速度运行时点击Test进行测试自己写的代码比较垃圾,命名与结构比较拉,高手轻点喷,新手有类似的需求可以拿去做参考上代码usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;u

  8. [Vuforia]二.3D物体识别 - 2

    之前说过10之后的版本没有3dScan了,所以还是9.8的版本或者之前更早的版本。 3d物体扫描需要先下载扫描的APK进行扫面。首先要在手机上装一个扫描程序,扫描现实中的三维物体,然后上传高通官网,在下载成UnityPackage类型让Unity能够使用这个扫描程序可以从高通官网上进行下载,是一个安卓程序。点到Tools往下滑,找到VuforiaObjectScanner下载后解压数据线连接手机,将apk文件拷入手机安装然后刚才解压文件中的Media文件夹打开,两个PDF图打印第一张A4-ObjectScanningTarget.pdf,主要是用来辅助扫描的。好了,接下来就是扫描三维物体。将瓶

  9. ruby-on-rails - 应用程序的名称是否可以作为变量使用? - 2

    当我创建一个Rails应用程序时,控制台:railsnewfoo我的代码可以使用字符串“foo”吗?puts"Yourapp'snameis"+app_name_bar 最佳答案 Rails.application.class将为您提供应用程序的全名(例如YourAppName::Application)。从那里您可以使用Rails.application.class.parent获取模块名称。 关于ruby-on-rails-应用程序的名称是否可以作为变量使用?,我们在StackOve

  10. ruby-on-rails - 使用作为方法的值在 ruby​​ 中搜索哈希 - 2

    我在搜索我的值是方法的散列时遇到问题。我只是不想运行plan_type与键匹配的方法。defmethod(plan_type,plan,user){foo:plan_is_foo(plan,user),bar:plan_is_bar(plan,user),waa:plan_is_waa(plan,user),har:plan_is_har(user)}[plan_type]end目前如果我传入“bar”作为plan_type,所有方法都会运行,我怎么能只运行plan_is_bar方法呢? 最佳答案 这个变体怎么样?defmethod

随机推荐