UGUI LateBinding使用注意事项

UGUI LateBinding使用注意事项

本期聚集话题: UGUI LateBinding使用注意事项、UpdateRendererBoundingVolumes函数的作用、避免ToArray产生的堆内存分配...


这是第116篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题,建议阅读时间10分钟,认真读完必有收获。

UWA 问答社区:answer.uwa4d.com
UWA QQ群2:793972859(原群已满员)


资源管理

Q1:我想请教一个关于UGUI和SpriteAtlas产生的AssetBundle依赖关系问题。我做了如下测试,测试环境:Unity 2018.1。

一个 UI Prefab 如下:
请输入图片描述

其中三个Image是这个样子的:
请输入图片描述

3个Image分别引用不同的贴图,但这3个贴图手动放入同一个SpriteAtlas,并去掉Include in build选项。
请输入图片描述

将UI Prefab和SpriteAtlas分别打入两个不同的AssetBundle,这时候发现前者和后者没有依赖关系。
请输入图片描述

然而从AssetBundle的大小很容易看出,图片资源没有被重复打入两个AssetBundle,如下图:
请输入图片描述

当我以AssetBundle的方式试图显示这个UI Prefab时,如果不事先将SpriteAtlas加载完毕,并且写好SpriteAtlasManager.atlasRequested回调,确实会报错。
请输入图片描述

由此推测,这个Prefab其实以某种方式知道自己需要用到哪个图集。那么问题来了,我既然无法通过AssetBundleManifest来获取对图集的依赖关系,我应该怎么优雅地处理这种依赖呢?也就是说,我如何得知我在加载这个UI Prefab之前必须加载哪个或者哪些SpriteAtlas呢?

建议题主先确认下是不是确实需要Late Binding(不勾选Include in build)。一旦启用Late Binding,Unity把Prefab和Atlas的依赖关系解开还是可以理解的,毕竟在以前,依赖的资源必须在Prefab加载或实例化之前准备好才行,但Late Binding是不需要的。所以如果其实不需要Late Binding,那么勾上Include in build即可,只是有可能会遇到一个Bug,导致Atlas冗余,但还是可以绕过去的。
Bug详见:https://answer.uwa4d.com/question/5a822325847802258a06509e

如果本身确实就是需要Late Binding,那么SpriteAtlasManager.atlasRequested这个回调里是可以拿到当前被Request的Atlas的Tag的,所以接下来就可以根据这个Tag来加载对应的Atlas了的。(由于Late Binding机制,此处的加载是不需要同步完成的)

该回答由UWA提供,欢迎大家转至社区进行进一步交流:
https://answer.uwa4d.com/question/5b17aec309749726e4188e10


堆内存

Q2:项目中使用了LineRenderer,我在划线的时候调用SetPositions(Vector3[] positions),路径保存在List里的,请问大家有没有什么办法能避免ToArray的分配?

遍历List,调用LineRenderer.SetPosition(int index, Vector3 position)
感谢胡阿毛@UWA问答社区提供了回答

使用一个池,预估一个数组大小,然后直接数组传递。因为如果做拖尾,点一定不会长时间存在,所以可以节省资源,同时节省list转型开销。那些空点可以归到一个位置,这个不会有太多消耗,是稳定的。
感谢马古斯@UWA问答社区提供了回答

这种事情我干的比较多。C#调用到引擎接口的时候,数据是拷贝过去的,所以想办法把传过去的数据稍微加工一下。
比如你传过去的数组实际大小是10,但是通过非安全代码把长度暂时修改为小于10的值,等API调用完毕后设置回来,有点像内存修改大法修改游戏数据。前提是你需要保证你调用的那个API是引擎导出来的:
通过反编译工具看看这个API的声明,InternalCall表示是引擎导出来的,那么可以用这个hack方法。

// UnityEngine.LineRenderer
[GeneratedByOldBindingsGenerator]
[MethodImpl(MethodImplOptions.InternalCall)]
public extern void SetPositions(Vector3[] positions);

先贴一段代码,对于值类型的数组,都可以这么处理。
例子中可以把Byte数组换成你的Vector3数组是没有任何问题的。

[StructLayout( LayoutKind.Sequential )]
struct ArrayHeader {
    internal IntPtr type;
    internal ArraySize_t length;
}

public unsafe static T HackArraySizeCall<T>( Byte[] array, int size, Func<Byte[], T> func ) {
    if ( array != null && func != null ) {
        if ( size < array.Length ) {
            fixed ( void* p = array ) {
                ArrayHeader* header = ( ( ArrayHeader* )p ) - 1;
                var oriLength = header->length;
                header->length = ( ArraySize_t )size;
                try {
                    return func( array );
                } finally {
                    header->length = oriLength;
                }
            }
        } else {
            return func( array );
        }
    }
    return default( T );
}

可以用例子中的代码稍微修改一下,你自己先拿一个公用的Vector3数组来手动做缓存,替换掉list,自己记录一个有效数据个数,等你需要传入API时,用这个hack函数骗一次内存开销调用。
感谢lujian@UWA问答社区提供了回答

最好能直接用Vector3[] 保存路径,其他都是次优解。
既然连Hack都来了,那么用反射拿List的_items,应该也不算个事儿。不过要注意的是这个_items的长度是capacity。
感谢凯奥斯@UWA问答社区提供了回答

欢迎大家转至社区进行进一步交流:
https://answer.uwa4d.com/question/5b1f437c09749726e4188e5f


渲染

Q3:我遇到一个奇怪的问题。我想实现一个画线的Shader,为了合并DrawCall,准备把线的速度Speed和线的缩放YScale放到法线Normal.x和normal.y中传给Shader中使用,在C#中通过mesh.normal直接指定法线。

然而奇怪的事情发生了,在Shader中取到的noraml.x分量值是正常的,normal.y分量是不正常的。这就导致线的速度有异常;使用切线也是如此,x分量值正常,其余分量都不正常。

难以理解的是,在Scene场景中线的速度是正常的,就是说在Scene中Shader取到的法线是正常的。在Game场景中速度就不对了,说明在Game场景中Shader取到的法线值是异常的。(x分量除外,它在两个场景中值都正确)
请输入图片描述

估计是Dynamic Batching的问题。因为要Batching,所以对于顶点来说,模型空间改变了,所以Normal和Tangent也会跟着改变。
相关问题:https://forum.unity.com/threads/dynamic-batching-seems-to-modify-preset-vertex-normals-and-tangent-is-this-fixable.240882/
建议使用UV3或UV4来保存自定义数据。

感谢凯奥斯@UWA问答社区提供了回答,欢迎大家转至社区进行进一步交流:
https://answer.uwa4d.com/question/5b2235c654646026e9ddd7d1


制作

Q4:如何用Unity实现内部可以移动的载具功能呢?例如魔兽世界的电梯、飞艇等。目前遇到的问题:

1)使用CharacterController,角色不绑定到载具上,然后角色自己模拟重力,但是角色有抖动, 而且载具水平移动的时候没办法带动角色;
2)使用CharacterController, 角色绑定到载具上, 但是CC貌似没有办法做局部位移;
3)不使用CharacterController, 完全用射线重写, 工作量较大;
烦请做过相关类似功能的朋友分享下心得。

猜测设计思路,抛砖引玉:

  • 角色水平坐标=载具水平坐标+角色相对位移;
  • 角色朝向=载具朝向+角色相对朝向;
  • 角色高度值=角色水平坐标对应的载具上表面;

1)修改CC,复用角色移动操作的逻辑,但是操作的结果是角色相对位移和角色相对朝向;
2)角色是否绑定载具应该都可以,但是我倾向于不绑定,更具灵活性;
3)角色高度用射线垂直碰撞载具网格,没有必要用重力。
感谢胡阿毛@UWA问答社区提供了回答

CharacterController移动时会有一个单位系数,载具移动时也会有一个系数,将两者相加,会得到一个最终系数,所以即使站在载具上不动,主角也会跟随载具动,题主可以试试。
感谢咸鱼@UWA问答社区提供了回答

最终方案是使用CC,重点是所有位移操作要放在Fixed Update里面,不然会有抖动,不需要绑定,记录载具每帧相对位移,跳跃等操作的话加惯性就可以了。
感谢题主马云@UWA问答社区提供了分享

欢迎大家转至社区进行进一步交流:
https://answer.uwa4d.com/question/5b20ae8509749726e4188e8c


渲染

Q5:Unity引擎从5.4升级到5.6.6之后,在UWA的性能测评报告中的“高CPU占用函数”列表中,多了UpdateRendererBoundingVolumes这个函数。这个函数是用来做什么的呢?

在UWA来看,这是Unity引擎在渲染前、LateUpdate后对场景中的物体进行包围盒的更新,当场景中的动态物体越多时,其开销越高,其开销如下图所示。

请输入图片描述
请输入图片描述

在UWA优化过的项目中,其开销主要与粒子系统和Skinned Mesh的数量相关,当该值较大时,一般出现在大场景且Active的粒子系统和蒙皮网格较多时,在目前的手游中,多见于MOBA、超大场景MMO和吃鸡类项目。当然,我们也遇到了一些奇怪的问题,比如一些项目在Unity 2017以后,NGUI和粒子系统的结合也造成了该项不正常的开销,但这些属于特殊情况。

以上,就是该函数的主要性能开销点。在我们了解了这些后,对其优化也可以有的放矢了,即尽可能控制场景中Active粒子系统和Skinned Mesh的数量,将1~2屏幕之外的此类GameObject进行Deactive等等。

以上回答由UWA提供,欢迎大家转至社区进行进一步交流:
https://answer.uwa4d.com/question/5b1fa43809749726e4188e64

今天的分享就到这里。当然,生有涯而知无涯。在漫漫的开发周期中,您看到的这些问题也许都只是冰山一角,我们早已在UWA问答网站上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入,也许你的方法恰能解别人的燃眉之急;而他山之“石”,也能攻你之“玉”。

官网:www.uwa4d.com
官方技术博客:blog.uwa4d.com
官方问答社区:answer.uwa4d.com
官方技术QQ群:793972859(原群已满员)