Unity - AssetBundle和XLua熱更新教程(簡單詳細)

前言

Unity版本:Version 2018.4.2f1

這裏首先需要我們掌握了XLua的基本知識,具體教程文檔可以去GitHub - XLua看看。

大概思路:標記需要熱更的資源,打包構建AssetBundle資源(包括lua腳本),上傳到服務器,客戶端通過服務器下載讀取AssetBundle資源,執行lua腳本即可。

這裏的測試 Demo 運行時 點擊鼠標左鍵原本應該是創建的Cube,通過熱更新 成功後,應該是創建一個 紅色的Sphere

實踐

一. Xlua及其HotFix的環境配置

  1. GitHub - XLua 下載最新版本的xlua工程,解壓並複製該文件中的 Tools文件夾工程根目錄 下 , Assets 下的 Plugins、XLua文件夾工程對應的Assets
  2. 打開HotFix的宏。在Unity菜單欄找到File -> Build Setting -> Player Setting ->Scriptsing Define Symbols ,在裏面輸入宏 HOTFIX_ENABLE,按ENTER後,Unity後臺會編譯一下。編譯過後會在編譯器XLua下面多一個“Hotfix Inject in Editor”(XLua -> Hotfix Inject in Editor)

二. 創建一個打包AssetBundle資源的腳本並放在Editor文件夾下

BuildAssetBundles.cs 腳本如下:

using System.Net;
using UnityEditor;
using System.IO;
using System;
//-----------------------------【構建AssetBundles資源包】-----------------------------
public class BuildAssetBundles
{
    // 菜單選項目錄
    [MenuItem("Assets/Build AssetBundles")]
    static public void BuildAllAssetBundles()
    {
        // 創建文件目錄
        string dir = "AssetBundles";
        if (!Directory.Exists(dir))
        {
            Directory.CreateDirectory(dir);
        }
        // 構建
        // 參數1:路徑
        // 參數2:壓縮算法,none 默認
        // 參數3:設備參數,ios,Android,windows等等
        BuildPipeline.BuildAssetBundles(dir, BuildAssetBundleOptions.None, BuildTarget.StandaloneWindows64);
        UnityEngine.Debug.Log("AssetBundle資源打包完成!");
    }
}

此時可以在 編輯器菜單欄中看到 Asset -> Build AssetBundles 按鈕。

三. 創建Demo場景及其資源腳本

首先創建一個基本的UI界面,有一個Button、Slider 和 Text 就行,用來顯示熱更新進度。

例如:
在這裏插入圖片描述

然後創建 GameScript.cs 和 DownLoad.cs 腳本,並且掛載到場景空物體上,分別拖拽Slider 和 Text 到DownLoad腳本的公共屬性,把 StartDownLoad方法綁定到Button組件的點擊事件:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-yBikQ5gb-1584196102306)(/markdown/images/5e6c8e5cedbc2a24dfa43a47)]

GameScript.cs :

using UnityEngine;
using UnityEngine.EventSystems;
using XLua;
//-----------------------------【遊戲腳本】-----------------------------
[Hotfix]
public class GameScript : MonoBehaviour
{
    public int num1 = 100;
    private int num2 = 200;
    void Update()
    {
        // 鼠標左鍵點擊
        if (Input.GetMouseButtonDown(0))
        {
            if (EventSystem.current.IsPointerOverGameObject())
            {
                Debug.Log("點擊到UGUI的UI界面");
            }
            else
            {
                //創建 cube  (後面會通過熱更 lua腳本替換掉這裏,使之生成Sphere)
                GameObject cubeGo = Resources.Load("Cube") as GameObject;
                // 在鼠標點擊的地方實例cube
                Ray ray = Camera.main.ScreenPointToRay (Input.mousePosition);
				RaycastHit hit;
				if (Physics.Raycast (ray, out hit)) {
					Debug.Log (hit.point);
					GameObject cube = GameObject.Instantiate (cubeGo, hit.point + new Vector3(0,1,0), transform.rotation)as GameObject;
                }
            }

        }
    }

    //射線 - 用於xlua調用 避免重載問題
    public static bool RayFunction(Ray ray, out RaycastHit hit)
    {
        return Physics.Raycast(ray, out hit);
    }

}

這裏需要注意的地方:
我們必須在需要熱更新的類打上 [Hotfix] 標籤,這裏很關鍵,我們後續會通過lua替換掉 GameScript 裏的Update方法。

DownLoad.cs :

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using UnityEngine.Networking;
using XLua;
using UnityEngine.UI;
//-----------------------------【下載資源】-----------------------------
public class DownLoad : MonoBehaviour
{
    // private string path = @"file://E:\WorkSpaces\Unity\XLuaFix\AssetBundles\newobj.u3d";
    // private string verPath = @"http://localhost/AssetBundles/version.txt";
    private string path = @"http://localhost/AssetBundles/newobj.u3d";

    public AssetBundle assetBundle;

    // 開始下載更新
    public void StartDownLoad()
    {
        Debug.Log("開始下載更新!");
        // 啓動協程
        StartCoroutine(GetAssetBundle(ExcuteHotFix));
    }


    //-----------------------------【從服務器下載熱更資源】-----------------------------
    public Slider slider;
    public Text progressText;//進度顯示
    IEnumerator GetAssetBundle(Action callBack)
    {
        UnityWebRequest www = UnityWebRequestAssetBundle.GetAssetBundle(path);
        www.SendWebRequest();
        while (!www.isDone)
        {
            Debug.Log(www.downloadProgress);
            slider.value = www.downloadProgress;//下載進度
            progressText.text = Math.Floor(www.downloadProgress * 100) + "%";
            yield return 1;
        }
        // 下載完成
        if (www.isDone)
        {
            progressText.text = 100 + "%";
            slider.value = 1;
            // 隱藏UI(等待1s)
            yield return new WaitForSeconds(1);
            GameObject.Find("Canvas").SetActive(false);
        }

        if (www.isNetworkError || www.isHttpError)
        {
            Debug.Log("DownLoad Err: " + www.error);
        }
        else
        {
            assetBundle = DownloadHandlerAssetBundle.GetContent(www);
            TextAsset hot = assetBundle.LoadAsset<TextAsset>("luaScript.lua.txt");

            string newPath = Application.persistentDataPath + @"/luaScript.lua.txt";
            if (!File.Exists(newPath))
            {
                // Create後如果不主動釋放資源就會被佔用,下次打開會報錯,所以一定要加上 .Dispose()
                File.Create(newPath).Dispose();
            }
            // 寫入文件
            File.WriteAllText(newPath, hot.text);

            Debug.Log("下載資源成功!new Path : " + newPath);
            // 下載成功後 讀取執行lua腳本
            callBack();
        }
    }

    //-----------------------------【執行熱更腳本】-----------------------------
    public void ExcuteHotFix()
    {
        Debug.Log("開始執行熱更腳本 luaScript");
        LuaEnv luaenv = new LuaEnv();
        luaenv.AddLoader(MyLoader);
        luaenv.DoString("require 'luaScript'");
    }

    // 自定義Loader
    public byte[] MyLoader(ref string filePath)
    {
        // 讀取下載的腳本資源
        string newPath = Application.persistentDataPath + @"/" + filePath + ".lua.txt";
        Debug.Log("執行腳本路徑:" + newPath);
        string txtString = File.ReadAllText(newPath);
        return System.Text.Encoding.UTF8.GetBytes(txtString);
    }
}

這裏的DownLoad腳本主要是用來下載服務器上的資源,包括貼圖,模型,預製體,lua腳本等,然後執行lua腳本替換掉原本的功能。

然後繼續 創建 一個 Cube 和 紅色的Sphere 預製體,放在Resources文件夾下。

此時運行遊戲,點擊鼠標左鍵會創建一個Cube。
後面我們通過熱更新的方式,去修改該方法,使之點擊創建Sphere!
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-FwJ5lIFG-1584196102307)(/markdown/images/5e6ce434edbc2a014d5d7129)]

四. 創建lua補丁腳本和打包AssetBundles資源

創建一個lua腳本,命名爲luaScript.lua.txt
主要這裏是以 .txt 爲後綴

代碼如下:

print("Version: 1.5")

xlua.private_accessible(CS.GameScript)

local unity = CS.UnityEngine

--[[
xlua.hotfix(class, [method_name], fix)
 描述 : 注入lua補丁
 class : C#類,兩種表示方法,CS.Namespace.TypeName或者字符串方式"Namespace.TypeName",字符串格式和C#的Type.GetType要求一致,如果是內嵌類型(Nested Type)是非Public類型的話,只能用字符串方式表示"Namespace.TypeName+NestedTypeName";
 method_name : 方法名,可選;
 fix : 如果傳了method_name,fix將會是一個function,否則通過table提供一組函數。table的組織按key是method_name,value是function的方式。
--]]
-- 替換掉 GameScript 的 Update 方法

xlua.hotfix(CS.GameScript,"Update",
    function(self)
        if unity.Input.GetMouseButtonDown(0) then
            local go = unity.GameObject.Find("ScriptsManager")
            -- 獲取assetBundle資源
            local ab = go:GetComponent("DownLoad").assetBundle
            -- 讀取創建 Sphere 
            local SphereGo = ab:LoadAsset("Sphere")

            -- 在鼠標點擊的位置實例Sphere
            local ray = unity.Camera.main:ScreenPointToRay (unity.Input.mousePosition)
            local flag,hit = CS.GameScript.RayFunction(ray)
            if flag then
                print(hit.transform.name)
                local sphere = unity.GameObject.Instantiate(SphereGo)
                sphere.transform.localPosition = hit.point + unity.Vector3(0,1,0)
            end
        
        end
    end
)

在打包之前我們先把需要熱更新的資源標記 AssetBundle,如下圖,標記Sphere和lua腳本 :
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-OI6sQK8Z-1584196102307)(/markdown/images/5e6c8feaedbc2a1e52ed8f10)]

最後, 點擊 Asset -> Build AssetBundles 按鈕打包,打包成功後 我們可以在項目根目錄 發現 AssetBundles 文件夾, 裏面的就是我們打包出來的AssetBundle資源,最終會傳到服務器上

五. 啓動本地測試服務器

我們在本地快速搭建一個服務器,這裏可以到我的github下載 webServer文件夾下的NetBox2.exe 程序,點擊運行即可快速啓動一個本地服務器。(當然也可以用其他的,如Nginx、Apache等,只有能下載資源就行)

然後把我們剛纔打包好的AssetBundles資源文件夾複製到 NetBox2.exe 同級目錄下,點擊 NetBox2.exe 運行即可啓動服務器,此時我們就可以通過遠程下載該資源。

目錄結構如下:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Rh5QtnHB-1584196102307)(/markdown/images/5e6c908bedbc2a2722aae6d9)]

六. 最後,運行測試Demo

在測試前我們先刪掉Resources文件夾下面的Sphere預製體和luaScript.lua腳本!

一切準備就緒後…

該demo在widows上測試如下:

  1. 點擊 XLua -> Generate Code 按鈕 生成lua代碼
  2. 點擊 XLua -> hotfix Inject In Editor 按鈕 注入xlua hotfix補丁
  3. 點擊運行測試。

如果最終生成的是Sphere 則說明熱更新成功
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-eRKMU5SY-1584196102308)(/markdown/images/5e6ce673edbc2a0020f7b49b)]

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-qZQv5UAY-1584196102308)(/markdown/images/5e6ce68bedbc2a2b0aa06bea)]

最後

該Demo已經上傳到我的 GitHub 上,歡迎Star,謝謝!

總結:

在開發的過程中我們就需要在有可能會出現bug或者需要熱更新的地方打上HotFix標籤。
標記需要熱更的資源,打包構建AssetBundle資源(包括貼圖,預製體,模型,lua腳本等),上傳到服務器,客戶端通過服務器下載讀取AssetBundle資源,執行lua腳本替換原有功能即可。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章