12

AssetBundle中加载SpriteAtlas图集之后卸载异常

 3 years ago
source link: https://blog.uwa4d.com/archives/TechSharing_232.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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加载如下:

1.png

按下空格卸载如下:

2.png

前后对比发现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。

3.png

可以看到内存中有SpriteAtlas,也有SpriteAtlas引用的Texture,这个Texture是被SpriteAtlas引用的。

4.png

调用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问答社区提供了回答,欢迎大家转至社区交流:
https://answer.uwa4d.com/question/5fd4de5210a17c6c2b09d625


Shader

Q:UWA报告中指出Shader.Parse调用频繁,这里我们目前有二个疑问:
第一,Shader解析以后占用的ShaderLab内存,在我们释放对应Shader以后是否也是正常释放的?
第二,Shader重复解析除了预加载我们是否可以通过其他方式来避免?比如,对Shader依赖分析做好以后是否可以避免?

5.png

另外,关于Standard ,是否可以提供一个工具让我们查询有哪些使用到了Standard?

A:1. Shader释放后,ShaderLab的内存是会相应下降的;如果Shader的依赖关系做好,可以很大程度上降低Shader资源的冗余问题;

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

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

6.png

UWA本地资源检测工具

7.png

感谢芭妮妮@UWA问答社区提供了回答,欢迎大家转至社区交流:
https://answer.uwa4d.com/question/5fd82ca810a17c6c2b09d68b


Script

Q:我遇到一个问题:在一个时间点一个GameObject的localScale会被设置成另外一个我不期望的值,但是找了半天相关引用的代码都没有发现localScale被改变。中途弹出了一个“ [Physics.PhysX] cleaning the mesh failed”错误,我本来以为是这个引起的,但是我逐帧打印localScale发现是在这个错误输出之后的N帧之后才出现的。相关引用方法也都打印了日志,但是都没有发现调用。

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

注意这个工具是需要在Windows使用的,通过注入Unity的DLL实现。简单写了个例子测试可用。

Callstack可以看到调用信息:

8.png

而断点跟进去通过Rider的反编译可以看到目前的Transform的localScale的set方法已经有回调了:

9.png

感谢范君@UWA问答社区提供了回答,欢迎大家转至社区交流:
https://answer.uwa4d.com/question/5fd71e7310a17c6c2b09d658


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问答社区提供了回答,欢迎大家转至社区交流:
https://answer.uwa4d.com/question/5fd9bc3910a17c6c2b09d6cc


Script

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

10.png

中间的红线是视口坐标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:
    2*tan(fov/2) * aspect * monsterCamZ
  15. 最后根据怪物视口比例:
    viewRatio = monsterCamX / camWidth

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

感谢Manchy@UWA问答社区提供了回答

A4:请参考下图公式:

11.png

感谢Xuan@UWA问答社区提供了回答,欢迎大家转至社区交流:
https://answer.uwa4d.com/question/5fd38ac210a17c6c2b09d620

封面图来自网络


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

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK