Unity3D⼿游开发⽇记(10)-资源打包的⼀些思考
Unity的资源打包,如果要做完美,其实是很复杂的.那么为什么资源要打包呢,因为我们要做资源更新.没办法啊.
在我看来,完美的资源打包⾄少有以下⼏个⽅⾯要处理好:
1) 资源分类设计合理.控制包粒度,单个包不能太⼤.
2) 灵活的⽂件打包结构.⽀持⽂件和⽂件夹打包
3) 共⽤资源的完美处理.防⽌重复打包.
4) 快速增量打包.加快打包速度.
具体来聊⼀下我的⼀些思考.
1)资源粒度设计合理
其实就是如何划分资源.先说下我的划分.
UI  .prefab
Model    .prefab        需要动态加载的模型,其实就是⾓⾊
Effect  .prefab        需要动态加载的特效,其实就是技能特效
Scene            .unity
Sound            .wav .mp3 .ogg
Animation      .anim
Table              .txt
Lua                .lua
以上的划分也⽐较常规,只是动作⽂件的划分⽐较特殊点.因为动作⽂件共⽤性很强,⽐如所有主⾓的⼏⼗套装备模型都是共⽤的⼀套动作,所以把动作从模型独⽴出来,动态加载动作,这样打包模型,就不会打包动作进去.动态的加载,也有⼀些讲究,原本是每个动作单独打包,⽤到什么动作才加载,这样粒度最⼩,最完美.但是由于WWW加载是异步的,不⽀持同步加载动作,会有很多问题,所以最后的解决⽅案是,把这个模型的所有动作打成了⼀个Bundle包,加载⼀个模型前,先加载这个模型的所有动作.这样来实现同步效果.
2)灵活的⽂件打包结构
上⾯提到了动作打包,我们把⼀个⽂件夹下⾯的所有动作打成了⼀个包,我们也可以单个动作打成⼀个包,所谓灵活,就是这个包可以⽀持1个或者多个主资源.我设计了3种分组类型.这样⼀个包对应哪些资源,就可以划分出来.
// 分组类型
public enum GroupType
{
File_Recursion,            // 按⽂件分组(递归遍历)
Directory_Recursion,        // 按⽂件夹分组(递归遍历)
File_Directory,            // 按⽂件和⽂件夹分组(第⼀级⽬录)
}
// 获得主资源路径
public static Dictionary<string, List<string>> GetMainAssetPaths(string path, string[] fileExtArray, GroupType groupType)
{
Dictionary<string, List<string>> paths = new Dictionary<string, List<string>>();
Dictionary<string, List<string>> paths = new Dictionary<string, List<string>>();
path = path.Replace('\\', '/');
if (groupType == GroupType.File_Directory)
{
string[] files = Directory.GetFiles(path);
string[] dictionaries = Directory.GetDirectories(path);
// 按⽂件分组
foreach (string file in files)
{
foreach (string fileExt in fileExtArray)
{
if (file.EndsWith(fileExt))
{
List<string> list = new List<string>(1); // 数量1
string filename = file.Replace(Application.dataPath, "Assets").Replace('\\', '/');
list.Add(filename);
string key = file.Replace('\\', '/');
key = key.Substring(key.LastIndexOf('/') + 1);
key = key.Substring(0, key.Length - fileExt.Length - 1);
paths.Add(key, list);
break;
}
}
unity3d animation
}
// 按⽂件夹分组
foreach (string directory in dictionaries)
{
files = Directory.GetFiles(directory);
string key = directory.Replace('\\', '/');
key = key.Substring(key.LastIndexOf('/') + 1);
List<string> list = new List<string>();
foreach (string file in files)
{
foreach (string fileExt in fileExtArray)
{
if (file.EndsWith(fileExt))
{
string filename = file.Replace(Application.dataPath, "Assets").Replace('\\', '/');
list.Add(filename);
}
}
}
paths.Add(key, list);
}
}
else
{
GetMainAssetPathsRecursion(paths, path, path, fileExtArray, groupType);
}
return paths;
}
static void GetMainAssetPathsRecursion(Dictionary<string, List<string>> paths, string startPath, string curPath, string[] fileExtArray, GroupType groupType)        {
string[] files = Directory.GetFiles(curPath);
string[] dictionaries = Directory.GetDirectories(curPath);
foreach (string file in files)
{
foreach (string fileEnd in fileExtArray)
{
if (file.EndsWith(fileEnd))
if (file.EndsWith(fileEnd))
{
string key = "";
string filename = file.Replace(Application.dataPath, "Assets").Replace('\\', '/');
if (groupType == GroupType.Directory_Recursion)
{
key = curPath.Replace('\\', '/');
key = key.Substring(startPath.Length + 1);
// 按⽂件夹分组的话,⼀个Key对应多个⽂件.
if (!paths.ContainsKey(key))
{
List<string> list = new List<string>();
list.Add(filename);
paths.Add(key, list);
}
else
{
List<string> list = paths[key];
list.Add(filename);
}
}
else
{
key = file.Replace('\\', '/');
key = key.Substring(startPath.Length + 1);
key = key.Substring(0, key.Length - fileEnd.Length - 1);
List<string> list = new List<string>(1); // 数量1
list.Add(filename);
if (!paths.ContainsKey(key))
paths.Add(key, list);
else
LogSystem.WarningLog("GetMainAssetPathsRecursion 已经包含这个key了 key:{0}", key);
}
break;
}
}
}
/
/ 递归
foreach (string directory in dictionaries)
{
GetMainAssetPathsRecursion(paths, startPath, directory.Replace('\\', '/'), fileExtArray, groupType);
}
}
3)共⽤资源的完美处理
游戏发布的时候.整个包⼩⼀些,推⼴费会少很多,要减少包⼤⼩,完美处理共⽤资源,防⽌资源重复打包就很重要.怎么完美处理呢,其实出共⽤资源,⽤依赖打包解决就⾏了.怎么处理依赖,这⾥就不赘述了.
⼀些常见的共⽤资源:
UI:
字体和⼀些常见的共⽤图集
Model & Effect:
共⽤的shader
Scene:
场景共⽤太多了,得⽤⼯具出来才⾏,我写了⼀个⼯具来查场景共⽤资源.⼀个场景⽤到了,UsedCounter就加1
1.可以筛选资源的后缀和路径.
2.列表点击标题可以排序,
3.点击资源名字,可以跳转到项⽬资源并选中,
4.点击使⽤次数按钮,可以打印哪些场景⽤到了这个资源.
共⽤资源出来了,就可以把共⽤资源打成⼀个或者⼏个包,
处理场景的共⽤资源,我看有些⼈从prefab层级去处理,动态加载这些共⽤的prefab,这种做法其实很⿇烦⽽没必要,你得从场景⾥⾯把这些prefab删除了,才能不打包这些共⽤资源到场景⾥⾯.改成从资源层级去处理就简单很多,因为资源才⼤,⽽prefab其实只是⼀些配置⽂件,不占空间.
4) 快速增量打包
在开发的时候特别重要,有了增量打包,就再也不⽤选中某个资源来打包了,⽐如UI改了,重打整个UI,⾃动出哪些UI改变或者增减了,⼀下就打好了.
Unity5有⽤manifest⽂件来做增量打包,如果是Unity4,其实⾃⼰可以做⼀套manifest.⽐较MD5就⾏了.
分享⼀下我的代码:
using System;
using System.IO;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.Security.Cryptography;
namespace Luoyinan
{
// 依赖资源⽂件信息
public class DependencyAssetInfo
{
public string m_AssetMD5 { get; set; }
public string m_AssetMetaMD5 { get; set; }
public string m_AssetFileSize { get; set; } // ⽤来快速检查过⼤的依赖资源
}
/
/ 包信息
public class AssetBundleInfo
{
public bool m_IsNeedBuild = false;
public string m_BundleName;
public Dictionary<string, DependencyAssetInfo> m_Dependencies = new Dictionary<string, DependencyAssetInfo>();
public string WriteToString(int id)
{
string content = "";
foreach (KeyValuePair<string, DependencyAssetInfo> pair in m_Dependencies)
{
DependencyAssetInfo info = pair.Value;
content += string.Format("{0}\t{1}\t{2}\t{3}\t{4}\t{5}\n"
, id
, m_BundleName
, pair.Key
, info.m_AssetMD5
, info.m_AssetMetaMD5
, info.m_AssetFileSize
);
}
return content;
}
}
public class AssetBundleManifestMgr : Singleton<AssetBundleManifestMgr>
{
public class MD5Info
{
public string md5;
public string filesize;
public MD5Info(string md5, string filesize)
{
this.md5 = md5;
this.filesize = filesize;
}
}
public MD5CryptoServiceProvider m_Md5Generator = new MD5CryptoServiceProvider();
public Dictionary<string, AssetBundleInfo> m_LastABInfos = new Dictionary<string, AssetBundleInfo>();

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。