返回 登录
4

Unity3D实战之多线程下载资源

在游戏开发中经常会使用多线程去处理,比如在3D游戏开发中,对于大地形的加载,通常的方式是使用分块的方式,以前做过3D游戏引擎用于加载魔兽世界地图场景,使用的技术就是多线程加载,在内存里放置9块地形,在缓存里面放置16块地形用于与内存进行地形块的交换。还有以前开发过的端游,主线程负责地形的加载,单独开一个线程用于地形的卸载。在Unity开发中,Unity它本身只有一个主线程运行,也就是单线程,但是可以在Unity中使用多线程加载资源。运用多线程的条件是如果同时要处理很多事情并且与Unity的对象没有交互可以用thread多线程,否则只能使用协成coroutine。在多处理器的计算机上可以做到多个线程的真正的同步,在Unity中,你仅能从主线程中访问Unity的组件对象和Unity系统调用,任何企图访问这些对象的第二个线程都将失败并引发错误,这是一个要重视的限制。下面介绍一下本节实现多线程的思路,并实现了对资源的断点续传。
本节使用的多线程是用于处理游戏资源的下载,意思是在程序运行时,在后台开启一个线程在用户不知情下,下载游戏资源。首先将需要下载的资源打包用zip压缩,主要目的是减少资源包体的大小,然后借助工具生成md5 的加密文件,名字为VersionMD5.xml,如果游戏资源有所改变,要重新命名生成的加密文件为VersionMD5_1.0.xml,并将其放到FTP资源服务器上。
游戏启动时,首先判断是否已连接WiFi,如果已开启,自动启动资源下载,程序会在游戏后台打开线程去下载资源,先从服务器上下载版本控制文件VersionMD5_1.0.xml,对比本地文件的VersionMD5.xml通过文件名和版本号对比,确定是否需要更新资源,如有不同加入下载列表。其次用户在启动游戏时,没有打开WiFi,要保证游戏在一定时间内能正常运行,等运行到游戏需要加载服务器资源时,如果本地无资源加载就开启强制下载,开启数据流量使用提醒下载结束后游戏就可以继续运行了。多线程下载流程图如下:
图片描述
在这里只将重点部分,如果想完整的看到可以购买《Unity3D实战核心技术详解》一书,电子工业出版社。
开始进入资源下载阶段,使用函数StartDownLoad开始启动资源下载,首先下载配置文件,本地的配置文件会与服务器的配置文件做对比决定下载哪个资源文件,在该函数中调用了LoadVersion函数,先处理版本配置文件,如下所示:

IEnumerator LoadVersion () {
        isDownload = true;
        //清空变量
        //LocalResOutVersion.Clear();
        ServerResVersion.Clear();
        NeedDownFiles.Clear();
        string serverMainVersion = "";
        //读取本地配置文件
        string localVersion = System.IO.Path.Combine(LOCAL_RES_OUT_PATH , MAIN_VERSION_FILE);
        if ( File.Exists(localVersion) )
            Instance.ParseLocalVersionFile(localVersion , LocalResOutVersion);
        //取得服务器版本
        string versionUrl = SERVER_RES_URL + "VersionNum/" + MAIN_VERSION_FILE;
        WWW sw = new WWW(versionUrl);
        yield return sw;
        if ( !string.IsNullOrEmpty(sw.error) )
            Debug.LogError("Server Version ..." + sw.error);
        else {
            serverMainVersion = sw.text;
            Debug.Log(serverMainVersion);
            //serverVersionByte = sw.bytes;
            ParseVersionFile(serverMainVersion , ServerResVersion);
        }

        if ( string.IsNullOrEmpty(sw.text) ) {
            Debug.LogError("无法连接服务器");
            //CompareLoacalVersion();
            //加载下一个场景
        } else//可以链接服务器
        {
            //对比本地和服务器的配置
            CompareServerVersion();
            //开启下载
            //CoroutineManager.DoCoroutine(DownLoadResByWWW());
            DownLoadResByThread();
        }
    }

配置文件对比完成后,确定开始启动线程下载资源,调用函数DownLoadResByThread下载,函数如下所示:

    public void DownLoadResByThread () {
        downloadThread = new Thread(new ThreadStart(DownFile));
        downloadThread.Start();
    }
该函数主要是起动一个线程,在这个线程里面调用函数DownFile去下载资源并且具有断点续传功能,也是该代码的核心部分,函数中都有注释,读者可以自行查看。下面把断点续传内容重点解释一下:使用的是HttpWebRequest和HttpWebResponse 作为资源请求使用的,获取到服务器请求后,可以拿到资源流,根据流的大小判断是否下载完整,如果不完整可以做个记录,继续下载,在最后又调用了一次DownFile()函数,函数迭代进行,直到资源下载完成,以下是处理断点续传的代码:
HttpWebRequest request = null;
        HttpWebResponse response = null;
        Stream ns = null;
        string test = "";
        try {
            //本地未下载完成
            if ( !isRight ) {
                request = (HttpWebRequest)HttpWebRequest.Create(serverPath);
                if ( curFileNum > 0 )
                    request.AddRange((int)curFileNum); //设置Range值
                response = (HttpWebResponse)request.GetResponse();
                //向服务器请求,获得服务器回应数据流
                ns = response.GetResponseStream();
                byte[] nbytes = new byte[1024];
                int nReadSize = 0;
                nReadSize = ns.Read(nbytes , 0 , 1024);
                while ( nReadSize > 0 ) {
                    fs.Write(nbytes , 0 , nReadSize);
                    nReadSize = ns.Read(nbytes , 0 , 1024);
                    curFileNum += nReadSize;
                    //Debug.Log(DownloadByte);
                }
                isRight = true;
                fs.Flush();
                fs.Close();
                ns.Close();
                request.Abort();
            }
            UpdateLocalVersionTemp(fileName , true , false);
            //解压(防止,更新下载不全,解压报错的文件占坑)
            if ( File.Exists(localUnZipPath) ) {
                File.Delete(localUnZipPath);
            }
            CompressUtil.DeCompress(localPath , localUnZipPath , null);
            UpdateLocalVersionTemp(fileName , true , true);
            updatedNum++;
            Debug.Log("down " + updatedNum + "/" + totalNeedUpdateNum + "," + fileName + "Loading complete");

        } catch ( Exception ex ) {
            if ( fs != null )
                fs.Close();
            UpdateLocalVersionTemp(fileName , false , false);
            isRight = false;
            Debug.Log(ex.ToString());
            //解压出错,删除下载文件
            if ( File.Exists(localPath) ) {
                File.Delete(localPath);
            }
            //StartDownLoad();
        } finally {
            if ( ns != null ) {
                ns.Close();
                ns = null;
            }
            if ( response != null ) {
                response.Close();
                response = null;
            }
            if ( request != null ) {
                request.Abort();
                request = null;
            }
            DownFile();
}

该代码脚本可以直接挂到对象上,完成资源的断点续传,断点续传主要是用于减少包体的大小,可以在玩家玩游戏的过程中,在后台起一个线程完成后续资源的下载,为了防止在下载的过程中由于网络不好,出现资源下载不完整,采用了断点续传技术,可以避免问题的发生,这样在玩家不知情的情况下就完成了游戏资源的下载。

笔者简介:姜雪伟个人主页

评论