1)AssetBundle中加载SpriteAtlas图集之后卸载异样
2)Shader相干问题
3)如何监听GameObject的localScale扭转
4)我的项目中大量的字节文件的合并和热更新计划
5)一个对于相机的几何数学问题


这是第232篇UWA技术常识分享的推送。明天咱们持续为大家精选了若干和开发、优化相干的问题,倡议浏览工夫10分钟,认真读完必有播种。

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

Texture

Q:我从AssetBundle包中加载图集和音频,而后在卸载的时候应用Resources.UnloadAsset,发现音频能够卸载,然而SpriteAtlas无奈卸载。

代码:

using System.Collections;using System.Collections.Generic;using UnityEngine;using UnityEngine.U2D;using UnityEngine.SceneManagement;public class Test_ResourceUnload : MonoBehaviour{    public AudioClip[] clips;    public SpriteAtlas[] atlas;    private void Update()    {        if (Input.GetKeyDown(KeyCode.A)) StartCoroutine(LoadAB());        if (Input.GetKeyDown(KeyCode.Space))        {            //SceneManager.LoadScene("222"); 加载场景主动卸载            for (int i = 0; i < atlas.Length; i++)            {                Resources.UnloadAsset(atlas[i]); //不能卸载            }            for (int i = 0; i < clips.Length; i++)            {                Resources.UnloadAsset(clips[i]); //能够卸载            }            //上面的能够卸载            //for (int i = 0; i < clips.Length; i++)            //    clips[i] = null;            //for (int i = 0; i < charAtlas.Length; i++)            //    charAtlas[i] = null;            //Resources.UnloadUnusedAssets();        }    }    private IEnumerator LoadAB()    {        atlas = new SpriteAtlas[5];        for (int i = 1; i < 6; i++)        {            string ABPath = Application.streamingAssetsPath + "/chars/" + i.ToString();            var ABRequest = AssetBundle.LoadFromFileAsync(ABPath);            yield return ABRequest;            AssetBundle charAB = ABRequest.assetBundle;            if (charAB != null)            {                atlas[i - 1] = charAB.LoadAllAssets<SpriteAtlas>()[0];                charAB.Unload(false);            }            else                Debug.LogError("加载关卡charAB谬误 null");        }        string ABPathAudios = Application.streamingAssetsPath + "/audiodubbing/1";        var ABRequestAudios = AssetBundle.LoadFromFileAsync(ABPathAudios);        yield return ABRequestAudios;        AssetBundle charABAudios = ABRequestAudios.assetBundle;        if (charABAudios != null)        {            clips = charABAudios.LoadAllAssets<AudioClip>();            charABAudios.Unload(false);        }        else            Debug.LogError("加载关卡charAB谬误 null");    }} 

在Proflier中查看(打包后电脑测试,非Editor),按下A加载如下:

按下空格卸载如下:

前后比照发现AudioClips曾经卸载了,然而图集却没有卸载。我的项目是简略的测试项目并没有在别处应用加载资源。

测试Unity版本2019.4.9。

A1:Resources.UnloadAsset在Unity的文档中有这样一句话:“This function can only be called on Assets that are stored on disk.”

所以SpriteAtlas是无奈应用这个接口卸载的,而Texture是能够的。卸载SpriteAtlas能够将图集独自打AssetBundle,应用AssetBundle.Unload(true)来卸载,或者清空援用后由下一次Resources.UnloadUnusedAssets来卸载。

感激范君@UWA问答社区提供了答复

A2:SpriteAtlas外面生成的图集(Texture)的确是无奈应用Resources.UnloadAsset来卸载的,应用这个接口只能卸载内存中SpriteAtlas对象,而不能卸载SpriteAtlas外面援用的sactx结尾的Texture。这种关系相似于Sprite和Texture。

能够看到内存中有SpriteAtlas,也有SpriteAtlas援用的Texture,这个Texture是被SpriteAtlas援用的。

调用Resoures.UnloadAsset(sa)之后,SpriteAtlas对象从内存里卸载了,然而那个sactx结尾的Texture还在内存中,只是没有了SpriteAtlas援用它而已。在Sprite中,咱们能够调用Resources.Unload(Sprite.texture)来卸载这个Sprite援用的纹理,然而SpriteAtlas没有提供这样的接口。咱们能够曲线获取到这个Texture,从SpriteAtlas外面加载一个小的Sprite,而后调用这个Resources.UnloadAsset(Sprite.texture),然而Unity会报错。

报错内容是“UnloadAsset can only be used on assets;”,所以只能清理完援用关系后调用Resources.UnloadUnusedAssets,或者AssetBundle.Unload(true)来卸载。

感激Xuan@UWA问答社区提供了答复

Shader

Q:UWA报告中指出Shader.Parse调用频繁,这里咱们目前有二个疑难:
第一,Shader解析当前占用的ShaderLab内存,在咱们开释对应Shader当前是否也是失常开释的?
第二,Shader反复解析除了预加载咱们是否能够通过其余形式来防止?比方,对Shader依赖剖析做好当前是否能够防止?

另外,对于Standard ,是否能够提供一个工具让咱们查问有哪些应用到了Standard?

A:1. Shader开释后,ShaderLab的内存是会相应降落的;如果Shader的依赖关系做好,能够很大水平上升高Shader资源的冗余问题;

2. Standard Shader能够通过UWA在线AssetBundle检测来查看,具体是打包到哪些AssetBundle文件中。同时,也能够通过UWA本地资源检测来查看Standard Shader的具体情况。

以下服务登录UWA官网均可收费应用:
在线AssetBundle资源检测

UWA本地资源检测工具

感激芭妮妮@UWA问答社区提供了答复

Script

Q:我遇到一个问题:在一个工夫点一个GameObject的localScale会被设置成另外一个我不冀望的值,然而找了半天相干援用的代码都没有发现localScale被扭转。中途弹出了一个“ [Physics.PhysX] cleaning the mesh failed”谬误,我原本认为是这个引起的,然而我逐帧打印localScale发现是在这个谬误输入之后的N帧之后才呈现的。相干援用办法也都打印了日志,然而都没有发现调用。

A:能够尝试下这个工具:
https://github.com/handzlikchris/Unity.MissingUnityEvents

留神这个工具是须要在Windows应用的,通过注入Unity的DLL实现。简略写了个例子测试可用。

Callstack能够看到调用信息:

而断点跟进去通过Rider的反编译能够看到目前的Transform的localScale的set办法曾经有回调了:

感激范君@UWA问答社区提供了答复

Script

Q:咱们我的项目中有大量的字节文件,大到地图数据,小到各种模块自定义的字节数据。都是通过流的形式去加载的。需要是心愿通过合并这些字节数据,缩小关上流的数量,同时能够分块压缩。

当初的计划:
1. 定义一个Block的大小比方1MB。
2. 对于大于1MB的字节数据按1MB宰割成Block,每个Block独立压缩,最初把这些压缩后的Block合并成一个文件。须要读取某一段数据的时候,通过压缩前后记录的地位,来判断须要解压哪几块Block,而后读取。
3. 对于小于1MB的字节数据和其余字节合并,直到大小大于等于1MB。对合并之后的Block压缩。须要读取某一个文件的时候,把文件所在的Block解压,通过之前记录的地位来读取数据。

最初,生成的文件外面,大文件还是一个文件(外部蕴含了多个1MB+的Block),然而小文件被合成了多个1MB左右的Block。

热更新方面:
1. 对于大文件来说,某一个BlockA数据变动之后,会New一个新的文件,BlockA数据会从服务器下载,其余的Block从本地原来的文件中拷贝过来。
2. 对于小文件来说,其中一个文件删除或者增加,会导致后续分Block的程序不同。
比方:原本有两个小文件的Block->ABCD和EFG,之后把小文件B删除了,生成的规定变成了ACDE和FG了,这样就须要把之前ABCD和EFG全副重写掉。

当初的计划对于热更新不太敌对,特地是小文件,一旦一个删除了或者增加,后续的Block都须要批改。

A1:提供一个思路,仅供参考。
按这个逻辑,打包小文件时应该要把上一次的打包后果的Block Table也作为输出,之前曾经存在的资源并且也在Block Table中有对应的Block时,应首先思考仍保留在这个Block中。

在这个根底上,针对文件新增、删除和更新的状况解决(以问题中Block1:ABCD,Block2:EFG来阐明)。

例子中提到的文件删除、文件B被删除,则新的版本中,Block1应为ACD。
文件新增,比方新增了文件H,如果大小大于Block Size,则依照你们的大文件逻辑解决,否则能够插入到某个仍有空间的Block内,如果没有合乎的Block,则新开一个Block寄存。

如果有文件更新,例如文件A更新为A1,更新后如果大于Block Size,则从Block1中拿出按大文件解决,Block1变更为BCD;如果小于Block Size,当A1 BCD的总大小依然满足Block Size的限度,则失常更新解决,如果A1 BCD的总大小大于Block Size的限度,则将其宰割,例如:A1B为一个新的Block,Block1变成CD。

这类大文件存储形式其实能够参考一些端游的实现形式,比方Blizzard晚期应用的时MPQ格局及前期应用的CASC格局,GitHub上都有开源库能够参考:
https://github.com/ladislav-zezula/StormLib
https://github.com/ladislav-zezula/CascLib

感激范君@UWA问答社区提供了答复

Script

Q:在晓得玩家的坐标点A,怪物的坐标点B,A和B在同一个水平面,相机的所有参数。A和B在视口的地位,可能是同一侧,也可能是不同侧,下图只是一个状况。

两头的红线是视口坐标X=0.5的地位,当初怪物的视口坐标X=y是在黄线的地位,当初想求相机绕着玩家的坐标点Y轴的方向,旋转多少度能够让怪物在视口的坐标变为X=x(就是绿线的地位)?目标是战斗的时候保障怪物主体显示在相机视口,即想显示在相机的局部视口范畴内。

mul(VP, 怪物世界坐标).x = 指定值
mul(VP, 玩家世界坐标).xy = 指定值
摄像机地位和人的地位的间隔 = 指定值

A1:如果是心愿角色和怪物主体始终显示在相机视口中,能够让相机始终对准A、B两点的中点(或中点左近的某一点),同时放弃相机别离与AB的间隔不小于某个值,看相机更凑近A点还是更凑近B点,以近的为准。插值计算应该能够实现你要的成果,思路供参考,还没有实际。

感激eangulee@UWA问答社区提供了答复

A2:在前提是玩家是第一人称视角下,屏幕上指标点A(ax,ay),换算到高空上对应的指标点B(bx,by,bz),假如玩家坐标P,以后怪物坐标M,剩下就是求PM和PB之间的夹角了。

感激孙星星@UWA问答社区提供了答复

A3:以下几点供参考:

  1. center:相机看向核心。
  2. d:相机与核心间隔。
  3. monster:怪物坐标。
  4. fov:相机y轴方向的视线角度。
  5. aspect:相机视线的宽高比。
  6. viewRatio:怪物在视口的x方向的坐标比例(0到1)。
  7. 假如相机旋转角度:a。
  8. 相机坐标:(center.x+dsina , 0, center.z+dcosa)。
  9. 相机x轴:(cosa, 0, -sina)。
  10. 相机y轴:(0, 1, 0)。
  11. 相机z轴:(sina, 0, cosa)。
  12. 怪物在相机空间的x坐标monsterCamX:dot(相机到怪物的向量,相机的x轴)
    = (monster.x-center.x-d sina) cosa - (monster.z-center.z-d cosa) sina
    = (monster.x - center.x) cosa - (monster.z-center.z) sina。
  13. 怪物在相机空间的z坐标monsterCamZ:dot(相机到怪物的向量,相机的z轴)
    = (monster.x-center.x) sina - d sina sina + (monster.z - center.z) cosa - d cosa cosa
    = (monster.x - center.x) sina +(monster.z - center.z) cosa - d。
  14. 相机在怪物的z坐标(深度)处可看到的xy面的宽度camWidth:
    2tan(fov/2) aspect * monsterCamZ
  15. 最初依据怪物视口比例:
    viewRatio = monsterCamX / camWidth

也可能会呈现解这样的方程:sina - 2cosa = 0.2,求角度a。

感激Manchy@UWA问答社区提供了答复

A4:请参考下图公式:

感激Xuan@UWA问答社区提供了答复

封面图来自网络


明天的分享就到这里。当然,生有涯而知无涯。在漫漫的开发周期中,您看到的这些问题兴许都只是冰山一角,咱们早已在UWA问答网站上筹备了更多的技术话题等你一起来摸索和分享。欢送酷爱提高的你退出,兴许你的办法恰能解他人的当务之急;而他山之“石”,也能攻你之“玉”。

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