[Unity熱更新]動態載入
阿新 • • 發佈:2019-01-09
參考連結:
http://www.xuanyusong.com/archives/1919
對於動態載入,主要有兩種方式:
1.使用Resources.Load
2.使用AssetBundle
在遊戲中,有一個很常見的情況:
有多個場景,且一開始時場景中角色和攝像機的位置旋轉是不同的。如果我們把角色都放在場景,然後打包,明顯是不對的(會增加apk的體積),所以需要把角色和場景分開,放在不同的包中。這時就需要根據配置資訊來放置角色和攝像機的位置了。
生成配置檔案:
using UnityEngine; using System.Collections; using UnityEditor; using System.Collections.Generic; using System.Xml; using System.IO; using System.Text; using LitJson; public class ExportScene : Editor { //將所有遊戲場景匯出為XML格式 [MenuItem ("Tool/ExportXML")] static void ExportXML () { string filepath = Application.dataPath + @"/StreamingAssets/sceneInfoXML.xml"; if(File.Exists (filepath)) { File.Delete(filepath); } XmlDocument xmlDoc = new XmlDocument(); XmlElement root = xmlDoc.CreateElement("scenes"); //遍歷所有的遊戲場景 foreach (UnityEditor.EditorBuildSettingsScene s in UnityEditor.EditorBuildSettings.scenes) { //當關卡啟用 if (s.enabled) { //得到關卡的名稱 string name = s.path; //開啟這個關卡 EditorApplication.OpenScene(name); XmlElement scene = xmlDoc.CreateElement("scene"); scene.SetAttribute("name",name); foreach (GameObject obj in Object.FindObjectsOfType(typeof(GameObject))) { //因為場景內物體都製作為prefab,所以只需遍歷父物體而無需遍歷子物體(prefab已包含子物體的資訊) if (obj.transform.parent == null) { XmlElement gameObject = xmlDoc.CreateElement("gameObject"); gameObject.SetAttribute("name",obj.name); gameObject.SetAttribute("asset",obj.name + ".prefab"); XmlElement transform = xmlDoc.CreateElement("transform"); XmlElement position = xmlDoc.CreateElement("position"); XmlElement position_x = xmlDoc.CreateElement("x"); position_x.InnerText = obj.transform.position.x+""; XmlElement position_y = xmlDoc.CreateElement("y"); position_y.InnerText = obj.transform.position.y+""; XmlElement position_z = xmlDoc.CreateElement("z"); position_z.InnerText = obj.transform.position.z+""; position.AppendChild(position_x); position.AppendChild(position_y); position.AppendChild(position_z); XmlElement rotation = xmlDoc.CreateElement("rotation"); XmlElement rotation_x = xmlDoc.CreateElement("x"); rotation_x.InnerText = obj.transform.rotation.eulerAngles.x+""; XmlElement rotation_y = xmlDoc.CreateElement("y"); rotation_y.InnerText = obj.transform.rotation.eulerAngles.y+""; XmlElement rotation_z = xmlDoc.CreateElement("z"); rotation_z.InnerText = obj.transform.rotation.eulerAngles.z+""; rotation.AppendChild(rotation_x); rotation.AppendChild(rotation_y); rotation.AppendChild(rotation_z); XmlElement scale = xmlDoc.CreateElement("scale"); XmlElement scale_x = xmlDoc.CreateElement("x"); scale_x.InnerText = obj.transform.localScale.x+""; XmlElement scale_y = xmlDoc.CreateElement("y"); scale_y.InnerText = obj.transform.localScale.y+""; XmlElement scale_z = xmlDoc.CreateElement("z"); scale_z.InnerText = obj.transform.localScale.z+""; scale.AppendChild(scale_x); scale.AppendChild(scale_y); scale.AppendChild(scale_z); transform.AppendChild(position); transform.AppendChild(rotation); transform.AppendChild(scale); gameObject.AppendChild(transform); scene.AppendChild(gameObject); root.AppendChild(scene); xmlDoc.AppendChild(root); xmlDoc.Save(filepath); } } } } //重新整理Project檢視, 不然需要手動重新整理哦 AssetDatabase.Refresh(); } //將所有遊戲場景匯出為JSON格式 [MenuItem ("Tool/ExportJSON")] static void ExportJSON () { string filepath = Application.dataPath + @"/StreamingAssets/sceneInfoJSON.txt"; FileInfo t = new FileInfo(filepath); if(File.Exists (filepath)) { File.Delete(filepath); } StreamWriter sw = t.CreateText(); StringBuilder sb = new StringBuilder (); JsonWriter writer = new JsonWriter (sb); writer.WriteObjectStart (); writer.WritePropertyName ("scenes"); writer.WriteArrayStart (); foreach (UnityEditor.EditorBuildSettingsScene s in UnityEditor.EditorBuildSettings.scenes) { if (s.enabled) { string name = s.path; EditorApplication.OpenScene(name); writer.WriteObjectStart(); writer.WritePropertyName("scene"); writer.WriteObjectStart(); writer.WritePropertyName("name"); writer.Write(name); writer.WritePropertyName("gameObjects"); writer.WriteArrayStart (); foreach (GameObject obj in Object.FindObjectsOfType(typeof(GameObject))) { if (obj.transform.parent == null) { writer.WriteObjectStart(); writer.WritePropertyName("name"); writer.Write(obj.name); writer.WritePropertyName("position"); writer.WriteArrayStart (); writer.WriteObjectStart(); writer.WritePropertyName("x"); writer.Write(obj.transform.position.x.ToString("F5")); writer.WritePropertyName("y"); writer.Write(obj.transform.position.y.ToString("F5")); writer.WritePropertyName("z"); writer.Write(obj.transform.position.z.ToString("F5")); writer.WriteObjectEnd(); writer.WriteArrayEnd(); writer.WritePropertyName("rotation"); writer.WriteArrayStart (); writer.WriteObjectStart(); writer.WritePropertyName("x"); writer.Write(obj.transform.rotation.eulerAngles.x.ToString("F5")); writer.WritePropertyName("y"); writer.Write(obj.transform.rotation.eulerAngles.y.ToString("F5")); writer.WritePropertyName("z"); writer.Write(obj.transform.rotation.eulerAngles.z.ToString("F5")); writer.WriteObjectEnd(); writer.WriteArrayEnd(); writer.WritePropertyName("scale"); writer.WriteArrayStart (); writer.WriteObjectStart(); writer.WritePropertyName("x"); writer.Write(obj.transform.localScale.x.ToString("F5")); writer.WritePropertyName("y"); writer.Write(obj.transform.localScale.y.ToString("F5")); writer.WritePropertyName("z"); writer.Write(obj.transform.localScale.z.ToString("F5")); writer.WriteObjectEnd(); writer.WriteArrayEnd(); writer.WriteObjectEnd(); } } writer.WriteArrayEnd();//end gameObjects writer.WriteObjectEnd();//end name writer.WriteObjectEnd();//end scene } } writer.WriteArrayEnd();//end scenes writer.WriteObjectEnd ();//end root sw.WriteLine(sb.ToString()); sw.Close(); sw.Dispose(); AssetDatabase.Refresh(); } //將所有遊戲場景匯出為Binary格式 [MenuItem ("Tool/ExportBinary")] static void ExportBinary () { string filepath = Application.dataPath + @"/StreamingAssets/sceneInfoBinary.txt"; if(File.Exists (filepath)) { File.Delete(filepath); } FileStream fs = new FileStream(filepath, FileMode.Create); BinaryWriter bw = new BinaryWriter(fs); foreach (UnityEditor.EditorBuildSettingsScene s in UnityEditor.EditorBuildSettings.scenes) { if (s.enabled) { string sceneName = s.path; EditorApplication.OpenScene(sceneName); bw.Write(sceneName); foreach (GameObject obj in Object.FindObjectsOfType(typeof(GameObject))) { if (obj.transform.parent == null) { bw.Write(obj.name); //將float轉化為short,這樣傳輸的位元組數就會變少 //接收時將short轉化為float就可以了 bw.Write((short)(obj.transform.position.x * 100)); bw.Write((short)(obj.transform.position.y * 100)); bw.Write((short)(obj.transform.position.z * 100)); bw.Write((short)(obj.transform.rotation.eulerAngles.x * 100)); bw.Write((short)(obj.transform.rotation.eulerAngles.y * 100)); bw.Write((short)(obj.transform.rotation.eulerAngles.z * 100)); bw.Write((short)(obj.transform.localScale.x * 100)); bw.Write((short)(obj.transform.localScale.y * 100)); bw.Write((short)(obj.transform.localScale.z * 100)); } } } } bw.Flush(); bw.Close(); fs.Close(); AssetDatabase.Refresh(); } }
讀取配置檔案:
using UnityEngine; using System.Collections; using System.Xml; using System.IO; //建立一個空物體,然後掛上此指令碼 public class ResolveSceneInfoXML : MonoBehaviour { // Use this for initialization void Start () { //電腦和iphone上的路徑是不一樣的,這裡用標籤判斷一下。 #if UNITY_EDITOR string filepath = Application.dataPath +"/StreamingAssets"+"/sceneInfoXML.xml"; #elif UNITY_IPHONE string filepath = Application.dataPath +"/Raw"+"/sceneInfoXML.xml"; #endif //如果檔案存在話開始解析。 if(File.Exists (filepath)) { XmlDocument xmlDoc = new XmlDocument(); xmlDoc.Load(filepath); XmlNodeList nodeList = xmlDoc.SelectSingleNode("scenes").ChildNodes; foreach(XmlElement scene in nodeList) { //因為我的XML是把所有遊戲物件全部匯出, 所以這裡判斷一下只解析需要的場景中的遊戲物件 //JSON和它的原理類似 if(!scene.GetAttribute("name").Equals("Assets/s1.unity")) { continue; } //prefab放置在Resources/Prefab資料夾下 //遍歷所有gameobject foreach(XmlElement gameObject in scene.ChildNodes) { string asset = "Prefab/" + gameObject.GetAttribute("name"); Vector3 pos = Vector3.zero; Vector3 rot = Vector3.zero; Vector3 sca = Vector3.zero; XmlNode transform = gameObject.SelectSingleNode("transform"); foreach(XmlElement prs in transform.ChildNodes) { if(prs.Name == "position") { foreach(XmlElement position in prs.ChildNodes) { switch(position.Name) { case "x": pos.x = float.Parse(position.InnerText); break; case "y": pos.y = float.Parse(position.InnerText); break; case "z": pos.z = float.Parse(position.InnerText); break; } } }else if(prs.Name == "rotation") { foreach(XmlElement rotation in prs.ChildNodes) { switch(rotation.Name) { case "x": rot.x = float.Parse(rotation.InnerText); break; case "y": rot.y = float.Parse(rotation.InnerText); break; case "z": rot.z = float.Parse(rotation.InnerText); break; } } }else if(prs.Name == "scale") { foreach(XmlElement scale in prs.ChildNodes) { switch(scale.Name) { case "x": sca.x = float.Parse(scale.InnerText); break; case "y": sca.y = float.Parse(scale.InnerText); break; case "z": sca.z = float.Parse(scale.InnerText); break; } } } } //拿到 旋轉 縮放 平移 以後克隆新遊戲物件 GameObject ob = (GameObject)Instantiate(Resources.Load(asset),pos,Quaternion.Euler(rot)); ob.transform.localScale = sca; } } } } }
using UnityEngine; using System.Collections; using System.IO; using LitJson; public class ResolveSceneInfoJSON : MonoBehaviour { // Use this for initialization void Start () { #if UNITY_EDITOR string filepath = Application.dataPath +"/StreamingAssets"+"/sceneInfoJSON.txt"; #elif UNITY_IPHONE string filepath = Application.dataPath +"/Raw"+"/json.txt"; #endif StreamReader sr = File.OpenText(filepath); string strLine = sr.ReadToEnd(); JsonData jd = JsonMapper.ToObject(strLine); JsonData scenes = jd["scenes"]; int i,j; for (i = 0; i < scenes.Count; i++) { JsonData scene = scenes[i]["scene"]; string sceneName = (string)scene["name"]; if(!sceneName.Equals("Assets/s1.unity")) { continue; } JsonData gameObjects = scene["gameObjects"]; for (j = 0; j < gameObjects.Count; j++) { string objectName = (string)gameObjects[j]["name"]; string asset = "Prefab/" + objectName; Vector3 pos = Vector3.zero; Vector3 rot = Vector3.zero; Vector3 sca = Vector3.zero; JsonData position = gameObjects[j]["position"]; JsonData rotation = gameObjects[j]["rotation"]; JsonData scale = gameObjects[j]["scale"]; pos.x = float.Parse((string)position[0]["x"]); pos.y = float.Parse((string)position[0]["y"]); pos.z = float.Parse((string)position[0]["z"]); rot.x = float.Parse((string)rotation[0]["x"]); rot.y = float.Parse((string)rotation[0]["y"]); rot.z = float.Parse((string)rotation[0]["z"]); sca.x = float.Parse((string)scale[0]["x"]); sca.y = float.Parse((string)scale[0]["y"]); sca.z = float.Parse((string)scale[0]["z"]); GameObject go = (GameObject)Instantiate(Resources.Load(asset),pos,Quaternion.Euler(rot)); go.transform.localScale = sca; } } } }
using UnityEngine;
using System.Collections;
using System.IO;
using System.Text;
using System;
public class ResolveSceneInfoBinary : MonoBehaviour {
// Use this for initialization
void Start ()
{
string filepath = Application.dataPath + @"/StreamingAssets/sceneInfoBinary.txt";
if (File.Exists (filepath))
{
FileStream fs = new FileStream (filepath, FileMode.Open);
BinaryReader br = new BinaryReader (fs);
string sceneName = br.ReadString();
while(fs.Position < fs.Length)
{
string objName = br.ReadString();
float px = br.ReadInt16() / 100f;
float py = br.ReadInt16() / 100f;
float pz = br.ReadInt16() / 100f;
float rx = br.ReadInt16() / 100f;
float ry = br.ReadInt16() / 100f;
float rz = br.ReadInt16() / 100f;
float sx = br.ReadInt16() / 100f;
float sy = br.ReadInt16() / 100f;
float sz = br.ReadInt16() / 100f;
string asset = "Prefab/" + objName;
Vector3 pos = new Vector3 (px,py,pz);
Vector3 rot = new Vector3(rx,ry,rz);
Vector3 sca = new Vector3(sx,sy,sz);
GameObject go = (GameObject)Instantiate(Resources.Load(asset),pos,Quaternion.Euler(rot));
go.transform.localScale = sca;
}
}
}
}
上面使用的是Resources.Load,下面就使用AssetBundle吧!這裡本人使用的是unity5的打包系統,先從本地伺服器下載AB包到本地,再載入。
測試場景很簡單,AB包有六個:總的AB包,3個物體的包,cube的材質,材質的紋理,下載後就把檔案放在Application.streamingAssetsPath下,載入時就載入camera、cube和燈光。伺服器是tomcat伺服器,當然也可以跳過下載的過程,直接本地載入AB包。要注意的是,測試最好使用new WWW,而不是WWW.LoadFromCacheOrDownload,因為LoadFromCacheOrDownload需要提供一個版本號,如果不更改版本號就不會下載新的AB包了。
建立AB:
using UnityEngine;
using System.Collections;
using UnityEditor;
/*
* 注意:
* 1.prefab的名稱以及生成的AB包的名稱最好不要帶空格,否則從伺服器下載AB包時,下載到本地的AB包可能為空。
* 2.prefab的名稱最好全部小寫,因為在設定AB包的名稱時,unity會將設定的名稱全部改為小寫。
* 這樣prefab的名稱就可以與AB包的名稱一一對應了
*/
public class CreateAssetBundle : Editor {
[MenuItem("Tool/SetFileBundleName")]
static void SetBundleName()
{
#region 設定資源的AssetBundle的名稱和副檔名
UnityEngine.Object[] selects = Selection.objects;
foreach (UnityEngine.Object selected in selects)
{
string path = AssetDatabase.GetAssetPath(selected);
AssetImporter asset = AssetImporter.GetAtPath(path);
asset.assetBundleName = selected.name; //設定Bundle檔案的名稱
asset.assetBundleVariant = "unity3d";//設定Bundle檔案的副檔名
asset.SaveAndReimport();
}
AssetDatabase .Refresh();
#endregion
}
[MenuItem("Tool/BuildAll")]
static void Build()
{
BuildPipeline.BuildAssetBundles(Application.dataPath + "/AB");
}
}
下載AB:
using UnityEngine;
using System.Collections;
using System.IO;
using System.Text;
[RequireComponent(typeof(LoadAssetbBundle))]
public class DownLoadAssetbBundle : MonoBehaviour {
//要下載的AB包
string[] s = new string[]{"AB","directionallight","cube","camera","m1","t1"};
// Use this for initialization
void Start ()
{
//print (Application.dataPath);
//print (Application.persistentDataPath);
//print (Application.streamingAssetsPath);
//print (Application.temporaryCachePath);
StartCoroutine (DownLoad(s));
}
IEnumerator DownLoad(string[] assetBundleNames)
{
FileStream fs = null;
for (int i = 0; i < assetBundleNames.Length; i++)
{
string savePath;
string url;
if(assetBundleNames[i].Equals("AB"))
{
savePath = Application.streamingAssetsPath + "/" + assetBundleNames[i];
url = "http://172.25.225.24:8080/UpdateServer" + "/" + assetBundleNames[i];
}
else
{
savePath = Application.streamingAssetsPath + "/" + assetBundleNames[i] + ".unity3d";
url = "http://172.25.225.24:8080/UpdateServer" + "/" + assetBundleNames[i] + ".unity3d";
}
//如果不存在則下載AB,並儲存到指定資料夾中
if (!File.Exists (savePath))
{
fs = new FileStream(savePath, FileMode.Create);
WWW www = new WWW (url);
yield return www;
byte[] bytes = www.bytes;
print(assetBundleNames[i] + " " + bytes.Length);
fs.Write(bytes,0,bytes.Length);
}
}
if (fs != null)
{
fs.Close ();
fs.Dispose ();
}
//載入AB
LoadAssetbBundle lab = GetComponent<LoadAssetbBundle> ();
if (lab != null)
{
lab.StartLoad("file://" + Application.streamingAssetsPath + "/",
new string[] { "camera", "cube", "directionallight"});//要載入的AB包
}
}
}
載入AB:
using UnityEngine;
using System.Collections;
public class LoadAssetbBundle : MonoBehaviour {
public void StartLoad(string assetBundlePath, string[] assetBundleNames)
{
StartCoroutine(LoadGo(assetBundlePath,assetBundleNames));
}
/*
* assetBundlePath:Assetbundle的資料夾,末尾要加"/"
* assetBundleNames:要載入的AssetBundle的名字,不用加字尾
* 注意:www需要加字尾,而AssetBundle.LoadAsset不需要
* */
IEnumerator LoadGo(string assetBundlePath, string[] assetBundleNames)
{
AssetBundleManifest manifest = null;
//首先載入Manifest檔案;
WWW mwww = new WWW(assetBundlePath + "AB");
yield return mwww;
if (!string.IsNullOrEmpty (mwww.error))
{
Debug.LogError (mwww.error);
}
else
{
AssetBundle ab = mwww.assetBundle;
manifest = (AssetBundleManifest)ab.LoadAsset ("AssetBundleManifest");
ab.Unload (false);
}
for (int i = 0; i < assetBundleNames.Length; i++)
{
//獲取依賴檔案列表;
string[] depends = manifest.GetAllDependencies(assetBundleNames[i] + ".unity3d");
AssetBundle[] dependsAssetBundle = new AssetBundle[depends.Length];
for(int index = 0;index < depends.Length;index++)
{
//載入所有的依賴檔案;
WWW dwww = new WWW(assetBundlePath + depends[index]);
yield return dwww;
dependsAssetBundle[index] = dwww.assetBundle;
}
//載入我們需要的檔案;
WWW gowww = new WWW(assetBundlePath + assetBundleNames[i] + ".unity3d");
yield return gowww;
if(!string.IsNullOrEmpty(gowww.error))
{
Debug.LogError(gowww.error);
}
else
{
AssetBundle goAB = gowww.assetBundle;
GameObject go = goAB.LoadAsset(assetBundleNames[i]) as GameObject;
if(go != null)
{
Instantiate(go);
}
goAB.Unload(false);
}
//解除安裝依賴檔案的包
for(int index = 0;index < depends.Length;index++)
{
dependsAssetBundle[index].Unload(false);
}
}
}
}