国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Unity 熱更新方案和流程

這篇具有很好參考價值的文章主要介紹了Unity 熱更新方案和流程。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

unity資源更新,游戲開發(fā),unity,lua,游戲引擎,Powered by 金山文檔

在開發(fā)商業(yè)游戲時,熱更新是一個很重要的模塊,這里講的熱更新不是指僅僅修復Bug,而是進行游戲功能的更新。簡單來講,就是啟動游戲后,跑個條,下載資源和代碼,然后再進入游戲。本篇博客所寫的內(nèi)容并不是最優(yōu)的解,只是完成了熱更新這個事情而已,具體使用還需要使用者根據(jù)自己的項目來具體來看。

這里采用的方案是使用 AssetBundle 和 xLua。使用 AssetBundle 是為了資源的完全自主控制。而整個游戲的邏輯部分,則使用 xLua 來實現(xiàn)。當然,C# 的代碼不可能一點沒有,只是一些核心的功能模塊,一般寫好后就不會改變的東西,或者對性能要求很高的東西,放在 C# 就可以。

整個功能分為編輯器部分,和運行時部分。編輯器部分就是編 Bundle,生成版本文件等。而運行時部分就是從 CDN 下載版本文件,對比版本號及本地資源是否有要更新的,如果有,則更新,更新完后進入游戲。沒有,則直接進入游戲。

編輯器部分

編輯器部分主要就是生成 Bundle 文件,首先,我是按目錄來劃分 Bundle 的,任何一個目錄下的文件(不包括子目錄)則會打成一個 Bundle。例如下面的目錄結(jié)構(gòu)

Res/
    - ConfigBytes/
    - UI/
    - LuaScripts/
    - Data/
        - ItemsData/
        - CharactersData/

首先 Res 目錄是資源的主目錄,下面有各種子目錄(Res 目錄下不會有需要打 Bundle 的文件)。ConfigBytes 目錄下的文件,會打成一個 Bundle。UI 目錄下的文件會打成一個 Bundle。LuaScripts 目錄下的文件會打成一個 Bundle。Data 目錄下的 ItemsData 目錄中的文件會打成一個 Bundle,Data 目錄下的 CharactersData 目錄會打成一個Bundle。如果 Data 目錄下存在文件(非目錄),則這些文件會打成一個 Bundle。簡單來講,就是會按文件夾來決定哪些文件打成一個Bundle,檢查的時候只會取一個文件夾下的文件,而不會遞歸取這個文件夾下的子目錄。

LuaScripts 目錄在開發(fā)時會放在 Assets 目錄外面,與其同級,在編 Bundle 時,會拷貝 LuaScripts 目錄及下面的所有文件,按原有目錄結(jié)構(gòu),拷貝到 Res 目錄中,并且會將每一個 xxx.lua 文件的擴展名改為 xxx.txt。因為 .lua 在 Unity 中識別不了。

打 Bundle 的腳本,會記錄每一個資源,所在的 Bundle 名稱,最后會生成一個 index.json 文件,這份索引文件記錄的,就是每一個資源的加載路徑,和所在的 Bundle 名。

Bundle 輸出后,會生成一個 version.json 的文件,這個文件,記錄了每一個 Bundle 的名字,MD5 和 文件大小。而熱更新對比一個文件是否需要更新,就是判斷遠程文件的 MD5 與本地文件的 MD5 是否相同,如果不想同,則需要更新遠程文件。

以上就是編輯器所做的事情,總結(jié)一下就是

  1. 拷貝 LuaScripts 到 Assets/Res/ 目錄中

  1. 將 Res/ 目錄下的文件按目錄進行 Bundle 生成

  1. 生成資源索引文件(index.json),并且將這個文件也打成 Bundle

  1. 根據(jù)生成的 Bundle,生成 version.json 文件

  1. 拷貝上面的 Bundle 及 version.json 文件到 StreamingAssets 目錄

  1. 將上面的 Bundle 及 version.json 文件上傳到遠程服務器或 CDN

第 5 步,之所以是要拷貝到 StreamingAssets 目錄,是為了用戶在第一次安裝游戲時,運行時邏輯會先判斷本地有沒有資源,如果沒有,或者版本號小于 StreamingAssets 中的 version.json 文件版本號,則需要將 StreamingAssets 目錄下的 Bundle 及 version.json 文件拷貝到 Persistent 目錄下,這樣就不用第一次安裝游戲,還需要跑條更新資源了。當然,雖然有了這個過程,拷貝完后,正常的版本檢查還是會做。

以上就是編輯器下做的事情,下面為運行時的流程

運行時資源更新部分

版本檢查及更新邏輯,可以放在一個獨立的場景中去進行,一旦更新完成后,就直接跳轉(zhuǎn)場景到游戲啟動場景,這個邏輯比較簡單清晰,不容易出錯。

在游戲啟動時,首先會去遠程拉取 version.json 文件,然后根據(jù) version.json 文件中的版本號與本地 version.json 文件中的版本號進行對比。如果不一樣,則需要根據(jù) version.json 文件中的 Bundle 信息,看一下哪一些 Bundle 需要更新,找到需要更新的 Bundle 后,依次下載始可。因為 version.json 中包含了每一個 Bundle 的文件大小,所以這里的下載進度條的進度,也是可以計算出來的。

在對比版本號時,需要根據(jù)自己游戲的實際進行,分為大版本號和熱更新版本號,如果大版本號不一樣,則直接不用判斷 Bundle 了,讓用戶進不了游戲,彈窗告訴用戶去下載最新的安裝包即可。如果大版本一樣,則進行熱更新的邏輯。這里的大版本判斷,不當要根據(jù) version.json 文件里的版本號來判斷,最好是根據(jù)包里代碼中或者包里的某個配置文件中的版本號來判斷,因為 version.json 文件是在手機的可讀寫目錄,對于 Android 來說,是很容易隨意找到這個文件,然后改掉的,從而繞過熱更新。

在 Bundle 都下載完后,需要將遠程的 version.json 文件寫入本地,覆蓋本地的 version.json 文件。

最后,再進行一步本地資源校對,就是計算每一個本地 Bundle MD5,是否與 version.json 中的 MD5 一致,如果不一致,則需要彈窗告訴玩家需要手動修復資源,或者直接自動下載覆蓋。手動修復也就是從遠程重新下載資源進行覆蓋。

最后一步資源校對通過后,則跳到游戲邏輯開始場景。

我是將版本檢查和更新的邏輯放在了 C# 實現(xiàn)的,當然,也可以放在 lua 來實現(xiàn),不過需要在更新完后,重新創(chuàng)建整個 lua 環(huán)境,以保證使用了最新的資源。

運行時資源加載部分

資源的加載,可以使用一個資源管理器腳本來實現(xiàn)。資源管理器在初始化時首先要加載 Bundle 的 AssetBundleManifest 信息,這個資源里記錄了各個 Bundle 與其他 Bundle 的依賴關(guān)系。然后加載 index 文件,也就是我們一開始生成的資源索引文件,這樣才能知道哪一個資源,在哪一個 Bundle 里。當要加載一個資源時,傳入資源加載路徑,首先會根據(jù) index 文件中的信息,找到這個 Bundle,然后從 Manifest 信息中,讀取這個 Bundle 的依賴 Bundle,如果有,則先加載依賴,最后,再加載當前 Bundle。Bundle加載完后,從 Bundle 中加載資源。

具體代碼(僅供參考)

AssetBundleBuilder.cs 編輯器下編 Bundle 的代碼

using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEditor.Build;
using System;
using System.IO;
using System.Text;
using System.Linq;
using System.Xml.Linq;
using System.Security.Cryptography;
using UnityEditor.Build.Reporting;

// 注意:BundleCombineConfig.json 中的配置,目錄最后!不要!加上 '/'publicclassAssetBundleBuilder{
privatestaticstring RES_TO_BUILD_PATH = "Assets/Res/";
privatestaticstring MANIFEST_FILES_PATH = string.Format("{0}/../BundleManifest/", Application.dataPath);
privatestatic StringBuilder IndexFileContent = null;
privatestatic StringBuilder VersionFileContent = null;
privatestatic MD5 md5 = null;
privatestatic BuildAssetBundleOptions BuildOption = BuildAssetBundleOptions.ChunkBasedCompression |
                                                        BuildAssetBundleOptions.ForceRebuildAssetBundle;

privatestatic BundleCombineConfig combineConfig = null;
privatestatic Dictionary<string, int> combinePathDict = null;

privatestaticstring version = "0.0.0";
privatestaticbool copyToStreaming = false;

private static voidInitBuilder()    {
        IndexFileContent = new StringBuilder();
        VersionFileContent = new StringBuilder();
        md5 = new MD5CryptoServiceProvider();
        combineConfig = null;
        combinePathDict = new Dictionary<string, int>();
    }

private static voidWriteIndexFile(string key, string value)    {
        IndexFileContent.AppendFormat("{0}:{1}", key, value);
        IndexFileContent.AppendLine();
    }

private static voidWriteVersionFile(string key, string value1, long value2)    {
        VersionFileContent.AppendFormat("{0}:{1}:{2}", key, value1, value2);
        VersionFileContent.AppendLine();
    }

private static longGetFileSize(string fileName)    {
try        {
            FileInfo fileInfo = new FileInfo(fileName);
return fileInfo.Length;
        }
catch (Exception ex)
        {
thrownew Exception("GetFileSize() fail, error:" + ex.Message);
        }
    }

private static stringGetMD5(byte[] retVal)    {
        StringBuilder sb = new StringBuilder();
for (int i = 0; i < retVal.Length; i++)
        {
            sb.Append(retVal[i].ToString("x2"));
        }
return sb.ToString();
    }

private static stringGetMD5HashFromFile(string fileName)    {
try        {
            FileStream file = new FileStream(fileName, FileMode.Open);
byte[] retVal = md5.ComputeHash(file);
            file.Close();

return GetMD5(retVal);
        }
catch (Exception ex)
        {
thrownew Exception("GetMD5HashFromFile() fail, error:" + ex.Message);
        }
    }

static stringGetBundleName(string path)    {
byte[] md5Byte = md5.ComputeHash(Encoding.Default.GetBytes(path));
string str = GetMD5(md5Byte) + ".assetbundle";
return str;
    }
privateclassBuildBundleData    {
private AssetBundleBuild build = new AssetBundleBuild();
privateList<string> assets = new List<string>();
privateList<string> addresses = new List<string>();

publicBuildBundleData(string bundleName)        {
            build.assetBundleName = bundleName;
        }

public voidAddAsset(string filePath)        {
string addressableName = GetAddressableName(filePath);
            assets.Add(filePath);
            addresses.Add(addressableName);
            WriteIndexFile(addressableName, build.assetBundleName);
        }

public AssetBundleBuild Gen()        {
            build.assetNames = assets.ToArray();
            build.addressableNames = addresses.ToArray();
return build;
        }
    }

private static stringGetAddressableName(string file_path)    {
string addressable_name = file_path;
        addressable_name = addressable_name.Replace(RES_TO_BUILD_PATH, "");
int dot_pos = addressable_name.LastIndexOf('.');
if (dot_pos != -1)
        {
int count = addressable_name.Length - dot_pos;
            addressable_name = addressable_name.Remove(dot_pos, count);
        }
return addressable_name;
    }

private static string[] GetTopDirs(string rPath)    {
return Directory.GetDirectories(rPath, "*", SearchOption.TopDirectoryOnly);
    }

private static voidCopyLuaDir()    {
// Copy Luastring luaOutPath = Application.dataPath + "/../LuaScripts";
string luaInPath = Application.dataPath + "/Res/LuaScripts";

        DeleteLuaDir();

        MoeUtils.DirectoryCopy(luaOutPath, luaInPath, true, ".txt");
        AssetDatabase.Refresh();
    }

private static voidDeleteLuaDir()    {
string luaInPath = Application.dataPath + "/Res/LuaScripts";

if (Directory.Exists(luaInPath))
        {
            Directory.Delete(luaInPath, true);
        }
    }

public static voidBuildBundleWithVersion(string v, bool copy)    {
        version = v;
        copyToStreaming = copy;
        BuildAssetBundle();
    }

    [MenuItem("Tools/Build Bundles")]private static voidBuildAssetBundle()    {
if (version == "0.0.0")
        {
            Debug.LogErrorFormat("請確認版本號");
return;
        }
        CopyLuaDir();

        InitBuilder();
        LoadBundleCombineConfig();
        Dictionary<string, BuildBundleData> bundleDatas = new Dictionary<string, BuildBundleData>();
        IndexFileContent.Clear();
        VersionFileContent.Clear();

        List<DirBundleInfo> dirList = new List<DirBundleInfo>();

// ============================        Queue<DirBundleInfo> dirQueue = new Queue<DirBundleInfo>();
        dirQueue.Enqueue(new DirBundleInfo(RES_TO_BUILD_PATH));
while (dirQueue.Count > 0)
        {
            DirBundleInfo rootDirInfo = dirQueue.Dequeue();
if (rootDirInfo.dir != RES_TO_BUILD_PATH)
            {
if (combinePathDict.ContainsKey(rootDirInfo.dir))
                {
                    rootDirInfo.combine2Dir = rootDirInfo.dir;
                }
                dirList.Add(rootDirInfo);
            }

foreach (string subDir inGetTopDirs(rootDirInfo.dir))            {
                DirBundleInfo subDirInfo = new DirBundleInfo(subDir);
                subDirInfo.combine2Dir = rootDirInfo.combine2Dir;
                dirQueue.Enqueue(subDirInfo);

                Debug.LogFormat("Dir: {0}, Combine2Dir: {1}", subDirInfo.dir, subDirInfo.combine2Dir);
            }
        }

foreach (DirBundleInfo dirInfo in dirList)
        {
string[] files = GetFiles(dirInfo.dir, SearchOption.TopDirectoryOnly);
if (files.Length > 0)
            {
                Debug.LogFormat("Dir: {0}, FileCount: {1}", dirInfo.dir, files.Length);
string bundleDirName = dirInfo.BundleDirName;
                BuildBundleData bbData = null;
if (bundleDatas.ContainsKey(bundleDirName))
                {
                    bbData = bundleDatas[bundleDirName];
                }
else                {
                    bbData = new BuildBundleData(GetBundleName(bundleDirName));
                    bundleDatas.Add(bundleDirName, bbData);
                }

foreach (string file in files)
                {
                    bbData.AddAsset(file);
                }
            }
        }

        List<AssetBundleBuild> bundleBuildList = new List<AssetBundleBuild>();
foreach (BuildBundleData data in bundleDatas.Values)
        {
            bundleBuildList.Add(data.Gen());
        }

string index_file_path = string.Format("{0}{1}.txt", RES_TO_BUILD_PATH, "index");
        File.WriteAllText(index_file_path, IndexFileContent.ToString());
        AssetDatabase.ImportAsset(index_file_path);
        AssetDatabase.SaveAssets();
        AssetDatabase.Refresh();

        AssetBundleBuild indexBuild = new AssetBundleBuild();
        indexBuild.assetBundleName = "index";
        indexBuild.assetNames = newstring[] { index_file_path };
        indexBuild.addressableNames = newstring[] { "index" };
        bundleBuildList.Add(indexBuild);
string bundleExportPath = string.Format("{0}/{1}/", Application.dataPath + "/../streaming", "Bundles");
if (Directory.Exists(bundleExportPath))
        {
            Directory.Delete(bundleExportPath, true);
        }
        Directory.CreateDirectory(bundleExportPath);

if (Directory.Exists(MANIFEST_FILES_PATH))
        {
            Directory.Delete(MANIFEST_FILES_PATH, true);
        }
        Directory.CreateDirectory(MANIFEST_FILES_PATH);

        BuildPipeline.BuildAssetBundles(bundleExportPath, bundleBuildList.ToArray(), BuildOption, EditorUserBuildSettings.activeBuildTarget);
        AssetDatabase.Refresh();
        DeleteLuaDir();
        AssetDatabase.Refresh();

// VersionProfile
        List<VersionBundleInfo> versionBundleList = new List<VersionBundleInfo>();
        MoeVersionInfo versionInfo = new MoeVersionInfo();
        versionInfo.version = version;
        versionInfo.asset_date = DateTime.Now.ToString("yyyyMMddHHmm");
string[] ab_files = Directory.GetFiles(bundleExportPath);
foreach (string ab_file in ab_files)
        {
if (Path.GetExtension(ab_file) == ".manifest")
            {
string new_path = ab_file.Replace(bundleExportPath, MANIFEST_FILES_PATH);
                File.Move(ab_file, new_path);
            }
else            {

                Debug.LogFormat("BundleName: {0}", ab_file);
var data = File.ReadAllBytes(ab_file);
using (var abStream = new AssetBundleStream(ab_file, FileMode.Create))
                {
                    abStream.Write(data, 0, data.Length);
                }

string md5 = GetMD5HashFromFile(ab_file);
long size = GetFileSize(ab_file);
string bundleName = string.Format("Bundles/{0}", Path.GetFileName(ab_file));
                VersionBundleInfo bInfo = new VersionBundleInfo();
                bInfo.bundle_name = bundleName;
                bInfo.md5 = md5;
                bInfo.size = size;
                versionBundleList.Add(bInfo);
            }
        }

        versionInfo.bundles = versionBundleList.ToArray();
string versionInfoText = Newtonsoft.Json.JsonConvert.SerializeObject(versionInfo);

        File.WriteAllText(string.Format("{0}/{1}", bundleExportPath, "version.json"), versionInfoText);

if (copyToStreaming)
        {
            CopyBundleToStreaming(bundleExportPath);
        }
        MoveToVersionDir(bundleExportPath, version);
        AssetDatabase.Refresh();
    }

private static voidMoveToVersionDir(string rootBundlePath, string version)    {
string destPath = rootBundlePath + "/" + version;
        Directory.CreateDirectory(destPath);
        destPath += "/Bundles";
        Directory.CreateDirectory(destPath);

string[] files = GetFiles(rootBundlePath, SearchOption.TopDirectoryOnly);
foreach (string file in files)
        {
string fileName = System.IO.Path.GetFileName(file);
string destFilePath = destPath + "/" + fileName;
            File.Move(file, destFilePath);
        }
    }

private static voidCopyBundleToStreaming(string bundleExportPath)    {
string destPath = Application.streamingAssetsPath + "/Bundles";
if (Directory.Exists(destPath))
        {
            Directory.Delete(destPath, true);
        }

        MoeUtils.DirectoryCopy(bundleExportPath, destPath, true);
    }

private static string[] GetFiles(string path, SearchOption so)    {
string[] files = Directory.GetFiles(path, "*", so);
        List<string> fileList = new List<string>();
foreach (string file in files)
        {
string ext = Path.GetExtension(file);
if (ext == ".meta" || ext == ".DS_Store")
            {
continue;
            }
            fileList.Add(file);
        }

return fileList.ToArray();
    }

classDirBundleInfo    {
publicstring dir;
publicstring combine2Dir;

publicbool IsCombine
        {
get            {
return !string.IsNullOrEmpty(combine2Dir);
            }
        }

publicstring BundleDirName
        {
get            {
if (IsCombine)
                {
return combine2Dir;
                }
else                {
return dir;
                }
            }
        }

publicDirBundleInfo(string dir, string combine2Dir = null)        {
this.dir = dir;
this.combine2Dir = combine2Dir;
        }

    }

classBundleCombineConfig    {
publicstring[] combieDirs;
    }

private static voidLoadBundleCombineConfig()    {
string path = Application.dataPath + RES_TO_BUILD_PATH.Replace("Assets", "") + "BundleCombineConfig.json";
if (File.Exists(path))
        {
string text = File.ReadAllText(path);
if (!string.IsNullOrEmpty(text))
            {
                combineConfig = Newtonsoft.Json.JsonConvert.DeserializeObject<BundleCombineConfig>(text);
if (combineConfig != null)
                {
                    Debug.LogFormat("Bundle合并配置成功!");
foreach (string cPath in combineConfig.combieDirs)
                    {
if (!combinePathDict.ContainsKey(cPath))
                        {
                            combinePathDict.Add(cPath, 0);
                        }
                    }
                }
            }
        }
    }
}

MoeVersionManager.cs 資源版本檢查及 Bundle 更新邏輯

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using BestHTTP;
using System;

publicclassMoeVersionManager : MoeSingleton<MoeVersionManager>
{
conststring REMOTE_URL = "這里改成自己的CDN域名或IP";
staticstring VERSION_FILE_DIR;
staticstring VERSION_FILE_PATH;
staticstring IN_VERSION_FILE_PATH;

private MoeVersionInfo currVersionInfo = null;
private MoeVersionInfo remoteVersionInfo = null;
private UpdateInfo updateInfo = null;

privatestatic OnVersionStateParam versionStateParam = new OnVersionStateParam();
privatestatic OnUpdateProgressParam updateProgressParam = new OnUpdateProgressParam();
privatestatic OnVersionMsgBoxParam msgBoxParam = new OnVersionMsgBoxParam();

privateenum EnProcessType
    {
        Normal,
        Fix,
    }

private Action<EnProcessType> actionTryUnCompress = null;
private Action<EnProcessType> actionUpdateVersionFile = null;
private Action<EnProcessType> actionUpdateBundles = null;
private Action<EnProcessType> actionCheckAssets = null;
private Action<EnProcessType> actionForceUpdateVersionFile = null;



protected override voidInitOnCreate()    {
        VERSION_FILE_DIR = Application.persistentDataPath + "/Bundles/";
        VERSION_FILE_PATH = Application.persistentDataPath + "/Bundles/version.json";
        IN_VERSION_FILE_PATH = Application.streamingAssetsPath + "/Bundles/version.json";
        Debug.LogFormat("{0}", VERSION_FILE_PATH);
        InitProcessChain();
        StartNormalProcess();
    }


private voidInitProcessChain()    {
this.actionTryUnCompress = (EnProcessType param) =>
        {
            Debug.LogFormat("Action>>> 解壓: {0}", param);
this.currVersionInfo = LoadVersionInfo(VERSION_FILE_PATH);
// if (!CheckBundleCorrect())if (currVersionInfo == null)
            {
                UpdateUIState("正在解壓資源");
                UnCompressBundle();
this.currVersionInfo = LoadVersionInfo(VERSION_FILE_PATH);
            }
else            {
// 判斷是不是更新包,也就是StreamingAssets里的版本是否比Persistent版本高,如果高的話,再次解壓Bundle                MoeVersionInfo inVersionInfo = LoadVersionInfo(IN_VERSION_FILE_PATH);
if (inVersionInfo != null)
                {
int[] inVersionDigit = inVersionInfo.GetVersionDigitArray();
int[] currVersionDigit = this.currVersionInfo.GetVersionDigitArray();
// if (inVersionInfo.GetVersionLong() > this.currVersionInfo.GetVersionLong())if (inVersionDigit[0] > currVersionDigit[0] ||
                       inVersionDigit[1] > currVersionDigit[1] ||
                       inVersionDigit[2] > currVersionDigit[2])
                    {
// 包里的版本比Persistent的版本高,可能玩家進行了大版本更新,重新解壓                        Debug.LogFormat("包里的Bundle版本 > Persistent Bundle 版本,重新解壓");
                        UpdateUIState("正在解壓資源");
                        UnCompressBundle();
this.currVersionInfo = LoadVersionInfo(VERSION_FILE_PATH);
                    }
else                    {
                        Debug.LogFormat("包里Bundle版本 <= Persistent Bundle版本,無需解壓~");
                    }
                }
else                {
                    Debug.LogErrorFormat("邏輯錯誤,從StreamingAssets 中加載VersionInfo文件失敗");
                }
            }
        };

this.actionUpdateVersionFile = (EnProcessType param) =>
        {
            Debug.LogFormat("Action>>> 獲取遠程版本文件: {0}", param);
            StartCoroutine(TryUpdateVersion((bool ok, bool majorUpdate) =>
            {
if (ok)
                {
if (majorUpdate)
                    {
// 調(diào)用商店                        OnMsgBox("新的大版本已更新,請下載最新安裝包!", "確定", () =>
                        {
                            JumpToDownloadMarket();
                        });
                    }
else                    {
// 成功了,接下來更新Bundlethis.actionUpdateBundles?.Invoke(param);
                    }
                }
else                {
// 版本文件更新失敗,彈窗詢問                    OnMsgBox("版本信息獲取失敗,請檢查網(wǎng)絡(luò)連接!", "重試", () =>
                    {
this.actionUpdateVersionFile?.Invoke(param);
                    });
                }
            }));
        };

this.actionForceUpdateVersionFile = (EnProcessType param) =>
        {
            Debug.LogFormat("Action>>> 強制獲取遠程版本文件: {0}", param);
            TryDeleteBundleDir();
            TryCreateBundleDir();
            StartCoroutine(TryUpdateVersion((bool ok, bool majorUpdate) =>
            {
if (ok)
                {
if (majorUpdate)
                    {
// 調(diào)用商店                        OnMsgBox("新的大版本已更新,請下載最新安裝包!", "確定", () =>
                        {
                            JumpToDownloadMarket();
                        });
                    }
else                    {
// 成功了,接下來更新Bundlethis.actionUpdateBundles?.Invoke(param);
                    }
                }
else                {
// 版本文件更新失敗,彈窗詢問                    OnMsgBox("版本信息獲取失敗,請檢查網(wǎng)絡(luò)連接!", "重試", () =>
                    {
this.actionForceUpdateVersionFile?.Invoke(param);
                    });
                }
            }, true));
        };

this.actionUpdateBundles = (EnProcessType param) =>
        {
            Debug.LogFormat("Action>>> 更新Bundle: {0}", param);
            StartCoroutine(TryUpdateBundle((bool ok) =>
            {
if (ok)
                {
// 成功了,接下來檢查資源,this.actionCheckAssets?.Invoke(param);
                }
else                {
                    OnMsgBox("資源下載失敗,請檢查網(wǎng)絡(luò)連接!", "重試", () =>
                    {
this.actionUpdateBundles(param);
                    });
                }
            }));
        };

this.actionCheckAssets = (EnProcessType param) =>
        {
            Debug.LogFormat("Action>>> 校對資源: {0}", param);
if (!CheckBundleCorrect())
            {
// 更新完了,本地Bundle還是不對                Debug.LogFormat("更新完Bundle后,發(fā)現(xiàn)文件不對");
if (param == EnProcessType.Normal)
                {
                    OnMsgBox("資源有錯誤,請修復客戶端!", "修復", () =>
                    {
this.actionForceUpdateVersionFile?.Invoke(EnProcessType.Fix);
                    });
                }
else                {
                    OnMsgBox("客戶端修復失敗,請重新下載安裝包!", "確定", () =>
                    {
                        JumpToDownloadMarket();
                    });
                }
            }
else            {
                UpdateUIState("進入游戲");
                MoeEventManager.Inst.SendEvent(EventID.Event_OnUpdateEnd);
            }
        };
    }



// 跳轉(zhuǎn)到下載商店private voidJumpToDownloadMarket()    {
        Application.OpenURL("https://taptap.com");
    }

private voidStartNormalProcess()    {
        TryCreateBundleDir();
this.actionTryUnCompress?.Invoke(EnProcessType.Normal);
this.actionUpdateVersionFile?.Invoke(EnProcessType.Normal);
    }

private voidStartFixProcess()    {
this.actionForceUpdateVersionFile(EnProcessType.Fix);
    }


private MoeVersionInfo LoadVersionInfo(string path)    {
        Debug.LogFormat("加載 Version 文件: {0}", path);
try        {
if (System.IO.File.Exists(path))
            {
string text = System.IO.File.ReadAllText(path);
if (!string.IsNullOrEmpty(text))
                {
                    MoeVersionInfo vInfo = Newtonsoft.Json.JsonConvert.DeserializeObject<MoeVersionInfo>(text);
if (vInfo != null)
                    {
                        Debug.LogFormat("Version 信息加載成功: {0}", vInfo.version);
return vInfo;
                    }
                }
else                {
                    Debug.LogFormat("Version 文件內(nèi)容為空");
                }
            }
else            {
                Debug.LogFormat("Version 文件不存在");
            }
        }
catch (System.Exception e)
        {
            Debug.LogErrorFormat("讀取Version文件出錯: {0}", e.ToString());
        }

returnnull;
    }

///<summary>/// 從 StreamingAssets 里將Bundle拷貝到 Persistent 目錄里 ///</summary>private voidUnCompressBundle()    {
        TryDeleteBundleDir();
        TryCreateBundleDir();
        Debug.LogFormat("嘗試從 Steaming 拷貝Bundle 到 Persistent");
try        {
if (System.IO.File.Exists(IN_VERSION_FILE_PATH))
            {
string text = System.IO.File.ReadAllText(IN_VERSION_FILE_PATH);
                Debug.LogFormat("Text: {0}", text);
                MoeVersionInfo inVersionInfo = Newtonsoft.Json.JsonConvert.DeserializeObject<MoeVersionInfo>(text);
if (inVersionInfo != null)
                {
// 拷貝 Bundleforeach (VersionBundleInfo bundleInfo in inVersionInfo.bundles)
                    {
string srcFilePath = string.Format("{0}/{1}", Application.streamingAssetsPath, bundleInfo.bundle_name);
string destFilePath = string.Format("{0}/{1}", Application.persistentDataPath, bundleInfo.bundle_name);
                        Debug.LogFormat("拷貝Bundle, {0} -> {1}", srcFilePath, destFilePath);
                        System.IO.File.Copy(srcFilePath, destFilePath, true);
                    }

// 拷貝 Version文件                    System.IO.File.Copy(IN_VERSION_FILE_PATH, VERSION_FILE_PATH, true);
                }
            }
else            {
                Debug.LogErrorFormat("解壓失敗,StreamingAssets 中沒有 Version 文件");
            }
        }
catch (System.Exception e)
        {
            Debug.LogErrorFormat("Bundle拷貝出錯! {0}", e.ToString());
        }
    }

public voidTryCreateBundleDir()    {
if (!System.IO.Directory.Exists(VERSION_FILE_DIR))
        {
            Debug.LogFormat("創(chuàng)建 Persistent Bundle 目錄");
            System.IO.Directory.CreateDirectory(VERSION_FILE_DIR);
        }
else        {
            Debug.LogFormat("Persistent Bundle 目錄已存在,不需要創(chuàng)建");
        }
    }

public voidTryDeleteBundleDir()    {
if (System.IO.Directory.Exists(VERSION_FILE_DIR))
        {
            System.IO.Directory.Delete(VERSION_FILE_DIR, true);
        }
    }

private stringGetLocalBundleMD5(string bundle_name)    {
string bundleFilePath = string.Format("{0}/{1}", Application.persistentDataPath, bundle_name);
if (System.IO.File.Exists(bundleFilePath))
        {
string md5 = MoeUtils.GetMD5HashFromFile(bundleFilePath);
return md5;
        }

returnnull;
    }

///<summary>/// 檢查當前的Bundle是否正確 ///</summary>///<returns></returns>public boolCheckBundleCorrect()    {
if (currVersionInfo != null)
        {
foreach (VersionBundleInfo bundleInfo in currVersionInfo.bundles)
            {
string bundleFilePath = string.Format("{0}/{1}", Application.persistentDataPath, bundleInfo.bundle_name);
bool matched = false;

if (GetLocalBundleMD5(bundleInfo.bundle_name) == bundleInfo.md5)
                {
                    matched = true;
                }
else                {
                    Debug.LogErrorFormat("MD5 不匹配: {0}, FileMD5: {1}, bInfoMD5: {2}", bundleInfo.bundle_name, GetLocalBundleMD5(bundleInfo.bundle_name), bundleInfo.md5);
                }

if (!matched)
                {
returnfalse;
                }
            }

            Debug.LogFormat("本地Bundle文件檢完全正確");
returntrue;
        }
else        {
returnfalse;
        }
    }

///<summary>//</summary>///<param name="callback"><是否成功,是否是強更></param>///<param name="force"></param>///<returns></returns>private IEnumerator TryUpdateVersion(System.Action<bool, bool> callback, bool force = false)    {
        UpdateUIState("正在檢查更新");
this.remoteVersionInfo = null;
this.updateInfo = null;
string remoteVersionUrl = REMOTE_URL + "/fishing/version.json";
        Debug.LogFormat("開始下載遠程 Version 文件: {0}", remoteVersionUrl);
        HTTPRequest request = new HTTPRequest(new System.Uri(remoteVersionUrl), false, true, null).Send();

while (request.State < HTTPRequestStates.Finished)
        {
yield return newWaitForSeconds(0.1f);
        }

if (request.State == HTTPRequestStates.Finished &&
         request.Response.IsSuccess)
        {
string remoteVersionText = request.Response.DataAsText;
if (!string.IsNullOrEmpty(remoteVersionText))
            {
                MoeVersionInfo remoteVersionInfo = Newtonsoft.Json.JsonConvert.DeserializeObject<MoeVersionInfo>(remoteVersionText);
if (remoteVersionInfo != null)
                {
                    Debug.LogFormat("遠程 Version 文件解析成功, Version: {0}", remoteVersionInfo.version);
// 判斷是否要更新
int appMajorVersion = AppConfig.Inst.GetMajorVersion();
// 判斷是否要強更int remoteMajor = remoteVersionInfo.GetMajorVersion();
if (remoteMajor > appMajorVersion)
                    {
// 這是一個需要強更的版本,需要提示用戶去商店下載                        Debug.LogFormat("發(fā)現(xiàn)強更版本,需要重新下包,進行大版本更新!");
                        callback?.Invoke(true, true);
                        callback = null;

                        UpdateUIState("新的大版本已更新,請下載最新安裝包!");
                    }
else                    {
// 強制修復if (force)
                        {
this.remoteVersionInfo = remoteVersionInfo;
                            List<VersionBundleInfo> updateBundleList = new List<VersionBundleInfo>();
                            updateBundleList.AddRange(remoteVersionInfo.bundles);
// 有需要更新的包this.updateInfo = new UpdateInfo();
this.updateInfo.remoteVersionInfo = remoteVersionInfo;
this.updateInfo.updateBundleList = updateBundleList;
                            Debug.LogFormat("強制更新,有需要更新的Bundle");
                            callback?.Invoke(true, false);
                            callback = null;
                        }
else                        {
// 正常更新int[] remoteVersionDigit = remoteVersionInfo.GetVersionDigitArray();
int[] currVersionDigit = this.currVersionInfo == null ? newint[] { 0, 0, 0 } : this.currVersionInfo.GetVersionDigitArray();
// if (this.currVersionInfo == null || remoteVersionInfo.GetVersionLong() > this.currVersionInfo.GetVersionLong())if (remoteVersionDigit[0] > currVersionDigit[0] ||
                               remoteVersionDigit[1] > currVersionDigit[1] ||
                               remoteVersionDigit[2] > currVersionDigit[2])
                            {
                                Debug.LogFormat("這次需要熱更新");
this.remoteVersionInfo = remoteVersionInfo;
                                List<VersionBundleInfo> updateBundleList = new List<VersionBundleInfo>();

foreach (VersionBundleInfo rBInfo in remoteVersionInfo.bundles)
                                {
if (GetLocalBundleMD5(rBInfo.bundle_name) != rBInfo.md5)
                                    {
                                        updateBundleList.Add(rBInfo);
                                    }
                                }

// 有需要更新的包this.updateInfo = new UpdateInfo();
this.updateInfo.remoteVersionInfo = remoteVersionInfo;
this.updateInfo.updateBundleList = updateBundleList;
                                Debug.LogFormat("有需要更新的Bundle");
                                callback?.Invoke(true, false);
                                callback = null;
                            }
else                            {
                                Debug.LogFormat("遠程版本號 {0} <= 本地版本號 {1},無需更新!", remoteVersionInfo.version, this.currVersionInfo.version);
                                callback?.Invoke(true, false);
                                callback = null;
                            }
                        }
                    }
                }
else                {
                    Debug.LogErrorFormat("遠程 Version 文件反序列化失敗: {0}", remoteVersionText);
                }
            }
else            {
                Debug.LogErrorFormat("遠程 Version 文件內(nèi)容為空");
            }
        }
else        {
            Debug.LogErrorFormat("遠程 Version 文件下載失敗: {0}, {1}", request.State, request.Response.StatusCode);
        }

        BestHTTP.PlatformSupport.Memory.BufferPool.Release(request.Response.Data);
        callback?.Invoke(false, false);
    }

private IEnumerator TryUpdateBundle(System.Action<bool> callback)    {
if (this.remoteVersionInfo != null && this.updateInfo != null)
        {
long totalSize = 0;
foreach (VersionBundleInfo bInfo inthis.updateInfo.updateBundleList)
            {
                totalSize += bInfo.size;
            }

            UpdateUIDownload(totalSize, 0);
long downloadedSize = 0;
bool hasError = false;

foreach (VersionBundleInfo bInfo inthis.updateInfo.updateBundleList)
            {
                Debug.LogFormat("Bundle信息 {0} | {1}", GetLocalBundleMD5(bInfo.bundle_name), bInfo.md5);
if (GetLocalBundleMD5(bInfo.bundle_name) != bInfo.md5)
                {
string remoteBundleUrl = string.Format("{0}/fishing/{1}/{2}", REMOTE_URL, this.updateInfo.remoteVersionInfo.version, bInfo.bundle_name);
                    Debug.LogFormat("開始更新Bundle: {0}", remoteBundleUrl);
                    HTTPRequest request = new HTTPRequest(new System.Uri(remoteBundleUrl), false, true, null).Send();
while (request.State < HTTPRequestStates.Finished)
                    {

yield return newWaitForSeconds(0.1f);
                    }

if (request.State == HTTPRequestStates.Finished && request.Response.IsSuccess)
                    {
                        downloadedSize += bInfo.size;
string bundleWritePath = Application.persistentDataPath + "/" + bInfo.bundle_name;
// 寫入Bundle文件                        System.IO.File.WriteAllBytes(bundleWritePath, request.Response.Data);
                        Debug.LogFormat("{0} 更新完成", bInfo.bundle_name);
                        UpdateUIDownload(totalSize, downloadedSize);
                    }
else                    {
                        Debug.LogErrorFormat("{0} 下載出錯: {1}, {2}", bInfo.bundle_name, request.State, request.Response.IsSuccess);
                        callback?.Invoke(false);
                        callback = null;
                        hasError = true;
break;
                    }
yieldreturnnull;
                    BestHTTP.PlatformSupport.Memory.BufferPool.Release(request.Response.Data);
                }
else                {
                    Debug.LogFormat("!!!!!!!!!!! 本地已存在需要更新的 {0},跳過下載", bInfo.bundle_name);
                    downloadedSize += bInfo.size;
                    UpdateUIDownload(totalSize, downloadedSize);
                }
            }

if (!hasError)
            {
                Debug.LogFormat("寫入遠程 Version 文件");
// 最后寫入Version文件string versionText = Newtonsoft.Json.JsonConvert.SerializeObject(this.updateInfo.remoteVersionInfo);
                System.IO.File.WriteAllText(VERSION_FILE_PATH, versionText);
yieldreturnnull;

// 重新加載一遍本地文件this.currVersionInfo = LoadVersionInfo(VERSION_FILE_PATH);
                UpdateUIState("更新完成");
            }
        }
else        {
            Debug.LogFormat("無需要更新,前置數(shù)據(jù)不足: remoteVersionInfo is Null: {0}, updateInfo is Null: {1}", this.remoteVersionInfo == null, this.updateInfo == null);
        }
        callback?.Invoke(true);
    }

privateclassUpdateInfo    {
public MoeVersionInfo remoteVersionInfo;
public List<VersionBundleInfo> updateBundleList;
    }

private voidUpdateUIState(string msg)    {
        versionStateParam.state = msg;
        MoeEventManager.Inst.SendEvent(EventID.Event_OnVersionState, versionStateParam);
    }

private voidUpdateUIDownload(long total, long downloaded)    {
        updateProgressParam.totalUpdateSize = total;
        updateProgressParam.nowUpdatedSize = downloaded;
        MoeEventManager.Inst.SendEvent(EventID.Event_OnUpdateProgress, updateProgressParam);
    }

private voidOnMsgBox(string msg, string btnText, System.Action callback)    {
        msgBoxParam.msg = msg;
        msgBoxParam.btnText = btnText;
        msgBoxParam.callback = callback;
        MoeEventManager.Inst.SendEvent(EventID.Event_OnVersionMsgBox, msgBoxParam);
    }
}

MoeReleaseAssetBundleManager.cs 運行時資源管理器文章來源地址http://www.zghlxwxcb.cn/news/detail-702052.html

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;

publicclassMoeReleaseAssetBundleManager : IMoeResAgent{
conststring INDEX_FILE = "index";
privateDictionary<int, Object> _resources = new Dictionary<int, Object>();
privateDictionary<int, AssetBundle> _bundles = new Dictionary<int, AssetBundle>();
privateDictionary<int, string> _bundles_index = new Dictionary<int, string>();

private AssetBundleManifest _manifest = null;

public voidInit()    {
        InitAndLoadManifestFile();
        InitAndLoadIndexFile();
    }

private voidInitAndLoadIndexFile()    {
        _bundles_index.Clear();
        AssetBundle indexBundle = LoadBundleSync(INDEX_FILE);
        TextAsset ta = indexBundle.LoadAsset<TextAsset>(INDEX_FILE);
if (ta == null)
        {
            Debug.LogErrorFormat("Index 文件加載失?。?);
return;
        }

string[] lines = ta.text.Split('\n');
char[] trim = newchar[] { '\r', '\n' };

if (lines != null && lines.Length > 0)
        {
for (int i = 0; i < lines.Length; ++i)
            {
string line = lines[i].Trim(trim);
if (string.IsNullOrEmpty(line))
                {
continue;
                }

string[] pair = line.Split(':');
if (pair.Length != 2)
                {
                    Debug.LogErrorFormat("Index 行數(shù)據(jù)有問題: {0}", line);
continue;
                }

int hash = pair[0].GetHashCode();
if (_bundles_index.ContainsKey(hash))
                {
                    Debug.LogErrorFormat("Index 文件中存在相同的路徑: {0}", pair[0]);
                }
else                {
                    _bundles_index.Add(hash, pair[1]);
                }
            }
        }

if (_bundles_index.Count != 0)
        {
            Debug.LogFormat("Bundle Index 初始化完成");
        }
else        {
            Debug.LogErrorFormat("Index 文件數(shù)據(jù)為空");
        }

        indexBundle.Unload(true);
        indexBundle = null;
    }

private voidInitAndLoadManifestFile()    {
        AssetBundle manifestBundle = LoadBundleSync("Bundles");
        _manifest = manifestBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
        manifestBundle.Unload(false);
        manifestBundle = null;
    }

public T LoadAsset<T>(string path) where T : UnityEngine.Object    {
        UnityEngine.Object obj = Load(path);
if (obj != null)
        {
return obj as T;
        }

returnnull;
    }

public byte[] LoadLuaCode(string path)    {
string assetPath = string.Format("LuaScripts/{0}", path);
        TextAsset ta = LoadAsset<TextAsset>(assetPath);
if (ta != null)
        {
return ta.bytes;
        }
returnnull;
    }



private UnityEngine.Object Load(string assetPath)    {
if (string.IsNullOrEmpty(assetPath))
        {
returnnull;
        }

int pathHash = assetPath.GetHashCode();
        Object obj = null;
if (_resources.TryGetValue(pathHash, out obj))
        {
if (obj == null)
            {
                _resources.Remove(pathHash);
            }
else            {
return obj;
            }
        }

        AssetLoadInfo loadInfo = GetAssetLoadInfo(assetPath);
// 加載依賴Bundle
for (int i = 0; i < loadInfo.dependencies.Length; ++i)
        {
if (LoadBundleSync(loadInfo.dependencies[i]) == null)
            {
                Debug.LogErrorFormat("加載依賴Bundle出錯,資源 {0}, 主Bundle:{1}, 依賴:{2}", assetPath, loadInfo.mainBundle, loadInfo.dependencies[i]);
returnnull;
            }
        }

        AssetBundle mainBundle = LoadBundleSync(loadInfo.mainBundle);
if (mainBundle == null)
        {
            Debug.LogErrorFormat("加載主Bundle出錯,資源:{0},主Bundle:{1}", assetPath, loadInfo.mainBundle);
returnnull;
        }

        obj = mainBundle.LoadAsset(assetPath);

if (obj == null)
        {
            Debug.LogErrorFormat("從Bundle加載資源失敗,資源:{0},主Bundle:{1}", assetPath, loadInfo.mainBundle);
returnnull;
        }

        _resources.Add(pathHash, obj);
return obj;
    }

private AssetBundle LoadBundleSync(string bundleName)    {
int bundleHash = bundleName.GetHashCode();
        AssetBundle bundle = null;

if (!_bundles.TryGetValue(bundleHash, out bundle))
        {
#if UNITY_EDITORstring rootPath = Application.dataPath + "/../streaming";
#elsestring rootPath = Application.persistentDataPath;
#endifstring bundleLoadPath = System.IO.Path.Combine(rootPath, string.Format("Bundles/{0}", bundleName));
            Debug.LogFormat(">>>> 加載Bundle: {0}", bundleLoadPath);

using (var fileStream = new AssetBundleStream(bundleLoadPath, FileMode.Open, FileAccess.Read, FileShare.None, 1024 * 4, false))
            {
                bundle = AssetBundle.LoadFromStream(fileStream);
            }

// bundle = AssetBundle.LoadFromFile(bundleLoadPath);
if (bundle != null)
            {
                _bundles.Add(bundleHash, bundle);
            }
else            {
                Debug.LogErrorFormat("Bundle 加載失敗 {0}, LoadPath: {1}", bundleName, bundleLoadPath);
            }
        }
else        {
// Debug.LogFormat("Bundle {0} 已加載,直接返回", bundleName);        }

return bundle;
    }

private stringGetAssetOfBundleFileName(string assetPath)    {
int assetHash = assetPath.GetHashCode();
string bundleName;
if (_bundles_index.TryGetValue(assetHash, out bundleName))
        {
return bundleName;
        }

returnstring.Empty;
    }

private AssetLoadInfo GetAssetLoadInfo(string assetPath)    {
        AssetLoadInfo loadInfo = new AssetLoadInfo();
        loadInfo.assetPath = assetPath;
        loadInfo.mainBundle = GetAssetOfBundleFileName(assetPath);
        loadInfo.dependencies = _manifest.GetAllDependencies(loadInfo.mainBundle);
return loadInfo;
    }


privateclassAssetLoadInfo    {
publicstring assetPath;
publicstring mainBundle;
publicstring[] dependencies;
    }
}

到了這里,關(guān)于Unity 熱更新方案和流程的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權(quán),不承擔相關(guān)法律責任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務器費用

相關(guān)文章

  • [游戲開發(fā)][Unity]Assetbundle打包篇(2)打包資源配置篇

    [游戲開發(fā)][Unity]Assetbundle打包篇(2)打包資源配置篇

    打包與資源加載框架目錄 可視化配置的方式有很多種,Json、XML、以及Unity內(nèi)置的ScriptableObject序列化 配置文件里要有哪些內(nèi)容呢,很顯然,最重要的就是目標文件路徑,其次是權(quán)重類型,權(quán)重類型有:必要打包型、被引用打包型、忽略類型。為何會有忽略類型呢,是因為我們

    2024年02月09日
    瀏覽(51)
  • 【抖音小游戲】 Unity制作抖音小游戲方案 最新完整詳細教程來襲【持續(xù)更新】
  • [游戲開發(fā)][Unity]Assetbundle打包篇(1)打包流程介紹

    [游戲開發(fā)][Unity]Assetbundle打包篇(1)打包流程介紹

    打包與資源加載框架目錄 先捋一下打AB包的整體思路,首先,Unity4.6版本之后就使用了全新的打包接口 無論是全新打包還是增量打包都是使用這個API,所以一切的一切,都要圍繞這個API開始講起。 該API有四個參數(shù) string outputPath AssetBundleBuild[] builds BuildAssetBundleOptions assetBundle

    2024年02月10日
    瀏覽(29)
  • [游戲開發(fā)][Unity]Assetbundle打包篇(5)使用Manifest二次構(gòu)建資源索引

    [游戲開發(fā)][Unity]Assetbundle打包篇(5)使用Manifest二次構(gòu)建資源索引

    打包與資源加載框架目錄 正文開始前,先把打包代碼放過來,請注意,前面的代碼已省略,自己去對比前面的文章。本篇文章從第一次執(zhí)行打包代碼開始。 第一次調(diào)用BuildPipeline.BuildAssetBundles打包API后(詳見代碼第七行),會返回AssetBundleManifest的引用, 【疑問】: BuildPipe

    2024年02月03日
    瀏覽(23)
  • Unity-微信小游戲上架流程-個人開發(fā)者

    Unity-微信小游戲上架流程-個人開發(fā)者

    寫在前面 ????????微信上架流程個人認為比較繁瑣,而且嚴格,時間跨度長。在微信平臺上架小游戲,實際上相當于將一個Unity WebGL的工程上架到微信小程序,只不過將微信小程序的類型設(shè)置成小游戲,使用微信專門的打包工具打包導出,并且使用微信專門的調(diào)試工具進行

    2024年04月17日
    瀏覽(23)
  • Unity手機游戲開發(fā):從搭建到發(fā)布上線全流程實戰(zhàn)

    Unity手機游戲開發(fā):從搭建到發(fā)布上線全流程實戰(zhàn)

    前言: 技術(shù)書籍是學習技術(shù)知識的重要資源之一。讀技術(shù)書可以幫助我們學習新技能和知識,技術(shù)書籍提供了可靠的、全面的信息,幫助我們快速學習新技能和知識。同時技術(shù)書籍有助于保持你的競爭力,因為它們提供了最新的技術(shù)知識和實踐。這在當今快速發(fā)展的技術(shù)領(lǐng)域

    2024年01月22日
    瀏覽(30)
  • [游戲開發(fā)]Unity顏色矯正無障礙方案

    [游戲開發(fā)]Unity顏色矯正無障礙方案

    之前有在關(guān)注色盲視覺糾正問題,最近在調(diào)整游戲的時候就打算把這個用上。 色弱色盲,這其實算是一種誤稱吧,只是人類中的少數(shù)派,只不過看到的顏色和大部分人不一樣。下文用,視覺少數(shù)者,來稱呼吧。 本質(zhì)上是因為感知顏色的細胞發(fā)生突變,感知與大部分人有差異

    2024年02月15日
    瀏覽(24)
  • Photon Unity Networking 實時多人在線游戲開發(fā)解決方案

    作者:禪與計算機程序設(shè)計藝術(shù) 2019年,由英特爾、Facebook等公司聯(lián)合舉辦的GDC大會上宣布了Unity Technologies將推出一個新品牌——Unity Game Development Platform(UGDP)。這個平臺將包括對虛幻引擎4、Unreal Engine 4和原生Unity引擎的支持。在這個平臺基礎(chǔ)上,Unity Technologies推出了實時的多

    2024年02月09日
    瀏覽(34)
  • Unity Xlua熱更新框架(五):Lua和UI管理

    Unity Xlua熱更新框架(五):Lua和UI管理

    :::info Lua存在兩種加載器,一種默認加載器(env.DoString(\\\"require(‘test’)\\\"直接用了默認加載其),直接調(diào)用StreamingAssets中的腳本);一種是自定義加載器(env.AddLoader(Envpath)),優(yōu)先于默認加載器(下文DoString就是從自定義加載器的路徑讀取的),并且當Lua代碼執(zhí)行require函數(shù)時,

    2024年02月06日
    瀏覽(39)
  • Unity資源熱更新框架

    Unity資源熱更新框架

    什么是熱更新? ????????游戲上線后,玩家下載第一個版本(1G左右或者更大),在之后運營的過程中,如果需要更換UI顯示,或者修改游戲的邏輯,這個時候,如果不使用熱更新,就需要重新打包,然后讓玩家重新下載,很顯然體驗非常不好。 熱更新可以在不重新下載客

    2024年02月10日
    瀏覽(18)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包