游戏优化系列三: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:对象存在的最后一帧完成所有帧更新之后,调用此函数(可能应 Object.Destroy 要求或在场景关闭时销毁该对象)。

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

具体日志如下:

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

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

Awake

当前控制脚本实例被装载的时候调用。一般用于初始化整个实例使用。

Start

当前控制脚本第一次执行Update之前调用。

Update

每帧都执行一次。这是最常用的事件函数。

FixedUpdate

每固定帧绘制时执行一次,和update不同的是FixedUpdate是渲染帧执行,如果你的渲染效率低下的时候FixedUpdate调用次数就会跟着下降。FixedUpdate比较适用于物理引擎的计算,因为是跟每帧渲染有关。Update就比较适合做控制。

LateUpdate

在每帧执行完毕调用,他是在所有update结束后才掉,比较适合用于命令脚本的执行。官网上例子是摄像机的跟随,都是在所有update *** 作完才跟进摄像机,不然就有可能出现摄像机已经推进了,但是视角里还未有角色的空帧出现。

Reset

这个是编辑器模式情况下你点击reset按钮(如果有的话)调用的,你可以在这里做调试的初始化工作。

OnApplicationFocus

OnApplicationPause

OnApplicationQuit

应用程序失去焦点,应用程序暂停,应用程序退出时候发送这些消息。

OnBecameInvisible

OnBecameVisible

当脚本宿主(不)被任何摄像机显示时候发送此消息。

OnCollisionEnter

OnCollisionExit

OnCollisionStay

当其他碰撞或者刚体(collider/rigidbody )和参数的碰撞或者刚体(collider/rigidbody )重叠、退出时发送前两个。而当他们保持重叠状态时每帧都会发送一个Stay消息。

OnConnectedToServer

OnDisconnectedFromServer

OnFailedToConnect

OnFailedToConnectToMasterServer

前两个 当客户端成功连接到服务器或者断开服务器时发送此消息。后两个当连接失败时候触发。

OnMasterServerEvent

当Master服务器发送报告时候触发。

OnNetworkInstantiate

当物体被Network.Instantiate时触发。

OnPlayerConnected

OnPlayerDisconnected

在服务端当玩家成功连接/离线时候触发。

OnControllerColliderHit

当控制者和参数ControllerColliderHit碰撞时候触发此消息。官方举例可以用于角色移动一个物体,当角色碰到这个参数物体时候,你可以在这函数里 *** 作移动此物体的动作。

OnParticleCollision

当粒子撞到碰撞体(collider)时触发。

OnDisable

OnEnable

当脚本宿主被启用或者禁用时候触发。

OnDrawGizmos

OnDrawGizmosSelected

编辑器状态时绘制Gizmos和Gizmos被选取时候触发。

注:Gizmos参见我另一篇blog,他是用与做自己的组件时候用的,比如路径点绘制之类的。

OnGUI

绘制GUI时候触发。一般在这个函数里绘制GUI菜单。

OnJointBreak

OnLevelWasLoaded

当新的level(Unity包)读取完毕时候触发。

OnMouseDown

OnMouseDrag

OnMouseEnter

OnMouseExit

OnMouseOver

OnMouseUp

鼠标事件,都是当鼠标和gui或者碰撞体(Collider)交互时候触发。需要说明的是drag其实就是鼠标down后up之前持续每帧都会发送此消息。

OnPostRender

这个函数仅用于宿主为摄像机的脚本。当此摄像机范围内所有渲染都完成时候触发此消息。

OnPreCull

这个函数仅用于宿主为摄像机的脚本。当此摄像机剔除了某个渲染场景时候触发此消息。

OnPreRender

这个函数仅用于宿主为摄像机的脚本。当此摄像机开始渲染某个场景时候触发此消息。

OnRenderImage

当所有渲染完成image的postprocessing effects(只有pro版支持)后触发。

OnRenderObject

这个函数仅用于宿主为摄像机的脚本。当使用Graphics.DrawMeshNow 或者其他函数绘制自己建立的物体渲染完毕时触发。

OnSerializeNetworkView

OnServerInitialized

当 Network.InitializeServer完成时触发。

OnTriggerEnter

OnTriggerExit

OnTriggerStay

当碰撞体(collier)接触触发区域(trigger)时候的一系列消息。

OnWillRenderObject

1、检查一下你的Unity版本是否正确,如果你的Unity版本不正确,可能会导致你的程序运行不正常。

2、检查一下你的场景中所有的游戏对象是否都已经添加了正确的组件,是否正确设置了参数,是否有漏掉的组件。

3、检查一下你的场景中所有的资源是否都已经加载完毕,比如材质、纹理等,是否有漏掉的资源。

4、检查一下你的代码是否有错误,比如变量名写错、语法错误等。

5、检查一下你的硬件是否具备运行Unity的最低配置要求。


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

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

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

发表评论

登录后才能评论

评论列表(0条)

保存