Unity3D的AssetBundle打包的建议

在Unity3D游戏开发中,要做到游戏资源的热更新,只有唯一途径,就是使用AssetBundle来打包资源,使用过AssetBundle的应该都知道,体验不是很好!实话说,从AssetBundle的设计来说就有太多的问题,AssetBundle对使用者来说不是透明的,使用者必须要关心资源所在的包,还要维护其复杂的依赖关系,并且Unity3D的Asset都是非托管对象,还要考虑它的释放等问题。在底层没有全局弱缓存,多次加载同一个AB,会导致内存中存在多份Asset模板对象。Unity3D的Resources、AssetBundle以及AssetDatabase,连加载的路径规则都不一致,有的不要扩展名,有的需要扩展名,有的必须完整路径加载,有的又可以只需要文件名就可以加载,导致使用起来也很迷糊。前面这些问题,有的是可以自己写一个加载插件解决的(比如我写的Loxodon.Framework.Bundle解决了其中大部分问题),有些是除了U3D官方,别人无法解决的(比如全局弱缓存)。吐槽完了,下面来看看使用过程中我遇到的一些问题和建议。

使用AssetBundle的建议

1、 关于内置的shader,除了Standard的2个shader外,尽量配置在Graphics中的Always Included Shaders 的列表中,配置在这个列表中的shader不会在被打包到AssetBundle中。关于Lightmap Modes和Fog Modes请都使用Manual模式,手动配置使用了的灯光贴图模式,不要选择自动,否则会根据当前打开的场景配置这几个参数,因为每次打包时,当前打开的场景可能不一样,会导致很多问题。

这个配置表不要频繁修改,每次修改之后,都要删除AssetBundle的打包目录,重新打包所有的AssetBundle,才会对所有的AssetBundle生效。否则只有有改变的AssetBundle会生效,没有改变的AssetBundle不会生效,也可能导致某些shader显示错误。

关于自定义的shader,可以统一打包到同一个AssetBundle中,在游戏启动时就载入到内存,并且保证这个AssetBundle不要卸载,这样确保在游戏运行过程中,不会出现多份shader,占用多份内存。

关于Standard的shader,最好不要使用,它占用大量内存(经常1兆到几兆),在某些低端机器还会导致GPU显存不够而崩溃,使用自定义shader替换是一个比较好的方式。

2、关于内置的资源,在Unity3D资源导入的时候,模型、特效、场景等经常会引用内置的材质球 Default-Diffuse、Default-Material、Default-Particle、Default-Skybox、Sprites-Default等。这会导致项目依赖大量的内置图片、材质和shader,特别是引用了Standard这个shader。在打包后,都被隐式的包含在AssetBundle中,导致大量的冗余,同样加载到内存中也会导致重复的内存占用。

去除这些资源比较有效的办法是拷贝一份内建资源到项目中,用自定义shader替换标准shader,然后写一个PostProcessor脚本,自动替换这些资源。

Unity5.6版本,内建资源如下:(可以从我的github下载

下面的脚本在资源导入时自动替换内置资源,避免依赖内置图片、shader、材质球等

using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.Text.RegularExpressions;

class BuiltinAssetReplacePostprocessor : AssetPostprocessor
{
    static Regex regex = new Regex("(Default-Material)|(Default-Diffuse)|(Default-Particle)|(Default-Skybox)|(Sprites-Default)|(Default-ParticleSystem)");
    static Dictionary<string, Material> materials = new Dictionary<string, Material>();
    static Material GetMaterial(string path)
    {
        Material material;
        if (materials.TryGetValue(path, out material))
            return material;

        material = AssetDatabase.LoadMainAssetAtPath(path) as Material;
        if (material == null)
            material = new Material(Shader.Find("Mobile/Diffuse"));

        materials.Add(path, material);
        return material;
    }

    //替换模型的内置材质球,只有当模型使用了内置的材质球和shader时会替换
    protected virtual Material OnAssignMaterialModel(Material previousMaterial, Renderer renderer)
    {
        if (previousMaterial.name == "" || previousMaterial.name.Contains("Default-Material")
                || previousMaterial.shader.name.StartsWith("Standard") || previousMaterial.name.Contains("-Default")
                || previousMaterial.name.Contains("No Name") || !(assetImporter as ModelImporter).importMaterials)
        {
            return GetMaterial("Assets/builtin/builtin_materials/Default-Material.mat");
        }
        return null;
    }

    //替换prefab中引用的内置资源
    static void OnPostprocessAllAssets(string[] importedAsset, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
    {
        if (importedAsset.Length > 0)
        {
            for (int i = 0; i < importedAsset.Length; i++)
            {
                var path = importedAsset[i];
                if (!path.EndsWith(".prefab"))
                    continue;

                GameObject go = AssetDatabase.LoadAssetAtPath<GameObject>(path);
                if (go == null)
                    continue;

                Renderer[] renderers = go.GetComponentsInChildren<Renderer>();
                if (renderers == null)
                    continue;

                bool success = false;
                foreach (Renderer p in renderers)
                {
                    if (p.sharedMaterials == null || p.sharedMaterials.Length <= 0)
                        continue;

                    Material[] materials = p.sharedMaterials;
                    for (int j = 0; j < materials.Length; j++)
                    {
                        var material = materials[i];
                        if (material == null || Regex.IsMatch(AssetDatabase.GetAssetPath(material), @"Assets/builtin/builtin_materials/"))
                            continue;

                        var match = regex.Match(material.name);
                        if (!match.Success)
                            continue;

                        Material newMaterial = GetMaterial(string.Format("Assets/builtin/builtin_materials/{0}.mat", material.name));
                        if (newMaterial == null)
                            continue;

                        materials[i] = newMaterial;
                        success = true;
                    }

                    if (success)
                        p.sharedMaterials = materials;
                }

                if (success)
                    Debug.LogFormat("Replace default material of particle: \"{0}\" ", importedAsset[i]);
            }
            return;
        }
    }
}

3、建议使用LZ4压缩格式,弃用以前的压缩格式,LZ4压缩支持分块压缩,虽然压缩比率不如LZMA,但是解压速度更快,而且加载某个特定资源不需要将整个AssetBundle解压,不会到导致大量的内存占用。

4、弃用WWW加载AssetBundle的方式,会占用AssetBundle包大小双倍以上的内存,导致内存峰值很高。使用UnityWebRequest替换WWW。LZ4格式,配合使用AssetBundle.LoadFromFileAsync 和 UnityWebRequest 加载,最大占用内存就是AB解压后的内存,不会导致内存峰值。

5、尽量不要协程并发加载同一个AssetBundle包中的资源,早期的版本(大概Unity3d 5.5)在Android平台发现会导致加载的资源材质或者贴图丢失,Unity3D新的版本不知道这个bug是否解决。

6、不同平台资源格式是有差异的,特别是shader,Android、iOS等其他平台的shader是不能在Editor模式使用的。遇到很多朋友在Android平台下打包AssetBundle,然后使用Android平台的资源,在编辑器模式下运行测试,出现大量紫块的问题。如果你的操作系统是Window平台,你应该打包Window平台的资源在Editor模式使用,如果操作系统是Mac,那么Editor模式下,你应该使用Mac平台的资源。如果非要在Editor模式下使用Android、iOS的资源,也可以在场景加载后,使用Shader.Find 加载shader,替换场景中所有的shader。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值