乐趣区

关于script:AssetBundle中加载SpriteAtlas图集之后卸载异常

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(原群已满员)

退出移动版