游戏优化系列三:Unity游戏的黑屏问题解决方法

游戏优化系列三:Unity游戏的黑屏问题解决方法,第1张

大家好,我叫Jack冯;
本人20年硕士毕业于广东工业大学,于2020年6月加入37手游安卓团队;目前主要负责海外游戏发行安卓相关开发。

一、背景
二、分析及解决
1、生命周期分析
(1)黑屏情况
(2)解决方法
(3)正常显示
2、涉及方法解析
(1)onWindowFocusChanged (boolean hasFocus)
(2)Android生命周期
(3)对比Android原生工程
(4)unity脚本生命周期
(5)分析脚本生命周期
三、结论

在Unity游戏工程中,经常遇到这样的问题:打开登录d框时,点击Home键先处理其他事宜再返回,发现屏幕黑屏;或者打开了其他接受输入焦点的对话框或d出窗口,点击返回键时发生屏幕黑屏,需要触摸屏幕(获得焦点)才能正常显示。

具体情形见下图:

其中,生命周期顺序如下:

- 打开页面:onCreate--onStart--onResume--onWindowFocusChanged:true

- 点击登录:--onWindowFocusChanged:false

- 点击Home返回:--onPause

- 重新进入:--onRestart--onStart--onNewIntent--onResume--onWindowFocusChanged:false(此时app页面出现黑屏)

在游戏主活动UnityPlayerActivity中,重写onStart()方法,添加获取焦点的方法,可避免黑屏。

其中,生命周期顺序如下:

- 打开页面:onCreate--onStart--onWindowFocusChanged:true--onResume--onWindowFocusChanged:true

- 点击登录:--onWindowFocusChanged:false

- 点击Home返回:--onPause

- 重新进入:--onRestart--onStart--onWindowFocusChanged:ture--onNewIntent--onResume(此时app页面正常显示)

由上可见,二者生命周期的异同在于,是否在调用onStart后调用一次onWindowFocusChanged:true,来获取当前窗口的焦点,实现正常交互。

当activity的当前窗口获得或失去焦点时调用,hasFocus == true表示当前窗口获得焦点,false则表示失去焦点。用法:

- eg:打开页面,当前activity处于活动栈最上层的活动,获得焦点--onWindowFocusChanged:ture;

- 点击登录,d框覆盖在原activity的上层,原activity失去焦点 --onWindowFocusChanged:false;(不仅限d框,还可以是其他获取焦点的页面)

- 此后点击Home键、再返回app,原activity仍然是失去焦点的状态(如果没有手动重新获取焦点),当前页面显示黑屏。

- onCreate (Bundle savedInstanceState):活动创建时调用一次,用于初始化当前活动数据和绑定页面的组件等。参数Bundle:如果活动在关闭后重新初始化,此参数则包含其最近一次调用 onSaveInstanceState(Bundle)存储的数据。

- onStart ():在活动创建方法onCreate(Bundle)或重新启动方法onRestart()之后调用,开始绘制视图、动画等,呈现给用户,其后一般调用onResume()。(可视化状态)

- onResume ():在onRestoreInstanceState()、onRestart()或onPause()之后调用,当前活动位于活动栈的顶部,即将开始与用户进行交互、准备好接收输入事件。(还不能响应输入事件)

- onPause ():活动仍在屏幕上可见,但用户不再与其交互时进行调用,eg:d框等页面覆盖了当前活动时。

- onStop ():当活动在屏幕上不可见时调用,eg:点击home键返回桌面

- onRestart ():在 onStop ()方法后,重新打开原activity时调用,其后一般调用onStart ()和onResume ()

- onDestroy ():在销毁活动之前执行任何最后的清理时调用。一般是活动即将结束(调用 finish()),或系统暂时销毁了此活动实例以节省空间

图为原生工程的AndroidDemo。对比UnityDemo,生命周期方法执行虽一致、焦点丢失情况则不相同。

为了进一步对比,下面引入unity脚本的常见生命周期方法。

unity脚本的常见生命周期方法如下:

-- Awake:始终在任何 Start 函数之前并在实例化组件之后调用此函数。(如果游戏对象在启动期间处于非活动状态,则在激活之后才会调用 Awake。)
-- OnEnable:(仅在对象处于激活状态时调用)在启用对象后立即调用此函数。在创建 MonoBehaviour 实例时(例如加载关卡或实例化具有脚本组件的游戏对象时)会执行此调用。
-- OnLevelWasLoaded:场景全部加载完成后
-- Start:仅当启用脚本实例后,才会在第一次帧更新之前调用 Start。

-- FixedUpdate:调用 FixedUpdate 的频度常常超过 Update。如果帧率很低,可以每帧调用该函数多次;如果帧率很高,可能在帧之间完全不调用该函数。
-- Update:每帧调用一次 Update。这是用于帧更新的主要函数。
-- LateUpdate:每帧调用一次 LateUpdate__(在 Update__ 完成后)。
-- OnGUI:每帧调用多次以响应 GUI 事件。首先处理布局和重新绘制事件,然后为每个输入事件处理布局和键盘/鼠标事件。

-- OnApplicationPause:一帧最后时调用,调用后会再触发一帧以刷新图像和切换暂停状态
-- OnApplicationQuit:在退出应用程序之前在所有游戏对象上调用此函数。在编辑器中,用户停止播放模式时,调用函数。

-- OnDisable:行为被禁用或处于非活动状态时,调用此函数。
-- OnDestroy:对象存在的最后一帧完成所有帧更新之后,调用此函数(可能应 ObjectDestroy 要求或在场景关闭时销毁该对象)。

这里将生命周期方法在UnityDemo中打印出来,主要对比黑屏情况下的生命周期情况。

具体日志如下:

综合分析,从桌面返回游戏App时,由于unity丢失焦点(I/Unity: UnityPlayerActivity OnApplicationFocus:False ),脚本没有执行,即无法渲染游戏画面对象,致使黑屏。

如果根据第二点添加获取焦点方法后,由下图可以看到继续执行的unity脚本生命周期方法,先获取到焦点、中止pause状态并绘制页面进行正常显示。即工程重新获取焦点后才会绘制图像。

解决的方法是
var button1Clicked:boolean = false;
function OnGUI() {
if(InputGetKeyDown(KeyCodeEsccape))
button1Clicked = true;
if (button1Clicked)
{
// button1Clicked =false;
if(GUIButton(Rect(0,0,200,100),"退出")
ApplicationQuit()
}
}
理由是gui 只能同时对一个按扭的事件作出反应

unity3d获取按键ascii结果有:Backspace退格键、DeleteDelete键、TabTabTab键、Keypad0小键盘0、UpArrow方向键上、DownArrow方向键下、RightArrow方向键右、LeftArrow方向键左。unity3d是由UnityTechnologies公司开发的一个让玩家轻松创建诸如三维视频游戏、建筑可视化、实时三维动画等类型互动内容的多平台的综合型游戏开发工具。

1将Unity3D安装目录下的Unity\Editor\Data\PlaybackEngines\androidplayer\release\bin\classesjar
拷贝到Android工程的libs文件夹里:
2
右键单击classesjar选择Build Path里面的Add to Build Path:
3
在MainActivity中继承UnityPlayerActivity。删除多余的方法,仅保留onCreate方法中的superonCreate语句,如下:
public class MainActivity extends UnityPlayerActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
superonCreate(savedInstanceState);
}
}
4
添加要在unity中调用的方法,这里添加一个带一个参数的方法StartActivity来调用Android的Activity从而运行Android的代码。还添加了一个有返回值的方法GetInt,用来测试在Unity中获取android中方法的返回值。(貌似只能有一个UnityPlayerActivity,而且这个Activity不能是调用第三方SDK方法的Activity。UnityPlayerActivity只能作为Unity和Android交互的一个接口,在这里面再去调用其他的Activity)
5
package comexampleunitydemo;

import androidcontentContext;
import androidcontentIntent;
import androidosBundle;

import comunity3dplayerUnityPlayerActivity;

public class MainActivity extends UnityPlayerActivity {
private Context mContext = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
superonCreate(savedInstanceState);
mContext = this;
}

public void StartActivity(String name) {
Intent intent = new Intent(mContext, WelcomeActivityclass);
intentputExtra("name", name);
thisstartActivity(intent);
}

public int GetInt()
{
return 1;
}
}
6
然后Clean一下当前工程,避免一些不必要的问题。
然后在Build Project
右键单击工程,选择Export
选择JAR file
右边全部勾选,输入JAR的路径和名字,然后单击finish:
在Unity项目中创建一个Plugins,如下的目录结构
其中bin文件夹放我们刚刚导出的JAR包,libs文件夹放需要用的第三方库文件,res文件夹和AndroidManifest直接从Android工程中拷贝过来。
(除了res文件夹不能动,其他的最好都不要放在单独的文件夹里面,貌似除了jar能加载出来,其他格式的文件如果放在其他目录下会加载不出来)
然后在Unity里面创建一个脚本文件,绑定在MainCamera上(当然也可以绑定在其他的对象上),如下:
在脚本文件中写如下代码:
using UnityEngine;
using SystemCollections;
public class Demo : MonoBehaviour {
void Start () {
}
void Update()
{
//当用户按下手机的返回键或home键退出游戏
if (InputGetKeyDown(KeyCodeEscape) || InputGetKeyDown(KeyCodeHome) )
{
ApplicationQuit();
}
}
void OnGUI()
{
if (GUIButton(new Rect(Screenwidth 01f, Screenheight 01f, Screenwidth 08f, Screenheight 01f), "StartActivity"))
{
AndroidJavaClass jc = new AndroidJavaClass("comunity3dplayerUnityPlayer");
AndroidJavaObject jo = jcGetStatic<AndroidJavaObject>("currentActivity");
joCall("StartActivity", "第一个Activity");
}
if (GUIButton(new Rect(Screenwidth 01f, Screenheight 02f, Screenwidth 08f, Screenheight 01f), "GetInt"))
{
AndroidJavaClass jc = new AndroidJavaClass("comunity3dplayerUnityPlayer");
AndroidJavaObject jo = jcGetStatic<AndroidJavaObject>("currentActivity");
DebugLog(joCall<int>("GetInt"));
}
}
}
然后生成APK文件,记得先把包名改成与Android工程一样的包名。
要测试,只能在手机上测。
最终效果图:
17
单击StartActivity,启动Android的一个Activity,单击GetInt,Console显示1

using UnityEngine;
using SystemCollections;
public class example : MonoBehaviour {
void OnGUI() {
Event e = Eventcurrent;
if (eisKey)
DebugLog("Detected key code: " + ekeyCode);

}
}

可以用以下两种方法实现:
1、通过代码设置:在InputField组件上添加对应的代码,在用户按下回车键时,将焦点跳转到下一个InputField或其他控件上。
2、通过Unity编辑器设置:在InputField组件的导航属性中,设置“自动跳转”选项为“True”,并在下一个InputField或其他控件上设置“自动选择”选项为“True”。


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/yw/13396886.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-07-28
下一篇 2023-07-28

发表评论

登录后才能评论

评论列表(0条)

保存