美文网首页
Unity2019与Android混合开发

Unity2019与Android混合开发

作者: Kalac | 来源:发表于2020-05-13 16:57 被阅读0次

    0. 开始前的版本对齐

    Unity版本:Unity2019.3.4f1
    AndroidStudio版本:3.5.3


    1. Unity -- 准备项目

    1. 新建项目
    2. 打开File -> Build Setting


      File -> Build Setting
    3. 切换工程模式

    首先选择Android Platform,然后点击Switch Platform切换工程模式。


    切换工程模式
    1. 导出Android工程
      勾上Export Project,否则下方的Export按钮会是一个Build,点击后Unity会直接导出一个Apk文件,而并不是一个Android Studio项目。
      点击Export后,选择保存位置后会成功输出一个Android Studio项目,此时Unity的操作告一段落。
    导出工程

    2. Android 打开项目

    在使用Android studio 打开项目时,会跳出一个选择SDK的选项,此处我选择使用Android Studio’s SDK。Project’s SDK是Unity提供的,我觉得用此SDK可能对原生开发会有一定的影响。我并没有使用Project's SDK进行验证。

    sdk 选择

    然后在弹出的Gradle 同步提示框中点击OK后项目就开始同步,如果无错误就可以进行开发了

    3. Android 项目结构

    Gradle同步完成后,可以看到以下目录(从Android视图切换为了Project)


    项目列表

    其中launcher为平时Android开发中app主module,推荐在launcher主module中开发新的逻辑。(java目录需要自行创建)。

    unityLibrary为Unity生成的子module。
    在unityLibrary中包含一个UnityPlayerActivity的示例Activity,在不进行修改任何代码的时候默认启动的Activity就是这个UnityPlayerActivity。(可以在AndroidManifest中看到将这个activity配置成了启动Acitivity)

    image.png

    而在unityLibrary module中的lib目录中可以看到有一个unity-classes.jar,一个非常重要的类UnityPlayer就是来自这个jar包。如果之前已经在Unity项目中添加过一些Android插件,在lib目录下也会出现这些其他的lib包

    那么我们来看下UnityPlayerActivity这个类

    // GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN
    package com.unity3d.player;
    
    import android.app.Activity;
    import android.content.Intent;
    import android.content.res.Configuration;
    import android.graphics.PixelFormat;
    import android.os.Bundle;
    import android.view.KeyEvent;
    import android.view.MotionEvent;
    import android.view.View;
    import android.view.Window;
    import android.view.WindowManager;
    import android.os.Process;
    
    public class UnityPlayerActivity extends Activity implements IUnityPlayerLifecycleEvents
    {
        protected UnityPlayer mUnityPlayer; // don't change the name of this variable; referenced from native code
    
        // Override this in your custom UnityPlayerActivity to tweak the command line arguments passed to the Unity Android Player
        // The command line arguments are passed as a string, separated by spaces
        // UnityPlayerActivity calls this from 'onCreate'
        // Supported: -force-gles20, -force-gles30, -force-gles31, -force-gles31aep, -force-gles32, -force-gles, -force-vulkan
        // See https://docs.unity3d.com/Manual/CommandLineArguments.html
        // @param cmdLine the current command line arguments, may be null
        // @return the modified command line string or null
        protected String updateUnityCommandLineArguments(String cmdLine)
        {
            return cmdLine;
        }
    
        // Setup activity layout
        @Override protected void onCreate(Bundle savedInstanceState)
        {
            requestWindowFeature(Window.FEATURE_NO_TITLE);
            super.onCreate(savedInstanceState);
    
            String cmdLine = updateUnityCommandLineArguments(getIntent().getStringExtra("unity"));
            getIntent().putExtra("unity", cmdLine);
    
            mUnityPlayer = new UnityPlayer(this, this);
            setContentView(mUnityPlayer);
            mUnityPlayer.requestFocus();
        }
    
        // When Unity player unloaded move task to background
        @Override public void onUnityPlayerUnloaded() {
            moveTaskToBack(true);
        }
    
        // When Unity player quited kill process
        @Override public void onUnityPlayerQuitted() {
            Process.killProcess(Process.myPid());
        }
    
        @Override protected void onNewIntent(Intent intent)
        {
            // To support deep linking, we need to make sure that the client can get access to
            // the last sent intent. The clients access this through a JNI api that allows them
            // to get the intent set on launch. To update that after launch we have to manually
            // replace the intent with the one caught here.
            setIntent(intent);
            mUnityPlayer.newIntent(intent);
        }
    
        // Quit Unity
        @Override protected void onDestroy ()
        {
            mUnityPlayer.destroy();
            super.onDestroy();
        }
    
        // Pause Unity
        @Override protected void onPause()
        {
            super.onPause();
            mUnityPlayer.pause();
        }
    
        // Resume Unity
        @Override protected void onResume()
        {
            super.onResume();
            mUnityPlayer.resume();
        }
    
        // Low Memory Unity
        @Override public void onLowMemory()
        {
            super.onLowMemory();
            mUnityPlayer.lowMemory();
        }
    
        // Trim Memory Unity
        @Override public void onTrimMemory(int level)
        {
            super.onTrimMemory(level);
            if (level == TRIM_MEMORY_RUNNING_CRITICAL)
            {
                mUnityPlayer.lowMemory();
            }
        }
    
        // This ensures the layout will be correct.
        @Override public void onConfigurationChanged(Configuration newConfig)
        {
            super.onConfigurationChanged(newConfig);
            mUnityPlayer.configurationChanged(newConfig);
        }
    
        // Notify Unity of the focus change.
        @Override public void onWindowFocusChanged(boolean hasFocus)
        {
            super.onWindowFocusChanged(hasFocus);
            mUnityPlayer.windowFocusChanged(hasFocus);
        }
    
        // 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);
        }
    
        // Pass any events not handled by (unfocused) views straight to UnityPlayer
        @Override public boolean onKeyUp(int keyCode, KeyEvent event)     { return mUnityPlayer.injectEvent(event); }
        @Override public boolean onKeyDown(int keyCode, KeyEvent event)   { return mUnityPlayer.injectEvent(event); }
        @Override public boolean onTouchEvent(MotionEvent event)          { return mUnityPlayer.injectEvent(event); }
        /*API12*/ public boolean onGenericMotionEvent(MotionEvent event)  { return mUnityPlayer.injectEvent(event); }
    }
    
    

    其中UnityPlayer mUnityPlayer就是Unity最终绘制内容的View(是一个FrameLayout),而UnityPlayerActivity 将这个View设置为自己的根View,进行显示。所以也可以自定义一个任意大小的布局,将mUnityPlayer当做正常的View 添加到布局中,进行自定义大小的控制。
    UnityPlayerActivity 也重写了onResumeonPause等进行了对mUnityPlayer生命周期的管理。

    4. Android与Unity跳转

    一般情况下,混合开发都是会先启动原生界面,然后通过点击原生的中button根据业务逻辑跳转至包含Unity的Activity。这样我们就不能将UnityPlayerActivity设置为第一个启动的Activity

    1. 取消UnityPlayerActivity默认启动
      AndroidManifest文件中删除或注释掉UnityPlayerActivity配置的下intent-filter
      删除intent-filter
      小伙伴如果之前已经在Unity中导入了其他Android插件,那么这个AndroidManifest中显示的Activity应该是插件中自定义的Activity,而不是UnityPlayerActivity,注释掉相应的代码即可。
    2. 页面跳转
      通过常规的startActivity即可启动UnityPlayerActivity
    findViewById(R.id.btn_button1).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
    
            Intent intent = new Intent(HomeActivity.this, UnityPlayerActivity.class);
                    
            startActivity(intent);
    
        }
    });
    

    但是,当你finish到这个UnityPlayerActivity时你会发现,即使还有Activity显示,应用还是自动关闭了。这个问题是因为在UnityPlayerActivity中的onDestroy方法中调用了mUnityPlayerdestroy方法。

        // Quit Unity
        @Override protected void onDestroy ()
        {
            mUnityPlayer.destroy();
            super.onDestroy();
        }
    
    

    我们点进mUnityPlayer.destroy()看一下

        public void destroy() {
            //...省略无用代码
            if (this.mProcessKillRequested) {
                if (this.m_UnityPlayerLifecycleEvents != null) {
                    this.m_UnityPlayerLifecycleEvents.onUnityPlayerQuitted();
                } else {
                    this.onUnityPlayerQuitted();
                }
    
                Process.killProcess(Process.myPid()); // 结束自己的进程
            }
    
            unloadNative();
        }
    

    发现在mProcessKillRequestedtrue的时候,会进行一个杀自己进程的操作,而我们一般app都是一个进程,就会导致我们的app被kill掉。
    解决办法就是在AndroidManifest配置一下UnityPlayerActivityUnityPlayerActivity以一个新的进程启动。

    android:process=":e.unitry3d"
    

    Android多进程总结一:生成多进程(android:process属性)

    5. Android 自定义Unity显示形式

    由于业务的需求决定,混合开发中的Unity不一定为全屏幕显示或者可能需要多个Unity界面,那么就需要继承UnityPlayerActivity进行自定义一个显示Unity的界面。

    当我们的业务需求决定了我们需要实现一个UnityPlayerActivity的子类进行扩展功能的时候,需要进行以下步骤:

    1. 禁止UnityPlayerActivity中添加mUnityPlayer
      UnityPlayerActivityonCreate中注释setContentViewrequestFocus代码,因为要在子类中按需加载mUnityPlayer,防止多次设置View,就注释掉父类的相关代码。
      // Setup activity layout
      @Override protected void onCreate(Bundle savedInstanceState)
      {
          requestWindowFeature(Window.FEATURE_NO_TITLE);
          super.onCreate(savedInstanceState);
    
          String cmdLine = updateUnityCommandLineArguments(getIntent().getStringExtra("unity"));
          getIntent().putExtra("unity", cmdLine);
    
          mUnityPlayer = new UnityPlayer(this, this);
          //setContentView(mUnityPlayer);
          //mUnityPlayer.requestFocus();
      }
    
    
    1. 实现子类,将mUnityPlayer设置给布局
    public class UnityActivity extends UnityPlayerActivity{
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            setContentView(R.layout.layout_unity);
    
            FrameLayout frameLayout = findViewById(R.id.framelayout);
            frameLayout.addView(mUnityPlayer);
    
            mUnityPlayer.requestFocus();
        }
    
    }
    

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

    这个地方需要注意两点:1.如果之前导入过插件,这里一定要继承自插件中实现的UnityPlayerActivity子类,否则,插件的方法不会被调用。2. 记得要将实现的Activity配置为新的进程。

    如果想启动不同的Unity界面,也不需要实现多个Activity子类,和Unity开发约定下通信规则,确定好发送什么参数启动什么页面,在Activity启动后调用相关的方法,发送约定好的参数即可。

    例如:
    启动界面:

        findViewById(R.id.btn_button1).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
    
                Intent intent = new Intent(HomeActivity.this, UnityActivity.class);
                intent.putExtra("panelName","LunchPanel");
                startActivity(intent);
            }
        });
    

    UnityActivity

        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            setContentView(mUnityPlayer);
            mUnityPlayer.requestFocus();
    
            String panelName = getIntent().getStringExtra("panelName");
    
            UnityPlayer.UnitySendMessage("UIRoot","openPanel",panelName);//unity方法
        }
    

    6. 使用Fragment当做Unity显示的载体

    目前我试出来的方案就是将mUnityPlayerFragment将要挂载的Activity中进行创建并进行生命周期的管理。

    Activity

    public class HomeActivity extends FragmentActivity {
        protected UnityPlayer mUnityPlayer;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.home_activity);
            mUnityPlayer = new UnityPlayer(this, null);
            FragmentManager fragmentManager = getSupportFragmentManager();
            fragmentManager.beginTransaction().add(R.id.fl,new UnityFragment(mUnityPlayer)).commit();
    
    
        }
    
    
        @Override protected void onNewIntent(Intent intent)
        {
            // To support deep linking, we need to make sure that the client can get access to
            // the last sent intent. The clients access this through a JNI api that allows them
            // to get the intent set on launch. To update that after launch we have to manually
            // replace the intent with the one caught here.
            setIntent(intent);
            mUnityPlayer.newIntent(intent);
        }
    
        // Quit Unity
        @Override protected void onDestroy ()
        {
            mUnityPlayer.destroy();
            //mUnityPlayer.unloadNative();
            super.onDestroy();
        }
    
        // Pause Unity
        @Override protected void onPause()
        {
            super.onPause();
            mUnityPlayer.pause();
        }
    
        // Resume Unity
        @Override protected void onResume()
        {
            super.onResume();
            mUnityPlayer.resume();
        }
    
        // Low Memory Unity
        @Override public void onLowMemory()
        {
            super.onLowMemory();
            mUnityPlayer.lowMemory();
        }
    
        // Trim Memory Unity
        @Override public void onTrimMemory(int level)
        {
            super.onTrimMemory(level);
            if (level == TRIM_MEMORY_RUNNING_CRITICAL)
            {
                mUnityPlayer.lowMemory();
            }
        }
    
        // This ensures the layout will be correct.
        @Override public void onConfigurationChanged(Configuration newConfig)
        {
            super.onConfigurationChanged(newConfig);
            mUnityPlayer.configurationChanged(newConfig);
        }
    
        // Notify Unity of the focus change.
        @Override public void onWindowFocusChanged(boolean hasFocus)
        {
            super.onWindowFocusChanged(hasFocus);
            mUnityPlayer.windowFocusChanged(hasFocus);
        }
    
        // 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);
        }
    
        // Pass any events not handled by (unfocused) views straight to UnityPlayer
        @Override public boolean onKeyUp(int keyCode, KeyEvent event)     { return mUnityPlayer.injectEvent(event); }
        @Override public boolean onKeyDown(int keyCode, KeyEvent event)   { return mUnityPlayer.injectEvent(event); }
        @Override public boolean onTouchEvent(MotionEvent event)          { return mUnityPlayer.injectEvent(event); }
        /*API12*/ public boolean onGenericMotionEvent(MotionEvent event)  { return mUnityPlayer.injectEvent(event); }
    
    }
    

    fragment

    public class UnityFragment extends Fragment{
    
        private UnityPlayer mUnityPlayer;
    
    
        public UnityFragment(UnityPlayer unityPlayer) {
            mUnityPlayer = unityPlayer;
        }
    
    
        @Nullable
        @Override
        public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    
            return mUnityPlayer;
        }
    
    }
    

    7. Unity与Android之间的通讯

    此内容网络上已有较多文章,本文不再叙述。

    8. 注意事项

    1. 当Unity与Android同时开发时,每次从Unity导出新的项目覆盖之前的老代码的时候主launcher中的AndroidManifest文件会被重置,导出前务必要备份。

    文章可能因为个人能力原因出现错误,忘谅解。希望能够指出。

    相关文章

      网友评论

          本文标题:Unity2019与Android混合开发

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