【转】unity 热更新思路和实现
声明:本文介绍的热更新方案是我在网上搜索到的,然后自己修改了一下,相当于是借鉴了别人的思路,加工成了自己的,在此感谢无私分享经验的朋友们。
想要使用热更新技术,需要规划设计好资源比较策略,资源版本,确保增加新资源后可以下载到本地,有资源更新的时候可以替换掉本地旧资源。我在前面写了一篇“unity 打包AssetBundle”的文章,里面生成了一个资源版本文件,不多解释了,上图。至于怎么生成这个文件的,可以看一下我前面写的文章。
废话不多说。

先介绍热更新步骤,后上代码
步骤一、在Resources目录下新建一个文本,名称是bundle_list(后缀是.txt),内容如下:
{"id":0,"version":"1.0","manifest":"android","resource":{}},当然您可以根据自己项目
实际情况来设计json格式。资源服务器上也会有一份格式相同的bundle_list
步骤二、如果是第一次进入游戏,Application.persistentDataPath目录下还没有bundle_list文件,这
时候就需要用Resources.Load方法从Resources目录中加载出来。否则
加载Application.persistentDataPath目录下的bundle_list
步骤三、从资源服务器下载bundle_list文件
步骤四、获取本地bundle_list的id和资源服务器下载的bundle_list中的id,做对比,如果前者等于后者,
则不需要更新,如果前者小于后者,则需要更新。
步骤五、分别解析出本地和资源服务器bundle_list中的资源路径名称,名称相同的,对比hash值,相同
则不需要更新,反之,更新。如果资源服务器有的名称本地没有,则表示是新增资源,需要
下载到本地。
步骤六、把资源服务器的bundle_list覆盖本地bundle_list。热更新完成。
代码:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.IO;
using LitJson;
/**
* 资源增量更新
* 1.检查本地Application.persistentDataPath目录中是否有bundle_list文件
* 2.如果没有,则从Resources目录中读取bundle_list文件
* 3.从服务器上下载bundle_list文件,判断版本是否一致,如果一致就不用更新
* 4.版本不一致,需要更新,更新
* 5.将最新的bundle_list存入Application.persistentDataPath目录中
**/
public class BundleUpdate : MonoBehaviour
{
private static readonly string VERSION_FILE = "bundle_list";
private string SERVER_RES_URL = "";
private string LOCAL_RES_URL = "";
private string LOCAL_RES_PATH = "";
/// <summary>
/// 本地版本json对象
/// </summary>
private JsonData jdLocalFile;
/// <summary>
/// 服务端版本json对象
/// </summary>
private JsonData jdServerFile;
/// <summary>
/// 本地资源名和路径字典
/// </summary>
private Dictionary<string, string> LocalBundleVersion;
/// <summary>
/// 服务器资源名和路径字典
/// </summary>
private Dictionary<string, string> ServerBundleVersion;
/// <summary>
/// 需要下载的文件List
/// </summary>
private List<string> NeedDownFiles;
/// <summary>
/// 是否需要更新本地版本文件
/// </summary>
private bool NeedUpdateLocalVersionFile = false;
/// <summary>
/// 下载完成委托
/// </summary>
/// <param name="www"></param>
public delegate void HandleFinishDownload(WWW www);
/// <summary>
/// 本次一共需要更新的资源数
/// </summary>
int totalUpdateFileCount = 0;
void Start()
{
#if UNITY_EDITOR && UNITY_ANDROID
SERVER_RES_URL = "file:///" + Application.streamingAssetsPath + "/android/";
LOCAL_RES_URL = "file:///" + Application.persistentDataPath + "/res/";
LOCAL_RES_PATH = Application.persistentDataPath + "/res/";
#elif UNITY_EDITOR && UNITY_IOS
SERVER_RES_URL = "file://" + Application.streamingAssetsPath + "/ios/";
LOCAL_RES_URL = "file:///" + Application.persistentDataPath + "/res/";
LOCAL_RES_PATH = Application.persistentDataPath + "/res/";
#elif UNITY_ANDROID
//安卓下需要使用www加载StreamingAssets里的文件,Streaming Assets目录在安卓下的路径为 "jar:file://" + Application.dataPath + "!/assets/"
SERVER_RES_URL = "jar:file://" + Application.dataPath + "!/assets/" + "android/";
LOCAL_RES_URL = "jar:file://" + Application.persistentDataPath + "!/assets/" + "/res/";
//LOCAL_RES_URL = "file://" + Application.persistentDataPath + "/res/";
LOCAL_RES_PATH = Application.persistentDataPath + "/res/";
#elif UNITY_IOS
SERVER_RES_URL = "http://127.0.0.1/resource/ios/"
LOCAL_RES_URL = "file:///" + Application.persistentDataPath + "/res/";
LOCAL_RES_PATH = Application.persistentDataPath + "/res/";
#endif
//初始化
LocalBundleVersion = new Dictionary<string, string>();
ServerBundleVersion = new Dictionary<string, string>();
NeedDownFiles = new List<string>();
//加载本地version配置
string tmpLocalVersion = "";
if (!File.Exists(LOCAL_RES_PATH + VERSION_FILE))
{
TextAsset text = Resources.Load(VERSION_FILE) as TextAsset;
tmpLocalVersion = text.text;
}
else
{
tmpLocalVersion = File.ReadAllText(LOCAL_RES_PATH + VERSION_FILE);
}
//保存本地的version
ParseVersionFile(tmpLocalVersion, LocalBundleVersion, 0);
//加载服务端version配置
StartCoroutine(this.DownLoad(SERVER_RES_URL + VERSION_FILE, delegate (WWW serverVersion)
{
//保存服务端version
ParseVersionFile(serverVersion.text, ServerBundleVersion, 1);
//计算出需要重新加载的资源
CompareVersion();
//加载需要更新的资源
DownLoadRes();
}));
}
//依次加载需要更新的资源
private void DownLoadRes()
{
if (NeedDownFiles.Count == 0)
{
UpdateLocalVersionFile();
return;
}
string file = NeedDownFiles[0];
NeedDownFiles.RemoveAt(0);
StartCoroutine(this.DownLoad(SERVER_RES_URL + file, delegate (WWW w)
{
//将下载的资源替换本地就的资源
ReplaceLocalRes(file, w.bytes);
DownLoadRes();
}));
}
private void ReplaceLocalRes(string fileName, byte[] data)
{
try
{
string filePath = LOCAL_RES_PATH + fileName;
if (!File.Exists(filePath))
{
string p = Path.GetDirectoryName(filePath);
if (!Directory.Exists(p))
Directory.CreateDirectory(p);
}
File.WriteAllBytes(filePath, data);
}
catch (System.Exception e)
{
Debug.Log("e is " + e.Message);
}
}
//更新本地的version配置
private void UpdateLocalVersionFile()
{
if (NeedUpdateLocalVersionFile)
{
if (!Directory.Exists(LOCAL_RES_PATH))
Directory.CreateDirectory(LOCAL_RES_PATH);
StringBuilder versions = new StringBuilder(jdServerFile.ToJson());
FileStream stream = new FileStream(LOCAL_RES_PATH + VERSION_FILE, FileMode.Create);
byte[] data = Encoding.UTF8.GetBytes(versions.ToString());
stream.Write(data, 0, data.Length);
stream.Flush();
stream.Close();
}
}
private void CompareVersion()
{
int localVersionId;
int serverVersionId;
if (jdLocalFile != null && jdLocalFile.Keys.Contains("id"))
localVersionId = (int)jdLocalFile["id"];
if (jdServerFile != null && jdServerFile.Keys.Contains("id"))
serverVersionId = (int)jdServerFile["id"];
#if UNITY_ANDROID || UNITY_EDITOR
NeedDownFiles.Add("android");
#endif
#if UNITY_IOS
#endif
foreach (var version in ServerBundleVersion)
{
string fileName = version.Key;
string serverHash = version.Value;
//新增的资源
if (!LocalBundleVersion.ContainsKey(fileName))
{
NeedDownFiles.Add(fileName);
}
else
{
//需要替换的资源
string localHash;
LocalBundleVersion.TryGetValue(fileName, out localHash);
if (!serverHash.Equals(localHash))
{
NeedDownFiles.Add(fileName);
}
}
}
totalUpdateFileCount = NeedDownFiles.Count;
//本次有更新,同时更新本地的version.txt
NeedUpdateLocalVersionFile = NeedDownFiles.Count > 0;
}
/// <summary>
///
/// </summary>
/// <param name="content"></param>
/// <param name="dict"></param>
/// <param name="flag">0表示本地版本文件,1表示服务器版本文件</param>
private void ParseVersionFile(string content, Dictionary<string, string> dict, int flag)
{
if (content == null || content.Length == 0)
{
return;
}
JsonData jd = null;
try
{
jd = JsonMapper.ToObject(content);
}
catch (System.Exception e)
{
Debug.LogError(e.Message);
return;
}
if (flag == 0)//本地
{
jdLocalFile = jd;
}
else if (flag == 1)//服务器
{
jdServerFile = jd;
}
else
return;
//获取资源对象
JsonData resObjs = null;
if (jd.Keys.Contains("resource"))
resObjs = jd["resource"];
if (resObjs != null && resObjs.IsObject && resObjs.Count > 0)
{
string[] resNames = new string[resObjs.Count];
resObjs.Keys.CopyTo(resNames, 0);
for (int i = 0; i < resNames.Length; i++)
{
if (resObjs.Keys.Contains(resNames[i]))
dict.Add(resNames[i], resObjs[resNames[i]].ToString());
}
}
}
private IEnumerator DownLoad(string url, HandleFinishDownload finishFun)
{
WWW www = new WWW(url);
yield return www;
if (!string.IsNullOrEmpty(www.error))
{
Debug.LogError("www.error is " + www.error);
yield break;
}
if (finishFun != null)
{
finishFun(www);
}
www.Dispose();
}
}
【转】unity 热更新思路和实现的更多相关文章
- 另类Unity热更新大法:代码注入式补丁热更新
对老项目进行热更新 项目用纯C#开发的? 眼看Unity引擎热火朝天,无数程序猿加入到了Unity开发的大本营. 一些老项目,在当时ulua/slua还不如今天那样的成熟,因此他们选择了全c#开发:也 ...
- unity热更新方案对比
Unity应用的iOS热更新 • 什么是热更新 • 为何要热更新 • 怎样在iOS 上对Unity 应用进行热更新 • 支持Unity iOS 热更新的各种Lua 插件的对照 什么是热更新 • ...
- Unity热更新对比
https://www.jianshu.com/p/f9d90edf4a7c Unity 热更新为啥用Lua 详解 ILRuntime的优势 同市面上的其他热更方案相比,ILRuntime主要有以下优 ...
- Unity 热更新实例一、C#Light 和UI系统使用实例
接下来我会运用热更新的机制,逐步制作一些例子来阐释脚本系统如何和Unity结合. 脚本不限于使用C#Lite,但是C#Lite会有一些便利之处,请往下看. 结合机制也不限于这一种,但是C#Lite的设 ...
- Unity热更新技术整理
一.热更新学习介绍 1.什么是热更新 举例来说: 游戏上线后,玩家下载第一个版本(70M左右或者更大),在运营的过程中,如果需要更换UI显示,或者修改游戏的逻辑,这个时候,如果不使用热更新,就需要重新 ...
- [Unity热更新]tolua# & LuaFramework(一):基础
一.tolua# c#调用lua:LuaState[变量名/函数名] 1.LuaState a.执行lua代码段 DoString(string) DoFile(.lua文件名) Require(.l ...
- Unity热更新 xLua
xLua是Unity3D下Lua编程解决方案,自2016年初推广以来,已经应用于十多款腾讯自研游戏,因其良好性能.易用性.扩展性而广受好评.现在,腾讯已经将xLua开源到GitHub. 2016年12 ...
- Unity热更新 AssetBundle
在游戏开发中,常常需要用到热更新技术.比如:一个手机游戏开发好后,用户安装到手机上.如果此时我们要更新一个新的功能,如果没有热更新,那么需要用户卸载掉手机上的游戏,然后安装新的包,这样做十分麻烦,而且 ...
- C#版的eval,C#Light开源嵌入式脚本,unity热更新不再愁
目前最新版本AlphaV0.06 完全的c#语法,可用于一切能运行C#的场合,wp windows xamarin mono asp.net unity3d 内嵌了int uint bool stri ...
随机推荐
- iOS应用启动原理图解 及ARC强弱引用
iOS应用启动原理图解(红色箭头表示strong强引用,绿色箭头代表weak若引用) 只要将UI控件拖到Storyboard里控制器的大view上,Xcode会自动将这些控件以强引用的形式加入到sel ...
- 【Node.js】新建一个NodeJS 4.X项目
前提工作 1.安装Node.js 各种下一步就好 2.安装NPM(node package manager) 安装好Node.js之后,打开cmd,输入npm install npm -g,程序会自动 ...
- 浅谈async函数await用法
今天状态不太好,睡久了懵一天. 以前只是了解过async函数,并还没有很熟练的运用过,所以先开个坑吧,以后再结合实际来更新下,可能说的有些问题希望大家指出. async和await相信大家应该不陌生, ...
- SpringMVC——笔记
使用 @RequestMapping 映射请求 Spring MVC 使用@RequestMapping 注解为控制器指定可以处理那些URL请求. 在控制器的类定义及方法定义处都可以标注 @Reque ...
- Linux中Elasticsearch集群部署
1.下载安装包elasticsearch-6.3.1 安装包自己下载,网上很多 2.安装位置在cd /usr/local/elasticsearch/目录下 3.因为ES使用root权限运行会报错, ...
- operator.attrgetter() 进行对象排序
## 使用operator.attrgetter() 进行对象排序 from operator import attrgetter class Student: def __init__(self, ...
- QQ空间|qq人气号怎么赚钱?
回报,付出的终极诉求,咱不论情怀. 在<怎么做一个QQ人气号>中,笔者大致提及,打造人气空间的流程,这里简单剖析下QQ人气空间的盈利模式. 关键词:转让,出售,广告,微商,合作,网红,接推 ...
- Python学习手册之内部方法、操作符重载和对象生命周期
在上一篇文章中,我们介绍了 Python 的类和继承,现在我们介绍 Python 的内部方法.操作符重载和对象生命周期. 查看上一篇文章请点击:https://www.cnblogs.com/dust ...
- C语言中字符串赋值的几个理解
在C语言中,字符串的赋值主要有两种方法,第一种是通过指针的方式直接赋值,第二种是通过数组直接赋值. 一.首先,我们来看第一种赋值方法:指针式赋值 我们知道,上面的示例是显然可以正常执行的,也是很容易理 ...
- Qt :undefined reference to vtable for "xxx::xxx"
现象: 类加上宏 Q_OBJECT 就会报错 :undefined reference to vtable for "xxx::xxx" 解决方法: 重新 qmake 其他情况,查 ...