项目中的小包更新机制
大部分成熟的线上的MMORPG项目,其包括的特效、图集、预制等资源的体积大小必定是庞大臃肿的,这个在业界也是公认的。
如果有打过安卓谷歌包的,想必都清楚谷歌有一个明文规定,上传至谷歌开发者后台的包体大小不能大于100M。对于包体大小大于100M的项目,Google官方也提供了一个方案,Google官方提供了Jobb工具用来生成obb文件,工具可以在 Android\sdk\tools\bin文件夹下找到,生成后于apk文件一起上传至开发者后台,待审核通过发布后供玩家下载。
另一方面,经过调查,国内玩家在下载游戏的时候更偏向于包体偏小的apk。这个时候,另辟蹊径的方案诞生了,以我经历的两款成熟的线上项目为案例,有两个方案供大家参考:
方案一
没有一个官方的名字,姑且叫它强制小包法叭。
在打apk的时候,将游戏用到的代码、闪屏图、loading图、主场景、登录场景、表格、开场动画等最基本的资源提前准备好,在打包时替换进去,这样包体不会很大,玩家所需的拓展资源则通过项目里的热更新机制加载。
这个方法有一个缺点,由于包体内所携带的资源并不完整,只能保证游戏可以运行不会崩溃,更多的功能资源包则需要在登录游戏之前下载。若热更新较大则会“吓跑”一些新玩家(不成文规定:新发布游戏的热更新包体宜小于30M),为了减少这一影响,第二种补充方案应运而生了。
方案二
游戏内小包资源法,作为方案一的补充,它对玩家更友好。
将项目内容分为必须功能和非必须功能,如玩家信息系统、公告系统、技能系统、排行榜系统、基本的新手任务系统以及主城场景等游戏正常运行所必须的或占资源不多的功能系统可以统一将预制图集资源打进包里。但是类似宠物系统、武将系统、野怪系统等占用较多预制资源分类打进AB小包中,用一个json文件来管理资源列表。
在loading主场景的时候,异步检测线上小包资源的json配置是否存在,若存在且为wifi环境则异步下载资源并解压预加载,主界面也会显示相应的窗口。若玩家当前网络环境不满足下载资源的要求,则会在非必须的功能入口处做出限制,告知需要加载资源包才能体验完整的游戏功能,并且会在加载完毕资源后给予一定的奖励。
这样玩家在初始阶段流失的相应较少,体验更佳,对玩家更为友好。
热更新机制
热更新机制已经在另一篇文章中详细阐述了,再此就不重复介绍了。–>热更新机制
游戏内小包资源法
1、小包有对应的资源版本号,在游戏打包前设定相应的资源版本号,而在拉取小包资源的时候也是以资源版本号作为索引去寻找相应的json配置文件。
2、商业游戏最重要的是游戏安全,可以在写入json配置文件列表的脚本中加入相应的文件大小及MD5等信息作为校验,这样就不会轻易被第三方非法修改。
3、在小包解压时,有概率会出现一些文件解压失败,损失一部分资源,或者玩家不小心删掉了一些资源文件,这个时候在重新下载所有小包资源就不明智了。在游戏中给玩家提供一个主动校验资源的功能,根据现有资源文件名遍历json配置资源列表来寻找需要缺失的文件,异步下载并解压。
4、游戏内小包资源法与热更新机制在本质上是一样的,都是通过unity的AssetBundle加载机制来实现的。
小包资源的加载状态
1 2 3 4 5 6 7 8 9 10 11 12
| public enum AssetsPackageState { None, ReadFileEnd, DownLoadStart, DownLoadStop, DownLoadComplete, DecompressStart, DecompresFail, AssetsReady, AssetsCheck, }
|
无感知下载小包资源
在加载进入主主场景时,检测是否需要加载资源,根据网络环境并自动加载,代码流程大致如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
| public void CheckInitDownLoad() { if (m_curAssetsPackageState != AssetsPackageState.None) { return; } if (IsAssetsPackageReady()) { m_isDecompressSuc = true; m_isDownLoadSuc = true; m_curAssetsPackageState = AssetsPackageState.AssetsReady; } else { if (m_curAssetsPackageState == AssetsPackageState.None) { LoginCheckDownLoadInfo(); } } }
public bool IsAssetsPackageReady() { if(m_data.assetPackage != null) { return m_data.assetPackage.isAssetsPackageReady; } return false; }
private void LoginCheckDownLoadInfo() { m_availablePath = GetPathForPlatform(platform, TargetAssetsBundlPath); if (!Directory.Exists(m_availablePath)) { Directory.CreateDirectory(m_availablePath); } if (m_updateStruct == null) { GetUpdateStructForSvr(InitUpateFileInfo); } else { InitUpateFileInfo(); } }
private void GetUpdateStructForSvr(Action succCB = null) { var url = "...."; DownloadSmallFileTask(url, (string text) => { m_updateStruct = JsonMapper.ToObject<UpdateStruct>(text); if (m_updateStruct != null) { if (succCB != null) succCB(); } }); StartDownloadSmallFile(succCB, failCB, netConnectCheckCB, tryTimeLimit); }
public class UpdateInfo { public string name; public int size; public string md5; public int desize; public string demd5; public UpdateInfo() { name = ""; size = 0; md5 = ""; desize = 0; demd5 = ""; } }
private void InitUpateFileInfo() { for (int i = 0; i < m_updateInfo.Count; i++) { var updateInfo = m_updateInfo[i]; InitFileReadTask(m_updateInfo[i], m_availablePath); m_needDeleteFileList.Add(m_availablePath + m_updateInfo[i].name); } }
private void InitFileReadTask(UpdateInfo updateInfo, string path) { string fileName = "ABC.ab"; string url = "https://www.AAA.com/ABC.ab"; string decompressFileName = "abc";
m_totalSize += updateInfo.size; AddReadFileTask(fileName, updateInfo.md5, updateInfo.size, () => { m_curSize += updateInfo.size; }, (TaskWorkStatus status) => {
AddDownloadBigFileTask(fileName, url, updateInfo.md5, updateInfo.size, () => { m_childDownSize = 0; m_curSize += updateInfo.size; }, (TaskWorkStatus downStatus) => { m_childDownSize = 0; }, (long curSize) => { m_childDownSize = curSize; } ); } );
var unDecompressFile = new CUnzipFile(fileName, decompressFileName, updateInfo.desize); m_totalDecompressSize += updateInfo.desize;
AddReadFileTask(decompressFileName, updateInfo.demd5, updateInfo.desize, () => { m_curDecompressSize += updateInfo.desize; }, (TaskWorkStatus status) => { AddDecompressFileTask(decompressFileName, updateInfo.demd5, updateInfo.desize, unDecompressFile, () => { m_curDecompressSize += updateInfo.desize; }); } ); }
|