游戏热更新前的准备
要用的数据类
public class VersionData
{
public string downLoadUrl;
public string version;
public int versionCode;
public List<AssetData> assetDatas;
}
public class AssetData
{
public string abName;
public int len;
public string md5;
}
AssetBundle打包工具(ABTools)
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using Unity.Plastic.Newtonsoft.Json;
using UnityEditor;
using UnityEngine;
public class ABTools : Editor
{
/// <summary>
/// 生成AB资源文件
/// </summary>
[MenuItem("Tools/ABTool")]
static void Build()
{
string outPath = Application.dataPath + "/ABTest/";
string path = Application.dataPath + "/Res/";
//获取文件路径数组
string[] filePaths = Directory.GetFiles(path, ".", SearchOption.AllDirectories);
foreach (string file in filePaths)
{
//过滤unity自动生成的文件
if (Path.GetExtension(file).Contains("meta")) continue;
Debug.Log("----------------------------------");
Debug.Log(file);
string abName = string.Empty;
//将文件路径指向Assets
string fileName = file.Replace(Application.dataPath, "Assets");
//获取AssetBundle文件对象
AssetImporter assetImporter = AssetImporter.GetAtPath(fileName);
//将路径指向Res文件夹下
abName = fileName.Replace("Assets/Res/", string.Empty);
//替换斜杠
abName = abName.Replace("\\", "/");
Debug.Log("replace before::" + abName);
//判断文件路径中是否有_Comm,有的话说明要把这些文件打在一个包内
if (file.Contains("_Comm"))
{
//将文件名替换掉 留下文件夹名
abName = abName.Replace("/" + Path.GetFileName(file), string.Empty);
Debug.Log("SSS::" + abName);
}
else
{
//将文件扩展名去掉
abName = abName.Replace(Path.GetExtension(file), string.Empty);
}
assetImporter.assetBundleName = abName;
Debug.Log(abName);
//AB包扩展名
assetImporter.assetBundleVariant = "u3d";
}
//打包
BuildPipeline.BuildAssetBundles(outPath, BuildAssetBundleOptions.ChunkBasedCompression, BuildTarget.StandaloneWindows64);
string[] filePathss = Directory.GetFiles(path, ".", SearchOption.AllDirectories);
foreach (string file in filePathss)
{
//过滤unity自动生成的文件
if (Path.GetExtension(file).Contains("meta")) continue;
//将文件路径指向Assets
string fileName = file.Replace(Application.dataPath, "Assets");
//获取AssetBundle文件对象
AssetImporter assetImporter = AssetImporter.GetAtPath(fileName);
assetImporter.assetBundleName = string.Empty;
}
//刷新文件界面
AssetDatabase.Refresh();
}
static VersionData versionData = new VersionData();
/// <summary>
/// 生成资源配置文件
/// </summary>
[MenuItem("Tools/MakeVersion")]
static void MakeVersion()
{
//下载Url
versionData.downLoadUrl = "http://127.0.0.1/game/ABTest/";
//版本号
versionData.version = "1.0.0";
//用来判断版本的int值
versionData.versionCode = 0;
if (versionData.assetDatas == null)
{
versionData.assetDatas = new List<AssetData>();
}
else
{
versionData.assetDatas.Clear();
}
string abPath = Application.dataPath + "/ABTest/";
//获取文件路径数组
string[] filePaths = Directory.GetFiles(abPath, ".", SearchOption.AllDirectories);
foreach (var file in filePaths)
{
//判断文件路径是否包含meta或manifest
if (Path.GetExtension(file).Contains("meta") || Path.GetExtension(file).Contains("manifest")) continue;
//转换斜杠
string abName = file.Replace("\\", "/");
abName = abName.Replace(abPath, string.Empty);
//读取文件长度
int len = File.ReadAllBytes(file).Length;
//计算文件md5值
string md5 = FileMD5(file);
AssetData data = new AssetData();
data.abName = abName;
data.len = len;
data.md5 = md5;
versionData.assetDatas.Add(data);
}
//序列化对象
string version = JsonConvert.SerializeObject(versionData);
//写出到本地
File.WriteAllText(abPath + "version.txt", version);
AssetDatabase.Refresh();
}
static StringBuilder sb = new StringBuilder();
private static string FileMD5(string filePath)
{
FileStream file = new FileStream(filePath, FileMode.Open);
MD5 md5 = new MD5CryptoServiceProvider();
byte[] bytes = md5.ComputeHash(file);
file.Close();
for (int i = 0; i < bytes.Length; i++)
{
sb.Append(bytes[i].ToString("X2"));
}
return sb.ToString();
}
}
游戏AB包管理类(ABManager)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.U2D;
public class ABManager : Singleton<ABManager>
{
AssetBundleManifest assetBundleManifest;
string AbPath = Application.dataPath + "/ABTest/";
public ABManager()
{
//获取所有资源的信息
AssetBundle assetBundle = AssetBundle.LoadFromFile(Application.dataPath + "/ABTest/ABTest");
//加载Manifest
assetBundleManifest = assetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
assetBundle.Unload(false);
}
Dictionary<string, MyAssetData> dicAssets = new Dictionary<string, MyAssetData>();
//加载所有资源
private T[] LoadAssets<T>(string abName) where T : Object
{
//获取所有依赖路径
string[] dependencies = assetBundleManifest.GetAllDependencies(abName);
//加载所有依赖的ab包
foreach (var item in dependencies)
{
if(dicAssets.ContainsKey(item))
{
dicAssets[item].count++;
}
else
{
AssetBundle assetBundle1 = AssetBundle.LoadFromFile(AbPath + item);
MyAssetData assetData = new MyAssetData(assetBundle1);
dicAssets.Add(item, assetData);
}
}
//加载该AB包
if(dicAssets.ContainsKey(abName))
{
dicAssets[abName].count++;
}
else
{
AssetBundle assetBundle1 = AssetBundle.LoadFromFile(AbPath + abName);
MyAssetData assetData = new MyAssetData(assetBundle1);
dicAssets.Add(abName, assetData);
}
return dicAssets[abName].ab.LoadAllAssets<T>();
}
/// <summary>
/// 卸载AB包
/// </summary>
/// <param name="abName"></param>
public void UnLoadAB(string abName)
{
//获取所有依赖文件路径
string[] dependencies = assetBundleManifest.GetAllDependencies(abName);
//卸载依赖AB包
foreach (var item in dependencies)
{
if (dicAssets.ContainsKey(item))
{
dicAssets[item].count--;
if (dicAssets[item].count <= 0)
{
dicAssets[item].Unload();
dicAssets.Remove(item);
}
}
}
//卸载该AB包
if (dicAssets.ContainsKey(abName))
{
dicAssets[abName].count--;
if (dicAssets[abName].count <= 0)
{
dicAssets[abName].Unload();
dicAssets.Remove(abName);
}
}
}
/// <summary>
/// 加载其余资源
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="abName"></param>
/// <param name="assetName"></param>
/// <returns></returns>
public T LoadOtherAsset<T>(string abName, string assetName)where T : Object
{
Object[] objects = LoadAssets<T>(abName);
Object obj = null;
//遍历AB包内资源
foreach (var item in objects)
{
if(item.name.Equals(assetName))
{
obj = item;
break;
}
}
return obj as T;
}
Dictionary<int, string> dicGameObject = new Dictionary<int, string>();
/// <summary>
/// 加载GameObject
/// </summary>
/// <param name="abName"></param>
/// <param name="assetName"></param>
/// <returns></returns>
public GameObject LoadGameObject(string abName, string assetName)
{
Object obj = LoadOtherAsset<GameObject>(abName, assetName);
GameObject go = GameObject.Instantiate(obj) as GameObject;
dicGameObject.Add(go.GetInstanceID(), abName);
return go;
}
/// <summary>
/// 加载图集
/// </summary>
/// <param name="abName"></param>
/// <param name="assetName"></param>
/// <returns></returns>
public Sprite[] LoadSprites(string abName, string assetName)
{
SpriteAtlas spriteAtlas = LoadOtherAsset<SpriteAtlas>(abName, assetName);
Sprite[] sprites = new Sprite[spriteAtlas.spriteCount];
spriteAtlas.GetSprites(sprites);
return sprites;
}
/// <summary>
/// 加载精灵
/// </summary>
/// <param name="abName"></param>
/// <param name="assetName"></param>
/// <param name="spriteName"></param>
/// <returns></returns>
public Sprite LoadSprite(string abName, string assetName, string spriteName)
{
SpriteAtlas spriteAtlas = LoadOtherAsset<SpriteAtlas>(abName, assetName);
return spriteAtlas.GetSprite(spriteName);
}
/// <summary>
/// 销毁GameObject
/// </summary>
/// <param name="go"></param>
public void DestroyGameObject(GameObject go)
{
int id = go.GetInstanceID();
string abName = dicGameObject[id];
Object.Destroy(go);
dicGameObject.Remove(id);
UnLoadAB(abName);
}
}
/// <summary>
/// 资源数据类
/// </summary>
public class MyAssetData
{
public AssetBundle ab;
public int count;
public MyAssetData(AssetBundle a)
{
ab = a;
count = 1;
}
public void Unload()
{
ab.Unload(true);
}
}
StreamingAssets路径到PersistentData路径以及检查更新
using Newtonsoft.Json;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.SceneManagement;
using XLua;
public class UpdateLoad : MonoBehaviour
{
Action update;
LuaEnv luaEnv;
Action<string> sceneLoadFinish;
/// <summary>
/// P路径
/// </summary>
public static string PPath
{
get
{
return Application.persistentDataPath + "/ABTest/";
}
}
/// <summary>
/// S路径
/// </summary>
public static string SPath
{
get
{
#if UNITY_ANDROID
return "jar:file://" + Application.dataPath + "!/assets/ABTest";
#else
return Application.streamingAssetsPath + "/ABTest/";
#endif
}
}
// Start is called before the first frame update
void Start()
{
//判断是否是第一次打开游戏
if(!Directory.Exists(PPath))
{
//创建P路径
Directory.CreateDirectory(PPath);
//将S文件夹中的文件复制到P路径下
StartCoroutine(Copy());
}
else
{
//检查是否有更新
StartCoroutine(CheckUpdate());
}
}
IEnumerator Copy()
{
//定义版本文件路径
string streamingAssetPathVersion = SPath + "version.txt";
Debug.Log(streamingAssetPathVersion);
string versionContent = "";
#if UNITY_ANDROID
//获取本地版本文件
UnityWebRequest unityWebRequest = UnityWebRequest.Get(streamingAssetPathVersion);
yield return unityWebRequest.SendWebRequest();
if(unityWebRequest.result == UnityWebRequest.Result.ConnectionError)
{
Debug.Log(unityWebRequest.error);
}
else
{
versionContent = unityWebRequest.downloadHandler.text;
}
unityWebRequest.Dispose();
#else
versionContent = File.ReadAllText(streamingAssetPathVersion);
#endif
//反序列化版本信息
VersionData versionData = JsonConvert.DeserializeObject<VersionData>(versionContent);
//遍历资源包资源
for (int i = 0; i < versionData.assetDatas.Count; i++)
{
AssetData assetData = versionData.assetDatas[i];
string spath = SPath + assetData.abName;
string p = PPath + assetData.abName;
string fileName = Path.GetFileName(spath);
string dir = Path.GetDirectoryName(p);
if(!Directory.Exists (dir))
{
Directory.CreateDirectory (dir);
}
#if UNITY_ANDROID
//从S路径获取文件
UnityWebRequest unityWebRequest1 = UnityWebRequest.Get("file://" + spath);
yield return unityWebRequest1.SendWebRequest();
if(unityWebRequest1.result == UnityWebRequest.Result.ConnectionError)
{
Debug.Log(unityWebRequest1.error);
}
else
{
//将文件写入到P路径下
File.WriteAllBytes(p, unityWebRequest1.downloadHandler.data);
}
unityWebRequest1.Dispose();
#else
File.Copy(spath, dir + fileName);
#endif
}
//将版本信息也复制到P路径下
File.WriteAllText(PPath + "/version.txt", versionContent);
yield return null;
//检查是否有更新
StartCoroutine(CheckUpdate());
}
IEnumerator CheckUpdate()
{
string localVersion = PPath + "version.txt";
Debug.Log(localVersion);
//读取本地版本文件
string localVersionContent = File.ReadAllText(localVersion);
VersionData localVersionData = JsonConvert.DeserializeObject<VersionData>(localVersionContent);
Dictionary<string, AssetData> versionDic = new Dictionary<string, AssetData>();
//将本地资源添加到字典中
for(int i = 0; i < localVersionData.assetDatas.Count; i++)
{
AssetData assetData = localVersionData.assetDatas[i];
versionDic.Add(assetData.abName, assetData);
}
string remoteVersion = localVersionData.downLoadUrl + "version.txt";
string remoteVersioonContent = "";
//获取服务器的版本文件
UnityWebRequest unityWebRequest = UnityWebRequest.Get(remoteVersion);
yield return unityWebRequest.SendWebRequest();
if(unityWebRequest.result == UnityWebRequest.Result.ConnectionError)
{
Debug.Log("出错");
}
else
{
remoteVersioonContent = unityWebRequest.downloadHandler.text;
}
//反序列化从服务器中获取的版本文件
VersionData remoteVersionData = JsonConvert.DeserializeObject<VersionData>(remoteVersioonContent);
List<AssetData> updateList = new List<AssetData>();
//先判断本地版本是否小于服务器的版本
if(localVersionData.versionCode < remoteVersionData.versionCode)
{
//循环从服务器获取的资源列表
for (int i = 0; i < remoteVersionData.assetDatas.Count; i++)
{
AssetData assetData = remoteVersionData.assetDatas[i];
//对比本地资源的字典中是否有该资源
if(versionDic.ContainsKey(assetData.abName))
{
//如果有再判断文件md5值是否有变化
if(versionDic[assetData.abName].md5 != assetData.md5)
{
updateList.Add(assetData);
}
}
//本地没有该资源直接添加到要更新的资源列表中
else
{
updateList.Add(assetData);
}
}
}
else
{
EnterGame();
yield break;
}
//最后将需要更新的文件从服务器下载到本地
for (int i = 0; i < updateList.Count; i++)
{
string abName = updateList[i].abName;
UnityWebRequest updateAsset = UnityWebRequest.Get(remoteVersionData.downLoadUrl + abName);
yield return updateAsset.SendWebRequest();
if(updateAsset.result == UnityWebRequest.Result.ConnectionError)
{
Debug.Log(updateAsset.result);
}
else
{
string perPath = PPath + abName;
string filename = Path.GetFileName(perPath);
string dir = Path.GetDirectoryName(perPath).Replace("\\", "/") + "/";
//判断本地是否存在该路径的文件夹
if(!Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}
//将下载的资源写入到本地
File.WriteAllBytes(dir + filename, updateAsset.downloadHandler.data);
}
}
//最后更新版本文件
File.WriteAllText(PPath + "version.txt", remoteVersioonContent);
yield return null;
EnterGame();
}
private void EnterGame()
{
PlayerConfig.Ins.Init();
luaEnv = new LuaEnv();
luaEnv.AddBuildin("rapidjson", XLua.LuaDLL.Lua.LoadRapidJson);
luaEnv.AddLoader(CustomLoader);
luaEnv.DoString("require 'GameMain'");
Action action = luaEnv.Global.Get<Action>("Login");
action();
sceneLoadFinish = luaEnv.Global.Get<Action<string>>("SceneLoadFinish");
SceneManager.sceneLoaded += sceneLoad;
}
private void sceneLoad(Scene arg0, LoadSceneMode arg1)
{
sceneLoadFinish(arg0.name);
}
private byte[] CustomLoader(ref string filepath)
{
string path = Application.dataPath + "/Lua/" + filepath + ".lua";
return File.ReadAllBytes(path);
}
public void Select()
{
Action action = luaEnv.Global.Get<Action>("Select");
action();
}
// Update is called once per frame
public void Update()
{
update?.Invoke();
}
}