今天开始unity热更新的真正核心,在做unity动态热更新的时候,查看网上的一些资料,发现unity给的官方AssetBundle方案,只可以实现资源的热更新,无法实现脚本代码的热更新,当然也有一些方案来实现脚本代码的热更新,在这里就天马行空的展开下(关于脚本热更新部分皆为互联网所得,没有经过验证,这里留下脚印,以备后用),网上最多提及的是如下这几种方案:uLua、uLua&cstoLua、sLua,而这三种方案的优缺在这里就不在做“搬运工”了,将来有机会做了实验之后在如实做下记录。
上一段主要说了些题外话,其实这篇文章主要说明的还是采用的AssetBundle的资源热更新技术,这个现在是一个成熟的技术方案,可以对图片、声音、模型等资源进行动态热更新,但是基本推荐的技术是将预制体或场景打为AssetBundle然后再进行使用,当然最优的方案还是将预制体打为AssetBundle进行使用。
还有就是一个比较重要的问题就是,有时某些资源在打完AssetBundle后,在使用时,会出现一些物体渲染不出来的问题,这个主要是跟sharder没有打到AssetBundle中有关,所以在打AssetBundle前添加上你所需的shader,位置Project Setting中的Graphics Settings中添加上所需shader,然后再打AssetBundle。
下面先来一段打AssetBundle的代码:
using UnityEngine;
using System.Collections;
using UnityEditor;
using System.IO;
public class AssetBundleTest : Editor
{
//将视图选择的文件夹下所有游戏对象打成一个AssetBundle包-安卓
[MenuItem("Custom Editor/Android/Create AssetBunldes ALL 3DA")]
static void CreateAndroidAssetBunldesALL3DA()
{
createAndroidAll("ALL-3DA");
}
[MenuItem("Custom Editor/Android/Create AssetBunldes ALL 3DB")]
static void CreateAndroidAssetBunldesALL3DB()
{
createAndroidAll("ALL-3DB");
}
[MenuItem("Custom Editor/Android/Create AssetBunldes ALL 3DC")]
static void CreateAndroidAssetBunldesALL3DC()
{
createAndroidAll("ALL-3DC");
}
[MenuItem("Custom Editor/Android/Create AssetBunldes ALL MovA")]
static void CreateAndroidAssetBunldesALLMovA()
{
createAndroidAll("ALL-MovA");
}
[MenuItem("Custom Editor/Android/Create AssetBunldes ALL MovB")]
static void CreateAndroidAssetBunldesALLMovB()
{
createAndroidAll("ALL-MovB");
}
static void createAndroidAll(string name)
{
string str = CheckOperationName();
if (str != "android")
{
Debug.LogError("操作错误!请选择Android分支");
return;
}
Caching.CleanCache();
string Path = Application.dataPath + "/StreamingAssets/"+name+".assetbundle";
//获取在Project视图中选择的所有游戏对象
Object[] SelectedAsset = Selection.GetFiltered(typeof(Object), SelectionMode.DeepAssets);
foreach (Object obj in SelectedAsset)
{
Debug.Log("Create AssetBunldes name :" + obj);
}
//打包资源
//注释掉的这两句为5.3.0前版本采用的AssetBundle打包方式没有LZ4压缩-第一句目标web,第二句目标安卓,第三句目标安卓
//if (BuildPipeline.BuildAssetBundle(null, SelectedAsset, Path, BuildAssetBundleOptions.UncompressedAssetBundle | BuildAssetBundleOptions.CollectDependencies, BuildTarget.WebPlayer))
//if (BuildPipeline.BuildAssetBundle(null, SelectedAsset, Path, BuildAssetBundleOptions.UncompressedAssetBundle | BuildAssetBundleOptions.CollectDependencies, BuildTarget.Android))
if (BuildPipeline.BuildAssetBundle(null, SelectedAsset, Path, BuildAssetBundleOptions.ChunkBasedCompression | BuildAssetBundleOptions.CollectDependencies, BuildTarget.Android))
{
AssetDatabase.Refresh();
}
else
{
}
}
//将视图选择的文件夹下所有游戏对象打成一个AssetBundle包-IOS
[MenuItem("Custom Editor/IOS/Create AssetBunldes ALL 3DA")]
static void CreateIOSAssetBunldesALL3DA()
{
createIOSAll("ALL-3DA-IOS");
}
[MenuItem("Custom Editor/IOS/Create AssetBunldes ALL 3DB")]
static void CreateIOSAssetBunldesALL3DB()
{
createIOSAll("ALL-3DB-IOS");
}
[MenuItem("Custom Editor/IOS/Create AssetBunldes ALL 3DC")]
static void CreateIOSAssetBunldesALL3DC()
{
createIOSAll("ALL-3DC-IOS");
}
[MenuItem("Custom Editor/IOS/Create AssetBunldes ALL MovA")]
static void CreateIOSAssetBunldesALLMovA()
{
createIOSAll("ALL-MovA-IOS");
}
[MenuItem("Custom Editor/IOS/Create AssetBunldes ALL MovB")]
static void CreateIOSAssetBunldesALLMovB()
{
createIOSAll("ALL-MovB-IOS");
}
static void createIOSAll(string name)
{
string str = CheckOperationName();
if (str != "ios")
{
Debug.LogError("操作错误!请选择IOS分支");
return;
}
Caching.CleanCache();
string Path = Application.dataPath + "/StreamingAssets/" + name + ".assetbundle";
//获取在Project视图中选择的所有游戏对象
Object[] SelectedAsset = Selection.GetFiltered(typeof(Object), SelectionMode.DeepAssets);
foreach (Object obj in SelectedAsset)
{
Debug.Log("Create AssetBunldes name :" + obj);
}
//打包资源
//注释掉的这两句为5.3.0前版本采用的AssetBundle打包方式没有LZ4压缩-第一句目标web,第二句目标IOS,第三句目标IOS
//if (BuildPipeline.BuildAssetBundle(null, SelectedAsset, Path, BuildAssetBundleOptions.UncompressedAssetBundle | BuildAssetBundleOptions.CollectDependencies, BuildTarget.WebPlayer))
//if (BuildPipeline.BuildAssetBundle(null, SelectedAsset, Path, BuildAssetBundleOptions.UncompressedAssetBundle | BuildAssetBundleOptions.CollectDependencies, BuildTarget.iOS))
if (BuildPipeline.BuildAssetBundle(null, SelectedAsset, Path, BuildAssetBundleOptions.ChunkBasedCompression | BuildAssetBundleOptions.CollectDependencies, BuildTarget.iOS))
{
AssetDatabase.Refresh();
}
else
{
}
}
[MenuItem("Custom Editor/Create Scene")]
static void CreateSceneALL ()
{
//清空一下缓存
Caching.CleanCache();
string Path = Application.dataPath + "/StreamingAssets/MyScene.unity3d";
string []levels = {"Assets/Level.unity"};
//打包场景
BuildPipeline.BuildPlayer(levels, Path, BuildTarget.Android,BuildOptions.BuildAdditionalStreamedScenes);
AssetDatabase.Refresh ();
}
[MenuItem("Custom Editor/TestCheckOperation")]
static void CheckOperation()
{
string name = CheckOperationName();
Debug.LogError("操作分支:"+name);
}
static string CheckOperationName()
{
string osSting =
#if UNITY_ANDROID
"android";
#elif UNITY_IPHONE
"ios";
#else
"qita";
#endif
return osSting;
}
}
上面代码为Editor代码,需要放入项目的Editor文件夹中。在上面代码共有四种功能,打安卓的AssetBundle,打ios的AssetBundle,打安卓的场景AssetBundle,检测当前所处分支。
打资源AssetBundle的方法是用鼠标选中资源或资源所处位置。如下图所示:
还有就是网上有的人说在windows系统下不能打ios的AssetBundle,打出来的AssetBundle不可用,最后在实际项目中经过验证发现并不是这样的,只需要在打android与ios的AssetBundle时(我们windows系统打ios跟安卓的AssetBundle,应用到不同平台完全没有问题,但是注意iOS跟安卓一定要分开打,选择相对应的BuildTarget),在Build Setting选择对应的分支平台,如下图为android分支平台:
若要选择ios分支平台,需要展开ios对应tab,如图所示,然后再点击button(Switch Platform),等一段时间进行切换(视项目资源多少,切换时间差异很大)。
下面开始记录下AssetBundle的使用部分,依然还是先贴上代码:
using UnityEngine;
using System.Collections;
public class RunScript : MonoBehaviour
{
public AudioSource mAudio;
private static AssetBundle mDownload=null;
private string debugLog = "";
//不同平台下StreamingAssets的路径是不同的,这里需要注意一下。
//public static readonly string PathURL =
public static string PathURL =
#if UNITY_ANDROID
//"jar:file://" + Application.dataPath + "!/assets/";
Application.dataPath + "!assets/";
#elif UNITY_IPHONE
Application.dataPath + "/Raw/";
#elif UNITY_STANDALONE_WIN || UNITY_EDITOR
//"file://" + Application.dataPath + "/StreamingAssets/";
Application.dataPath + "/StreamingAssets/";
#else
string.Empty;
//Application.dataPath + "/StreamingAssets/";
#endif
void Start()
{
#if UNITY_ANDROID
Debug.Log("UNITY_ANDROID");
debugLog += "UNITY_ANDROID"+"\n";
#elif UNITY_IPHONE
Debug.Log("UNITY_IPHONE");
debugLog += "UNITY_IPHONE"+"\n";
#elif UNITY_STANDALONE_WIN || UNITY_EDITOR
Debug.Log("UNITY_STANDALONE_WIN || UNITY_EDITOR");
debugLog += "UNITY_STANDALONE_WIN || UNITY_EDITOR" + "\n";
#else
Debug.Log("else");
debugLog += "else"+"\n";
#endif
#if UNITY_EDITOR
PathURL = Application.dataPath + "/StreamingAssets/";
#endif
}
void OnGUI()
{
GUI.Label(new Rect(0,60,Screen.width,Screen.height),debugLog);
if(GUILayout.Button("ALL Assetbundle"))
{
StartCoroutine(LoadAllGameObject());
}
if(GUILayout.Button("Open Scene"))
{
StartCoroutine(LoadSceneCreatFromFile());
}
if (GUILayout.Button("Open game1"))
{
LoadGame1test();
}
if (GUILayout.Button("Open game2"))
{
LoadGame2Test();
}
}
//加载assetBundle获得场景并载入
private IEnumerator LoadSceneCreatFromFile()
{
AssetBundle download = AssetBundle.LoadFromFile(PathURL + "MyScene.unity3d");
yield return download;
//var bundle = download.assetBundle;
Application.LoadLevel("Level");
}
//加载assetBundle获得预制体与音频
private IEnumerator LoadAllGameObject()
{
Debug.Log(PathURL + "ALL-3DA-IOS.assetbundle");
debugLog += PathURL + "ALL-3DA-IOS.assetbundle" + "\n";
AssetBundle download = AssetBundle.LoadFromFile(PathURL + "ALL-3DA-IOS.assetbundle");
yield return download;
GameObject obj = GameObject.Instantiate(download.LoadAsset("Prefab0")) as GameObject;
debugLog += obj + "\n";
Debug.Log(obj);
obj.transform.position = new Vector3(0, 0, 0);
GameObject obj1 = GameObject.Instantiate(download.LoadAsset("art")) as GameObject;
debugLog += obj1 + "\n";
Debug.Log(obj1);
obj1.transform.position = new Vector3(0, 0.1f, 0);
mAudio.clip = download.LoadAsset("art.mp3") as AudioClip;
mAudio.Play();
}
private IEnumerator LoadGame1()
{
if (mDownload == null)
{
Debug.Log(PathURL + "ALL.assetbundle");
debugLog += PathURL + "ALL.assetbundle" + "\n";
mDownload = AssetBundle.LoadFromFile(PathURL + "ALL.assetbundle");
}
yield return mDownload;
GameObject obj = GameObject.Instantiate(mDownload.LoadAsset("Prefab0")) as GameObject;
debugLog += obj + "\n";
Debug.Log(obj);
obj.transform.position = new Vector3(0, 0, 0);
}
private IEnumerator LoadGame2()
{
if (mDownload == null)
{
Debug.Log(PathURL + "ALL.assetbundle");
debugLog += PathURL + "ALL.assetbundle" + "\n";
AssetBundle download = AssetBundle.LoadFromFile(PathURL + "ALL.assetbundle");
}
yield return mDownload;
GameObject obj1 = GameObject.Instantiate(mDownload.LoadAsset("art")) as GameObject;
debugLog += obj1 + "\n";
Debug.Log(obj1);
obj1.transform.position = new Vector3(0, 0.1f, 0);
}
private void LoadGame1test()
{
if (mDownload == null)
{
Debug.Log(PathURL + "ALL.assetbundle");
debugLog += PathURL + "ALL.assetbundle" + "\n";
mDownload = AssetBundle.LoadFromFile(PathURL + "ALL.assetbundle");
}
GameObject obj = GameObject.Instantiate(mDownload.LoadAsset("Prefab0")) as GameObject;
debugLog += obj + "\n";
Debug.Log(obj);
obj.transform.position = new Vector3(0, 0, 0);
}
private void LoadGame2Test()
{
if (mDownload == null)
{
Debug.Log(PathURL + "ALL.assetbundle");
debugLog += PathURL + "ALL.assetbundle" + "\n";
AssetBundle download = AssetBundle.LoadFromFile(PathURL + "ALL.assetbundle");
}
GameObject obj1 = GameObject.Instantiate(mDownload.LoadAsset("art")) as GameObject;
debugLog += obj1 + "\n";
Debug.Log(obj1);
obj1.transform.position = new Vector3(0, 0.1f, 0);
}
public void testAudio(){
mAudio.clip = (AudioClip)Resources.Load("art", typeof(AudioClip));
mAudio.Play();
}
}
上面的代码主要是使用unity5.4.0调试出来的,unity的版本在5.3.0时对AssetBundle进行了一些修订,所以这个版本前后可能会出现一些API不兼容的问题,出现这个问题还需要自己进行调整下,在这里特此说明。
对于AssetBundle的使用,避免不了的就是对于AssetBundle的卸载:
-
Unload
该方法会卸载运行时内存中包含在bundle中的所有资源。
当传入的参数为true,则不仅仅内存中的AssetBundle对象包含的资源会被销毁。根据这些资源实例化而来的游戏内的对象也会销毁。
当传入的参数为false,则仅仅销毁内存中的AssetBundle对象包含的资源。
而在对于大量图片,最好是使用异步加载的方式,大体代码如下:
Texture frameAniTex;
AssetBundleRequest mRequest = mDownLoad.LoadAssetAsync(textureName + "_" + currentIndex.ToString("D5"),typeof(Texture));
frameAniTex = mRequest.asset as Texture;
今天暂时能够想到的就暂时就这些了。
最后在这里还是需要重申由于安卓不同路径下的读写权限的问题,所以在实验assetbundle热更新的时候,路径地址Application.persistentDataPath。
demo地址:http://download.csdn.net/detail/xunni_5241/9623036
本人文章纯属个人总结,且某些demo在项目采用前可能来自互联网,最终实验验证后实际项目中采用,若发现来自贵方,谅解;若发现,纰漏错误妄指正。