0. 开始前的版本对齐
Unity版本:Unity2019.3.4f1
AndroidStudio版本:3.5.3
1. Unity -- 准备项目
- 新建项目
-
打开File -> Build Setting
- 切换工程模式
首先选择Android Platform,然后点击Switch Platform切换工程模式。
- 导出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进行验证。
然后在弹出的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)
而在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
也重写了onResume
、onPause
等进行了对mUnityPlayer
生命周期的管理。
4. Android与Unity跳转
一些情况下,混合开发都是会先启动原生界面,然后通过点击原生的中button
根据业务逻辑跳转至包含Unity的Activity
。这样我们就不能将UnityPlayerActivity
设置为第一个启动的Activity
。
- 取消
UnityPlayerActivity
默认启动
在AndroidManifest
文件中删除或注释掉UnityPlayerActivity
配置的下intent-filter
小伙伴如果之前已经在Unity中导入了其他Android插件,那么这个AndroidManifest
中显示的Activity
应该是插件中自定义的Activity
,而不是UnityPlayerActivity
,注释掉相应的代码即可。 - 页面跳转
通过常规的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
方法中调用了mUnityPlayer
中destroy
方法。
// 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();
}
发现在mProcessKillRequested
为true
的时候,会进行一个杀自己进程的操作,而我们一般app都是一个进程,就会导致我们的app被kill掉。
解决办法就是在AndroidManifest
配置一下UnityPlayerActivity
,UnityPlayerActivity
以一个新的进程启动。
android:process=":e.unitry3d"
Android多进程总结一:生成多进程(android:process属性)
5. Android 自定义Unity显示形式
由于业务的需求决定,混合开发中的Unity
不一定为全屏幕显示或者可能需要多个Unity界面,那么就需要继承UnityPlayerActivity
进行自定义一个显示Unity的界面。
当我们的业务需求决定了我们需要实现一个UnityPlayerActivity
的子类进行扩展功能的时候,需要进行以下步骤:
- 禁止
UnityPlayerActivity
中添加mUnityPlayer
在UnityPlayerActivity
的onCreate
中注释setContentView
和requestFocus
代码,因为要在子类中按需加载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();
}
- 实现子类,将
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显示的载体
一种方案就是将mUnityPlayer
在Fragment
将要挂载的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. 注意事项
- 当Unity与Android同时开发时,每次从Unity导出新的项目覆盖之前的老代码的时候主launcher中的AndroidManifest文件会被重置,导出前务必要备份。
文章可能因为个人能力原因出现错误,忘谅解。希望能够指出。