Unity Xlua热更新框架(十):真机调试与服务器内网穿透

15. 真机调试

15-1. 真机调试 1

  • 启动热更新流程
  • 制作Loading界面
  • 打包APK

修改GameStart脚本
之前逻辑时GameStart直接执行加载ui的语句,现在变成将几个重要的语句如启动Lua的StartLua和启动UI的GameInit放进函数中,注册到EventManager,根据是否热更新,fire执行对应的事件。没有热更新直接init,有热更新,在HotUpdate中加载loadingUI最后EnterGame里Fire启动UI的GameInit事件

public class GameStart : MonoBehaviour
{
    public GameMode GameMode;
    public bool OpenLog;

    // Start is called before the first frame update
    void Start()
    {
        //开始时订阅事件
        Manager.Event.Subscribe((int)GameEvent.StartLua, StartLua);
        Manager.Event.Subscribe((int)GameEvent.GameInit, GameInit);
        AppConst.GameMode = this.GameMode;
        AppConst.OpenLog = this.OpenLog;
        DontDestroyOnLoad(this);

        if (AppConst.GameMode == GameMode.UpdateMode)
            this.gameObject.AddComponent<HotUpdate>();//如果是热更新模式,启动更新后立刻解析filelist启动lua肯定不行,因为是旧文件,需要热更新完成后再调用
        else
            Manager.Event.Fire((int)GameEvent.GameInit);//不是热更新模式就直接执行前面注册的gameInit方法
    }
    
    private void GameInit(object args)
    {
        if (AppConst.GameMode != GameMode.EditorMode)
            Manager.Resource.ParseVersionFile();//编辑模式下不需要filelist
        Manager.Lua.Init();
    }

    void StartLua(object args)
    {
        //初始化完成之后(lua都加载完),在执行回调
        Manager.Lua.StartLua("main"); //输入的文件名
        //输入的是函数名
        XLua.LuaFunction func = Manager.Lua.LuaEnv.Global.Get<XLua.LuaFunction>("Main");
        func.Call();
        
        Manager.Pool.CreateGameObjectPool("UI", 10);
        Manager.Pool.CreateGameObjectPool("Monster", 120);
        Manager.Pool.CreateGameObjectPool("Effect", 120);
        Manager.Pool.CreateAssetPool("AssetBundle", 10);
    }

    public void OnApplicationQuit()
    {
        Manager.Event.UnSubscribe((int)GameEvent.StartLua, StartLua);
        Manager.Event.UnSubscribe((int)GameEvent.GameInit, GameInit);
    }
}
public enum GameEvent
{
    GameInit = 10000,
    StartLua,
}
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.Networking;

/// 
/// 热更新类:
/// 定义了下载信息DownFileInfo,和用来保存只读目录filelist和服务器filelist的变量
/// 通用UnityWebRequest,既可以从本地下载(释放)文件也可以从服务器下载文件,用DownLoadFile实现了单个或多个文件(封装了单个文件下载)下载
/// GetFileList可以从filelist获取需要更新的文件信息
/// 流程:
/// IsFirstInstall如果是初次安装,就从可读目录释放资源ReleaseResources到可读写目录(然后热更新CheckUpdate),如果可读目录没有资源就从服务器下载资源CheckUpdate到可读写目录。释放与下载逻辑一致
/// ReleaseResources释放可读目录filelist,释放完OnReleaseReadPathFileListComplete下载所有包,回调单个文件下载好OnReleaseFileComplete写入可读写目录,所有文件下载完OnReleaseAllFileComplete把可读目录filelist写入可读写目录,CheckUpdate
/// CheckUpdate下载服务器filelist,下载完OnDownLoadServerFileListComplete下载所有包,回调单个文件下载好OnUpdateFileComplete写入可读写目录,所有文件下载完OnUpdateAllFileComplete把服务器filelist写入可读写目录,进入游戏
/// 
public class HotUpdate : MonoBehaviour
{
    
    //下载文件数量
    private int m_DownloadCount;

    private GameObject loadingObj;
    private LoadingUI loadingUI;

    private void Start()
    {
        GameObject go = Resources.Load<GameObject>("LoadingUI");
        loadingObj = Instantiate(go);
        loadingObj.transform.SetParent(this.transform);
        loadingUI = loadingObj.GetComponent<LoadingUI>();
        if(IsFirstInstall())
        {
            //如果是初次安装,先按照可读目录的filelist释放资源到可读写目录,再更新
            ReleaseResources();
        }
        else
        {
            CheckUpdate();
        }
    }       
   
    private void ReleaseResources()
    {
        m_DownloadCount = 0;//释放资源时,把下载文件数量初始化一下
        string url = Path.Combine(PathUtil.ReadPath, AppConst.FileListName);
        DownFileInfo info = new DownFileInfo();
        info.url = url;
        //UnityWebRequest可以从本地下载
        //先读取filelist里需要释放的文件
        StartCoroutine(DownLoadFile(info, OnReleaseReadPathFileListComplete));
    }

    private void OnReleaseReadPathFileListComplete(DownFileInfo file)
    {
        //从只读目录加载完filelist后,保存filelist的内容
        m_ReadPathFileListData = file.fileData.data;
        //获取到filelist里的所有要释放的文件的文件信息
        List<DownFileInfo> fileInfos = GetFileList(file.fileData.text, PathUtil.ReadPath);
        StartCoroutine(DownLoadFile(fileInfos, OnReleaseFileComplete, OnReleaseAllFileComplete));
        //开始释放资源时,将释放文件的数量去更新进度条的显示文本
        loadingUI.InitProgress(fileInfos.Count, "Releasing resources, not consuming data...");
    }

    private void OnReleaseFileComplete(DownFileInfo fileinfo)
    {
        Debug.Log("OnReleaseFileComplete:" + fileinfo.url);
        //可读写目录加bundle目录
        string writeFile = Path.Combine(PathUtil.ReadWritePath, fileinfo.fileName);
        FileUtil.WriteFile(writeFile, fileinfo.fileData.data);
        m_DownloadCount++;
        loadingUI.UpdateProgress(m_DownloadCount);//每释放完一个文件,都更新一下进度条
    }

	private void CheckUpdate()
    {
        m_DownloadCount = 0;
        //更新前,先从本地的可读写目录先获取旧的filelist文件信息,读取md5
        string oldFilelistPath = PathUtil.ReadWritePath + AppConst.FileListName;
        oldFileMD5 = new Dictionary<string, string>();
        if (FileUtil.IsExits(oldFilelistPath))
        {
            //对文件进行读取
            string[] data = File.ReadAllLines(oldFilelistPath);

            //解析文件信息
            for (int i = 0; i < data.Length; i++)
            {
                string[] infos = data[i].Split('|');
                string fileName = infos[1];
                string md5 = infos[2];
                oldFileMD5.Add(fileName, md5);
            }
        }
        
        //获取filelist再资源服务器上的地址
        string url = Path.Combine(AppConst.ResourcesUrl, AppConst.FileListName);
        DownFileInfo info = new DownFileInfo();
        info.url = url;
        StartCoroutine(DownLoadFile(info, OnDownLoadServerFileListComplete));
    }

    private void OnDownLoadServerFileListComplete(DownFileInfo file)
    {
        m_DownloadCount = 0;//开始更新时重置下载文件数量
        //保存最新的filelist
        m_ServerFileListData = file.fileData.data;
        //获取资源服务器的文件信息目录
        List<DownFileInfo> fileInfos = GetFileList(file.fileData.text, AppConst.ResourcesUrl);
        //定义需要下载的文件集合
        List<DownFileInfo> downListFiles = new List<DownFileInfo>();

        //遍历资源服务器的文件信息
        for (int i = 0; i < fileInfos.Count; i++)
        {
            string localFile = Path.Combine(PathUtil.ReadWritePath, fileInfos[i].fileName);
            //判断本地是否存在,如果不存在就下载
            if(!FileUtil.IsExits(localFile))
            {
                fileInfos[i].url = Path.Combine(AppConst.ResourcesUrl, fileInfos[i].fileName);
                downListFiles.Add(fileInfos[i]);
            }
        }
        if (downListFiles.Count > 0)
        {
            StartCoroutine(DownLoadFile(fileInfos, OnUpdateFileComplete, OnUpdateAllFileComplete));
            loadingUI.InitProgress(downListFiles.Count, "Downloading new resources...");//当判断完本地没有哪些文件,服务器哪些文件需要下载后,更新进度条显示,传进去需要下载的文件数量
        }
        else
        {
            EnterGame();
        }
    }

    private void OnUpdateFileComplete(DownFileInfo file)
    {
        Debug.Log("OnUpdateFileComplete:" + file.url);
        //下载新文件
        string writeFile = Path.Combine(PathUtil.ReadWritePath, file.fileName);
        FileUtil.WriteFile(writeFile, file.fileData.data);
        m_DownloadCount++;
        loadingUI.UpdateProgress(m_DownloadCount);//每更新下载完一个文件都更新一下进度条
    }

    private void OnUpdateAllFileComplete()
    {
        //所有文件下载完,写入最新的filelist
        FileUtil.WriteFile(Path.Combine(PathUtil.ReadWritePath, AppConst.FileListName), m_ServerFileListData);
        EnterGame();
        loadingUI.InitProgress(0, "Loading...");
    }

    private void EnterGame()
    {
        Manager.Event.Fire((int)GameEvent.GameInit);
        Destroy(loadingObj,2);//立刻退出太突兀了,适当延时
    }
}

Loading界面需要在加载前执行,因此只能放在Resources路径下
image.png
编写LoadingUI脚本挂到LoadingUI.prefab上,把几个变量赋值进去。
重新构建ab包,删除可读写目录的缓存,就可以测试运行了

15-2. 真机调试 2(MD5码校验)(Android没测试)

修改为安卓平台,修改main.bytes中的服务器地址和AppConst的资源地址为电脑实际的地址,不能是127了
重新生成Xlua code、构建Android的ab包,然后拷贝到web服务器
rapidjson需要编译一个android的才能在安卓运行

https://blog.csdn.net/qq_36480278/article/details/109803833

发现对于不同分辨率下UI界面显示有问题,因此将TestUI修改为拉伸,Canvas设置为拉伸,参考分辨率1920*1080,修改UIManager中的UI组的建立

 public void SetUIGroup(List<string> group)
    {
        for (int i = 0; i < group.Count; i++)
        {
            GameObject go = new GameObject("Group-" + group[i]);
            RectTransform RT = go.AddComponent<RectTransform>();
            //将普通物体转化为适用于UI的RectTransform,并通过下面的组件设置为Stretch方法。
            RT.anchorMin = Vector2.zero;
            RT.anchorMax = Vector2.one;
            RT.pivot = 0.5f * Vector2.one;
            RT.offsetMin = Vector2.zero;
            RT.offsetMax = Vector2.zero;
            go.transform.SetParent(m_UIParent, false);
            m_UIGroups.Add(group[i], go.transform);
        }
    }

为文件添加了MD5码校验

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

/// 
/// 构建工具类:
/// 创建了三种构建方法windows android ios。
/// Build方法:从所有路径查找文件,排除meta后,把每个文件名作为被打包资源名和bundle名(当然一个bundle可以打包多个文件),GetDependence获取所有文件的依赖文件,
/// 把这些信息写入到bundleInfos,BuildPipeline.BuildAssetBundles一下,就建好了ab包,然后把所有包的信息写入filelist中
/// 
public class BuildTool : Editor
{
    //如何使用Build呢,直接添加工具栏
    [MenuItem("Tools/Build Windows Bundle")]
    static void BundleWindowsBuild()
    {
        Build(BuildTarget.StandaloneWindows);
    }

    //如何使用Build呢,直接添加工具栏
    [MenuItem("Tools/Build Android Bundle")]
    static void BundleAndroidBuild()
    {
        Build(BuildTarget.Android);
    }

    //如何使用Build呢,直接添加工具栏
    [MenuItem("Tools/Build IOS Bundle")]
    static void BundleIOSBuild()
    {
        Build(BuildTarget.iOS);
    }

    //为了能够构建多平台,需要把目标平台作为参数传入。
    static void Build(BuildTarget target)
    {
        //主要目的是收集这个build信息,需要打哪些文件,需要给bundle包用一个什么样的名字,BuildAssetBundles函数用到这个Build数组
        List<AssetBundleBuild> assetBundleBuilds = new List<AssetBundleBuild>();

        //文件信息列表
        List<string> bundleInfos = new List<string>();

        //第一步搜索出我们这个所有文件的文件名Directory.GetDirectories和Directory.GetFiles对应两种打包策略一个获取文件夹一个获取文件,GetFiles比较简单
        //searchPattern通配符,*是默认 https://www.cnblogs.com/ost/archive/2006/08/20/481625.html
        string[] files = Directory.GetFiles(PathUtil.BuildResourcesPath, "*", SearchOption.AllDirectories);
        //所有文件都找出来了,需要排除调meta文件
        for (int i = 0; i < files.Length; i++)
        {
            if (files[i].EndsWith(".meta") || files[i].EndsWith(".json"))
            { 
                continue; 
            }
            //创建一个需要build的Bundle
            AssetBundleBuild assetBundle = new AssetBundleBuild();

            //处理出来的路径斜杠可能不同。需要规范一下
            string fileName = PathUtil.GetStandardPath(files[i]);

            string assetName = PathUtil.GetUnityPath(fileName);//获取unity相对路径
            assetBundle.assetNames = new string[] { assetName };//assetBundle是一个相对路径文件名

            string bundleName = fileName.Replace(PathUtil.BuildResourcesPath, "").ToLower();
            assetBundle.assetBundleName = bundleName + ".ab";//Bundle需要后缀是.ab,,,,,,,,至此,Bundle的信息收集完了,需要放进list

            assetBundleBuilds.Add(assetBundle);
            
            //获取文件MD5
            string md5 = GetMD5(assetName);

            //添加文件和依赖信息
            List<string> dependenceInfo = GetDependence(assetName);
            //版本信息包括文件路径名、bundle名、依赖文件列表
            string bundleInfo = assetName + "|" + bundleName + ".ab" + "|" + md5;

            if (dependenceInfo.Count > 0)
                bundleInfo = bundleInfo + "|" + string.Join("|", dependenceInfo);

            bundleInfos.Add(bundleInfo);
        }

        //为什么不用另一个重载BuildAssetBundles(string outputPath, BuildAssetBundleOptions assetBundleOptions, BuildTarget targetPlatform),是因为需要自己去资源设置bundle名打标签,很麻烦
        //第二个参数把list转为array数组
        //第三个参数是压缩格式,选择默认
        //第四个参数是目标平台,先选择win
        if(Directory.Exists(PathUtil.BundleOutPath))
        {
            //判断是否有路径,如果有这个文件夹,就删掉文件,,递归recursive删掉所有文件和子文件。
            Directory.Delete(PathUtil.BundleOutPath, true);
        }
        Directory.CreateDirectory(PathUtil.BundleOutPath);//删除路径后,创建路径

        BuildPipeline.BuildAssetBundles(PathUtil.BundleOutPath, assetBundleBuilds.ToArray(), BuildAssetBundleOptions.None, target);

        //写bundle信息文件
        File.WriteAllLines(PathUtil.BundleOutPath + "/" + AppConst.FileListName, bundleInfos);
        //创建好文件后,在unity资源库中刷新一下
        AssetDatabase.Refresh();
    }

    private static string GetMD5(string path)
    {
        //var path = EditorUtility.OpenFilePanel("", "", "lua");
        FileStream file = new FileStream(path, System.IO.FileMode.Open);
        MD5 md5 = new MD5CryptoServiceProvider();
        byte[] retVal = md5.ComputeHash(file);
        file.Close();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < retVal.Length; i++)
        {
            sb.Append(retVal[i].ToString("x2"));
        }
        Debug.Log(sb.ToString());
        return sb.ToString();
    }
}
internal class BundleInfo
{
    public string AssetName;
    public string BundleName;
    public string md5;
    public List<string> Dependeces;
}
public void ParseVersionFile()
{
    //拿到版本文件路径
    string url = Path.Combine(PathUtil.BundleResourcePath, AppConst.FileListName);
    //对文件进行读取
    string[] data = File.ReadAllLines(url);

    //解析文件信息
    for (int i = 0; i < data.Length; i++)
    {
        BundleInfo bundleInfo = new BundleInfo();
        string[] info = data[i].Split('|');
        bundleInfo.AssetName = info[0];
        bundleInfo.BundleName = info[1];
        bundleInfo.md5 = info[2];
        //list特性:本质是数组,可动态扩容
        bundleInfo.Dependeces = new List<string>(info.Length - 3);
        for (int j = 3; j < info.Length; j++)
        {
            bundleInfo.Dependeces.Add(info[j]);
        }
        m_BundleInfos.Add(bundleInfo.AssetName, bundleInfo);

        //查找luaScripts下的lua文件,添加到luamanager中
        if(info[0].IndexOf("LuaScripts") > 0)
        {
            Manager.Lua.LuaNames.Add(info[0]);
        }

    }
}
private Dictionary<string, string> oldFileMD5;

/// 
/// 检查更新
/// 
private void CheckUpdate()
{
    m_DownloadCount = 0;
    //更新前,先从本地的可读写目录先获取旧的filelist文件信息,读取md5
    string oldFilelistPath = PathUtil.ReadWritePath + AppConst.FileListName;
    oldFileMD5 = new Dictionary<string, string>();
    if (FileUtil.IsExits(oldFilelistPath))
    {
        //对文件进行读取
        string[] data = File.ReadAllLines(oldFilelistPath);

        //解析文件信息
        for (int i = 0; i < data.Length; i++)
        {
            string[] infos = data[i].Split('|');
            string fileName = infos[1];
            string md5 = infos[2];
            oldFileMD5.Add(fileName, md5);
        }
    }

    //获取filelist再资源服务器上的地址
    string url = Path.Combine(AppConst.ResourcesUrl, AppConst.FileListName);
    DownFileInfo info = new DownFileInfo();
    info.url = url;
    StartCoroutine(DownLoadFile(info, OnDownLoadServerFileListComplete));
}

private void OnDownLoadServerFileListComplete(DownFileInfo file)
{
    m_DownloadCount = 0;//开始更新时重置下载文件数量
    //保存最新的filelist
    m_ServerFileListData = file.fileData.data;

    //获取资源服务器的文件信息目录
    List<DownFileInfo> fileInfos = GetFileList(file.fileData.text, AppConst.ResourcesUrl);
    //定义需要下载的文件集合
    List<DownFileInfo> downListFiles = new List<DownFileInfo>();

    //遍历资源服务器的文件信息
    for (int i = 0; i < fileInfos.Count; i++)
    {
        string localFile = Path.Combine(PathUtil.ReadWritePath, fileInfos[i].fileName);
        //判断本地是否存在,如果不存在就下载/或本地存在但MD5码不同,就要下载///如果没有旧MD5list说明以前没下载过
        if(!FileUtil.IsExits(localFile) || (oldFileMD5.Count >0 && FileUtil.IsExits(localFile) && fileInfos[i].md5 != oldFileMD5[fileInfos[i].fileName]))
        {
            //Debug.Log(localFile);
            fileInfos[i].url = Path.Combine(AppConst.ResourcesUrl, fileInfos[i].fileName);
            downListFiles.Add(fileInfos[i]);
        }
    }
    if (downListFiles.Count > 0)
    {
        StartCoroutine(DownLoadFile(downListFiles, OnUpdateFileComplete, OnUpdateAllFileComplete));
        loadingUI.InitProgress(downListFiles.Count, "Downloading new resources...");//当判断完本地没有哪些文件,服务器哪些文件需要下载后,更新进度条显示,传进去需要下载的文件数量
    }
    else
    {
        EnterGame();
    }
}

16. 自建服务器

https://www.cnblogs.com/kire-cat/p/16361589.html
使用natapp内网穿透后,将AppConst和mian.bytes的服务器地址修改为对应地址,即可从该地址访问到本机的文件并下载更新。
NetBox服务器将资源路径设置为127.0.0.1:80占用了80端口,而natapp的zhe1123.natapp1.cc指向了127.0.0.1:80这个端口,可以从这个域名和端口从本地服务器中读取文件并下载。
需要每次往NetBox中手动写入最新的GameServer域名和地址,让main.bytes连接到这个端口,获取数据。使用花生壳新建一个内网穿透专门用作映射服务器客户端的地址。因为只有花生壳的地址能分出host和port
image.png
image.png

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

public class GameServerManager : MonoBehaviour
{
    internal class DownFileInfo
    {
        public string url;
        public DownloadHandler fileData;
    }

    public string GameServerDomain;
    public string GameServerPort;

    IEnumerator DownLoadFile(DownFileInfo info, Action<DownFileInfo> Complete)
    {
        //老版本用WWW,现在已经弃用
        //新版需要使用UnityWebRequest,,,,,引入UnityEngine.Networking
        UnityWebRequest webRequest = UnityWebRequest.Get(info.url);
        //下载一个文件,等待,下载完继续执行
        yield return webRequest.SendWebRequest();

        //if(webRequest.isHttpError || webRequest.isNetworkError)
        if (webRequest.result == UnityWebRequest.Result.ProtocolError || webRequest.result == UnityWebRequest.Result.ConnectionError)
        {
            Debug.Log("下载文件出错:" + info.url);
            yield break;
            //下载失败,重试,有次数限制
        }

        //yield return new WaitForSeconds(0.2f);
        
        //下载完成后,给info赋值
        info.fileData = webRequest.downloadHandler;
        //如果下载的是filelist,直接解析,用webRequest.downloadHandler.text.
        //如果是bundle,可以写入,用webRequest.downloadHandler.data
        Complete?.Invoke(info);
        //下载完成后释放掉。
        webRequest.Dispose();
    }
    
    //下载完GameServer文件后,解析其中的host和port
    private void OnDownLoadGameServerFileComplete(DownFileInfo file)
    {
        string content = file.fileData.text;
        string[] files = content.Split(":");
        GameServerDomain = files[0];
        GameServerPort = files[1];
        Debug.Log(GameServerDomain + ":" + GameServerPort);
    }

    //作为事件被注册到时间中心,在Start中运行一次,拿到最新的服务器的域名和端口
    public void GetGameServerHostPort(object args)
    {
        //获取filelist再资源服务器上的地址
        string url = Path.Combine(AppConst.GameServerUrl, AppConst.GameServerFileName);
        DownFileInfo info = new DownFileInfo();
        info.url = url;
        StartCoroutine(DownLoadFile(info, OnDownLoadGameServerFileComplete));
    }

    private void Start()
    {
        Manager.Event.Subscribe((int)GameEvent.GameServer, GetGameServerHostPort);
        Manager.Event.Fire((int)GameEvent.GameServer);
    }
    
    public void OnApplicationQuit()
    {
        Manager.Event.UnSubscribe((int)GameEvent.GameServer, GetGameServerHostPort);
    }
}

public static GameServerManager GameServer
{
    get { return _gameServer; }
}

public void Awake()
{
    _resource = this.gameObject.AddComponent<ResourceManager>();
    _lua = this.gameObject.AddComponent<LuaManager>();
    _ui = this.gameObject.AddComponent<UIManager>();
    _entity = this.gameObject.AddComponent<EntityManager>();
    _scene = this.gameObject.AddComponent<MySceneManager>();
    _sound = this.gameObject.AddComponent<SoundManager>();
    _event = this.gameObject.AddComponent<EventManager>();
    _pool = this.gameObject.AddComponent<PoolManager>();
    _net = this.gameObject.AddComponent<NetManager>();
    _gameServer = this.gameObject.AddComponent<GameServerManager>();
}
public enum GameEvent
{
    GameInit = 10000,
    StartLua,
    GameServer,
}

public class AppConst
{
    //GameServer地址
    public const string GameServerUrl = "http://zhe1123.natapp1.cc/GameServerUrl/";
    public const string GameServerFileName = "GameServer.txt";
}
function Main()
  msg_mgr.init();--加载所有message模块
  Manager.Net:Init();--初始化TCP客户端
  --Manager.Net:ConnectServer("192.168.241.1", 8000);--连接网络
  host = Manager.GameServer.GameServerDomain
  port = Manager.GameServer.GameServerPort
  print(host..":"..port)
  Manager.Net:ConnectServer(host, port);--连接网络
  --连接网络ConnectServer调用的NetClient的OnConnectServer,设置端口
  --在调用OnConnect中GetStream和BeginRead获取网络数据流,读取到数据调用OnRead,OnRead中有ReceiveData解析数据
  --解析完继续BeginRead,获取后面的数据


  --print("hello main")
  Manager.UI:OpenUI("TestUI", "UI", "ui.TestUI")
  --Manager.Scene:LoadScene("Test01","scene.Scene01")
end

打开NetBox后,开启natapp和花生壳,删除Unity缓存路径下载的热更新资源,发布exe包前记得修改GameMode为Update,发布exe后,运行,就可以热更新~

演示效果:

Unity Xlua热更新Demo:仿塞尔达风格

本框架至此结束~

你可能感兴趣的:(Xlua,Unity,unity,服务器,lua)