• 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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    public enum GameEvent
    {
        GameInit = 10000,
        StartLua,
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    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);//立刻退出太突兀了,适当延时
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159

    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);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    为文件添加了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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    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]);
            }
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    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);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    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>();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    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";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

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

    演示效果:

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

    本框架至此结束~

  • 相关阅读:
    大厂日常模型测试要求
    【LSSVM回归预测】基于matlab人工蜂群算法优化最小二乘支持向量机LSSVM数据回归预测【含Matlab源码 2213期】
    AtCoder Beginner Contest 264 部分题解
    Spring 的事务实现方式有哪些?
    基于ssm的医院住院病房管理系统
    java计算机毕业设计新闻网站源码+系统+mysql数据库+lw文档+部署
    三分类网络的物理意义是什么?
    Shell入门1
    Python趣味算法入门 - 打鱼还是晒网
    计算机竞赛 机器视觉opencv答题卡识别系统
  • 原文地址:https://blog.csdn.net/weixin_42264818/article/details/128211474