Super detailed implementation of packaging ab package from scratch, uploading ab package to the server, loading ab package from the server (using nginx to build a file server), and realizing hot patch function (based on xLua)

Foreword:

Unity editor version: 2019.4.0

The following file naming and directory are best consistent with my own

1. Create a blank U3D project

2. Build the xLua environment

Download the xLua toolkit from github

Import the xLua toolkit and configure the development environment

First import the Plugins and XLua folders in the xLua/Assets directory to the Assets directory in the project,

Secondly, import the Tools folder under the xLua file directory to the directory at the same level as the Assets directory in the project.

Click Edit/Project Settings, find Scriptng Define Symbols under Other Settings, enter HOTFIX_ENABLE and press Enter (be sure to press Enter!)

Then check if there is an XLua extension in the upper menu bar, and there are three options: Generate Code, Clear Generated Code, Hotfix Inject In Editor, so far XLua configuration is complete.

3. Build a simple game scene

A simple unupdated scene is built as follows.

Create a new Resources folder, create a Cube in the scene, drag a Sphere into the Resources folder as a prefab, and create an empty object GameManager in the scene to hang the script.

Create a new Scripts folder, and create a GameScript script in the folder. This script is used to generate a cube by clicking on the screen. Note: To mark the Hotfix tag, the Update method in the script will be replaced with lua later

using UnityEngine;
using UnityEngine.EventSystems;
using XLua;
//-----------------------------【游戏脚本】-----------------------------
[Hotfix]
public class GameScript : MonoBehaviour
{
    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);
    }
}

Create a new XLuaManager script to execute the Lua script, and import the singleton template before creating it

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

// 实现普通的单例模式
// where 限制模板的类型, new()指的是这个类型必须要能被实例化
public abstract class Singleton<T> where T : new() {
    private static T _instance;
    private static object mutex = new object();
    public static T instance {
        get {
            if (_instance == null) {
                lock (mutex) { // 保证我们的单例,是线程安全的;
                    if (_instance == null) {
                        _instance = new T();
                    }
                }
            }
            return _instance;
        }
    }
}

// Monobeavior: 声音, 网络
// Unity单例

public class UnitySingleton<T> : MonoBehaviour
where T : Component {
    private static T _instance = null;
    public static T Instance {
        get {
            if (_instance == null) {
                _instance = FindObjectOfType(typeof(T)) as T;
                if (_instance == null) {
                    GameObject obj = new GameObject();
                    _instance = (T)obj.AddComponent(typeof(T));
                    obj.hideFlags = HideFlags.DontSave;
                    // obj.hideFlags = HideFlags.HideAndDontSave;
                    obj.name = typeof(T).Name;
                }
            }
            return _instance;
        }
    }

    public virtual void Awake() {
        DontDestroyOnLoad(this.gameObject);
        if (_instance == null) {
            _instance = this as T;
        }
        else {
            GameObject.Destroy(this.gameObject);
        }
    }
}

Create a new XLuaManager script, and create a new LuaTxt folder in the Assets directory. The lua script must be named with a .txt suffix, otherwise the lua script cannot be packaged into an ab package.

using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;
using XLua;
public class XLuaManager : UnitySingleton<XLuaManager>
{
    private static string luaScriptsFolder = "LuaTxt";
    private LuaEnv env = null;
    public override void Awake()
    {
        base.Awake();
        this.InitLuaEnv();
    }
    public byte[] LuaScriptLoader(ref string filePath)
    {
         string newPath = Application.dataPath + @"/" +luaScriptsFolder+@"/" + filePath + ".lua.txt";
         Debug.Log("执行脚本路径:" + newPath);
         string txtString = File.ReadAllText(newPath);
         return System.Text.Encoding.UTF8.GetBytes(txtString);
    }
    private void InitLuaEnv()
    {
        this.env = new LuaEnv();
        //添加我们自定义的lua代码装载器
        this.env.AddLoader(LuaScriptLoader);
    }
    public void EnterGame()
    {
        this.env.DoString("require(\"Click\")");
    }
}

Create a new txt under the LuaTxt folder, name it Click.lua.txt, and copy the following code into the txt

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("GameManager")
                -- 获取assetBundle资源
                local ab = go:GetComponent("GameManager").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
)

Create a new GameMagager script, and fill in the UnityWebRequest www = UnityWebRequestAssetBundle.GetAssetBundle(@"http://server's public IP/sphere.unity3d"); line of code with the server's public IP.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using System.IO;
using System;
using UnityEngine.UI;
public class GameManager : UnitySingleton<GameManager>
{
    public Slider slider;
    public Text progressText;//进度显示
    [HideInInspector]
    public AssetBundle assetBundle;
    public override void Awake()
    {
        base.Awake();
        this.gameObject.AddComponent<XLuaManager>();
        this.gameObject.AddComponent<GameScript>();
    }
    IEnumerator GameStart()
    {
        yield return checkHotUpdate();
        XLuaManager.Instance.EnterGame();
    }
    IEnumerator checkHotUpdate()
    {
        UnityWebRequest www = UnityWebRequestAssetBundle.GetAssetBundle(@"http://服务器的公网IP/sphere.unity3d");
        www.SendWebRequest();
        while (!www.isDone)
        {
            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);
            Debug.Log(assetBundle.name);
            TextAsset hot = assetBundle.LoadAsset<TextAsset>("Click.lua");
            string newPath = Application.dataPath+"/LuaTxt" + @"/Click.lua.txt";
            if (!File.Exists(newPath))
            {
                // Create后如果不主动释放资源就会被占用,下次打开会报错,所以一定要加上 .Dispose()
                File.Create(newPath).Dispose();
            }
            // 写入文件
            File.WriteAllText(newPath, hot.text);
            Debug.Log("下载资源成功!new Path : " + newPath);
        }
    }
    public void BtnHot()
    {
        StartCoroutine(GameStart());
    }
}

And hang on the GameManager empty object, and assign slider and progresstext

Then add a monitor to the button, the monitor function is the BtnHot() function in GameManager

 Four, pack ab package

For convenience, we will make Click.lua.txt and Sphere ball into an ab package, we need to replace the cube with a ball (note that you must press Enter after entering the name)

Create a new Editor folder under the Assets directory (it is very important otherwise the subsequent packaging will report an error), and create a CreateAssetBundle script under the Editor folder to pack the ab package

using UnityEditor;
using System.IO;

public class CreateAssetBundle
{
    [MenuItem("AssetBundles/BuildAssets")]
    static void BuildAllAssetBundles()
    {
        string dir = "AssetBundles";
        if (Directory.Exists(dir) == false)
        {
            Directory.CreateDirectory(dir);
        }
        BuildPipeline.BuildAssetBundles(dir, BuildAssetBundleOptions.None, BuildTarget.StandaloneWindows64);
    }
}

Click BuildAssets under AssetBundles

At this time, you will find that there is an AssetBundles folder in the directory of the same level as Assets, and the ab package is inside the folder.

If the packaging error is as follows

Please refer to the link https://blog.csdn.net/u014513152/article/details/106085418

5. Upload the ab package to the server

Preparations: Go to the official website to download Xshell Xftp

Website address: https://www.xshell.com/zh/xshell/

 Server: Here is the Tencent Cloud lightweight server (please apply for one in advance)

Start Xshell, create a new session, and fill in the ip address of your server as the host

 Fill in the username and password

After the login is successful, click the transfer new file under the window to automatically open Xftp, and drag the AssetBundles that have just been packaged to the right.

So far, the ab package is successfully uploaded

Six, nginx new file server 

First go back to the Xshell page where you just logged in successfully and download nginx

Enter sudo yum install nginx (I use yum to download, please Baidu if the download fails)

Modify nginx default configuration file

Find the /etc/nginx/nginx.conf configuration file and right-click to open it with Notepad

You can refer to my modified configuration file information. The most important thing is to configure the location in the server and set the port number to 80.

user  root;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  65535;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  200;

    #gzip  on;

 server {
       listen 80;
       server_name  localhost;
               location / {
	root  /root/AssetBundles;
	autoindex on;
	charset utf-8;
        }
}

}

At this point, nginx has built a file server

7. Preparation before the test

Go back to the Xshell page, enter nginx -c /etc/nginx/nginx.conf to start nginx (ensure that nginx is open when testing Unity)

Enter the public network ip of your server in the browser to see if you can see the ab package. If you see it, nginx has successfully built the file server! ! !

 Go back to U3d, click Generate Code under XLua to generate code, and finished will appear in the Console panel! Indicates that the generation is complete

Then click Hotfix Inject In Editor under Xlua to inject the code into the editor, and had injected appears below! Indicates that the injection is complete.

Delete Click.lua.txt under LuaTxt, delete the Sphere prefab under Resources, of course, you can not delete it, just to prove that you are calling the ab package on the server, not the existing file of the project.

8. Test

Test, click to run, this is the situation when the hot update is not performed, click on the screen and a cube will appear.

Then run again, first click the hot update button, wait for the progress bar to load to 100%, and then click the ground, a ball will appear, 

And in LuaTxt you can see the Click.lua.txt file downloaded from the server 

This article reference link https://blog.csdn.net/weixin_33918788/article/details/112710709

Guess you like

Origin blog.csdn.net/ysn11111/article/details/124581657