客户端工程师的分工

前几天,和项目组的客户端负责人、主程聊了一下,了解了一下客户端工程师的人员分工。目前该项目组使用双塔模式,1主程+1技术专家,1资深员工,1高级员工,1中级员工,若干初级员工,分工大体如下:

  • 接sdk:中级工程师负责,虽然这一块比较枯燥而且看上去难度不大,但是做的好的话,可以把这套流程缕顺,很好的预防问题和节约大量时间)。
  • 各种系统:由资深工程师带着初级工程师负责,当然大部分工作是初级工程师完成的,比如ui之类的,反正就是业务逻辑,属于功能性的,但是也有复杂的模块,新手指引之类的(当玩家按照新手指引去操作,当然没问题,但是如果出现非常规操作,比如切换到后台后再恢复,重启app等,这就需要复杂的逻辑了)。
  • 系统的架构:高级工程师负责,把各个系统轮岗一遍了就可以做这个了,不要小看这个,把模块划分好了,架构架设好了,代码就清晰了,bug就好找了。
  • 内存管理和资源管理:主程和技术专家负责,这一块是项目的基石,看上去两个相同的游戏,包体大小不一样,内存大小不一样,加载速度不一样,流畅度不一样,这一块需要研究过、经历过很多案例,切身做过深刻思考。主程负责搭架构,技术专家负责优化。
  • 战斗:客户端主程或者服务器主程负责,最核心的模块。
  • 渲染和优化:技术专家负责。手机游戏从2012年开始爆发,最开始只要产品做出来就行,发展到现在,对画面有了很高的要求。复杂的画面和优秀的性能是反义词,这就要求技术专家具有深厚的图形学技术积累,尽量做到用优质的图形学算法实现绚丽的效果。

之前有一篇文章说优化,但那篇文章更多的是说优化的流程,所以,现在单独拿出来一篇文章来说资源管理。

这篇文章估计又会很长,甚至不会完结,所以有兴趣转载的话,请注明出处:电子设备中的画家|王烁 于 2017 年 11 月 6 日发表,原文链接(http://geekfaner.com/unity/blog8_Resource.html)。不然转载了一篇没结束的文章,总是不太好的,同样情况适用于我网站中的任何一篇文章。

名词解释

  • Application.dataPath:返回程序的数据文件所在的文件夹路径。
  • Windows:D:/UnityProject/Geekfaner/AFunnyDemo/AFunnyDemo/Assets

  • Application.persistentDataPath:返回一个持久化数据存储目录的路径,可以在此路径下存储一些持久化的数据文件。一般是程序所在平台的固定位置,适用于存放程序在运行过程中产生的数据文件。
  • Windows:C:/Users/XXX/AppData/LocalLow/DefaultCompany/AFunnyDemo

  • Application.streamingAssetsPath:返回数据流的缓存目录,适用于存放一些外部数据文件,比如AB。
  • Windows:D:/UnityProject/Geekfaner/AFunnyDemo/AFunnyDemo/Assets/StreamingAssets

  • Application.temporaryCachePath:返回一个临时数据的缓冲目录。
  • Windows:C:/Users/XXX/AppData/Local/Temp/DefaultCompany/AFunnyDemo

  • AssetBundle.LoadFromFileAsync:从disk中,通过路径从文件异步加载一个AB。支持压缩格式的bundle,比如lzma压缩格式,数据会被自动解压缩到内存中,chunk压缩格式或者未压缩的数据可以直接从disk中被读取。是读取AB的最快方式。
  • this.m_createRequest = AssetBundle.LoadFromFileAsync(path);

  • AssetBundle.LoadFromFile:从disk中,通过路径从文件同步加载一个AB。支持压缩格式的bundle,比如lzma压缩格式,数据会被自动解压缩到内存中,chunk压缩格式或者未压缩的数据可以直接从disk中被读取。与上面AssetBundle.LoadFromFileAsync唯一的区别就是同步的,AB被加载后才会返回。
  • AssetBundle ab = AssetBundle.LoadFromFile(path);

  • AssetBundle.LoadAssetAsync:从AB中,通过名字异步的读取asset。
  • m_Request = bundle.m_AssetBundle.LoadAssetAsync(m_AssetName, m_Type);

  • AssetBundle.LoadAsset:从AB中,通过名字同步的读取asset。
  • T asset = bundle.m_AssetBundle.LoadAsset<T>(assetName);

  • AssetBundle.Unload:从内存中释放AssetBundle文件。当参数为true的时候,包括从AB中load的asset也会被删除,如果这个asset被挂在在gameobject上,那么则显示missing。当参数为false的时候,从AB中load的asset会被保留,但是无法再从AB中load新的asset了。没被删除的asset只能通过Resources.UnloadAssetResources.UnloadUnusedAssets释放。
  • m_AssetBundle.Unload(false);

  • Resources.Load:从Assets文件夹中的任何一个Resources文件夹中通过path指定,读取asset。
  • asset = Resources.Load(path);

  • Resources.LoadAsync:从Assets文件夹中的任何一个Resources文件夹中通过path指定,异步的读取asset。返回一个ResourceRequest,当加载结束的时候能获取到一个asset。
  • m_request = Resources.LoadAsync(m_path, m_type);

  • Resources.UnloadAsset:将asset从内存中卸载,只有存放在disk中的asset可以使用这个函数。如果场景中的任意GameObject引用了这个asset,那么当该资源再次被访问到的时候还会被重新加载。
  • Resources.UnloadAsset(asset);

  • Resources.UnloadUnusedAssets:将unused的asset从内存中卸载。遍历所有gameobject的方式,如果该asset没有,就会认为是unused。当该资源再次被访问到的时候还会被重新加载。一般在切换场景的时候调用两次,一次为引擎在切换场景时自动调用,另一次则为用户手动调用(一般出现在场景加载后,用户调用它来确保上一场景的资源被卸载干净) 如果用如果户对加载后的资源进行了储存(比如放到Container中),但在场景切换时并没有将其Remove或Clear,就会造成资源泄露)。建议研发团队密切关注加载模块中Resources.UnloadUnusedAssets的调用频率,尽可能避免较短时间该函数的重复调用。对于长时间停留在一个场景中的项目(比如MMO游戏)来说,可尝试间隔一定时间(比如15分钟甚至更长时间)来主动触发一次
  • Resources.UnloadUnusedAssets();

  • Object.Instantiate:相当于Ctrl+D。如果一个游戏物体,组件或脚本实例被Clone,实例将克隆整个游戏物体层次,以及所有子对象也会被克隆。这个函数保留被克隆物体的位置和旋转,所有游戏物体被激活。当具体Instantiate一个资源时,会对此资源的Asset资源进行Clone+引用操作。Clone的资源包括GameObject、Tranform。引用的资源包括Texture、TerrainData、Shader。引用与Clone同时存在的包括 Mesh、material、PhysicMaterial、noxss。引用的资源只是指针的指向。Clone的会重新生成新的内存。当GameObject Destroy的时候,只会释放生成这个资源时Clone的那部分内存。Asset内存和AssetBundle的镜像内存都没有被释放。场景切换的时候,Instantiate Clone的那部分内存和Asset内存都会被释放掉,AB不会被释放。这里参考了一篇文章Unity资源加载与释放
  • GameObject go = Instantiate(prefab) as GameObject;

  • File.ReadAllText:打开一个文本文件,读取文件的所有行,然后关闭该文件。
  • curResVersioh = int.Parse(File.ReadAllText(successPath));

  • File.WriteAllText:创建一个新文件,在文件中写入内容,然后关闭文件。如果目标文件已存在,则改写该文件。
  • File.WriteAllText(successPath, Version.ResVersion.ToString());

  • File.Copy:将现有文件(参数1)复制到新文件(参数2)。
  • File.Copy(m_serverlistTmpPath, m_serverlistPath, true);

  • File.Delete:删除指定文件。
  • File.Delete(m_serverlistTmpPath);

  • File.Exists:查看文件是否存在。
  • File.Exists(patchFile)

  • File.OpenWrite:打开一个现有文件或创建一个新文件以进行写入。返回一个FileStream。
  • m_fs = File.OpenWrite(m_path);

  • FileStream.Seek:将该流的当前位置设置为给定值。
  • m_fs.Seek(m_startPos, SeekOrigin.Current);

  • FileStream.SetLength:将该流的长度设置为给定值。
  • m_fs.SetLength(postion);

  • FileStream.Close:关闭当前文件流并释放与之关联的所有资源(如文件磁盘缓冲区)。注意:创建文件流后,无论是读取还是写入操作,最后必须用Close方法关闭文件流!这样做的目的一方面是释放文件操作所占用的系统资源,另一方面避免文件有出现无法预料的结果。
  • m_fs.Close();

  • UnityWebRequest:为了代替WWW,以满足HTTP通信的需求。
  • UnityWebRequest request = UnityWebRequest.Get(fullURL);

  • UnityWebRequest.SetRequestHeader:断点续传
  • request.SetRequestHeader("Range", string.Format("bytes={0}-", m_downloadHandler.StartPos));

  • UnityWebRequest.Send:异步的下载远程server中的内容。
  • yield return request.Send();

  • WWW:访问网页。通过调用www在后台开始进行下载,返回一个WWW对象。通过yield的方式异步等待下载结束。主要用于需要从网站服务器获取数据。支持Get和Post两种模式,默认为Get。
  • WWW www = new WWW(url);

  • WWW.Dispose:断开一个WWW对象。用法很多,比如断开一个网络游戏。
  • www.Dispose();

资源使用流程

  • 真机默认使用AB模式,安装包中的AB资源会被放到StreamingAssetsPath路径下(原始AssetBundle存放路径),使用热更新下载(API UnityWebRequest)下来的AB资源会被放到persistentDataPath路径下(补丁AssetBundle存放路径,优先从这个路径加载资源)
  • 针对当前平台,通过API AssetBundle.LoadFromFileAsync加载AssetBundleManifest所在的AB(优先从补丁目录加载AssetBundle),由于该API为异步加载,所以多次轮询确定AB已经被加载成功后,再通过AssetBundle.LoadAssetAsync从该AB中异步的加载出AssetBundleManifest文件。
  • 可以通过API AssetBundle.LoadFromFile同步的加载AB,然后再通过AssetBundleManifest文件判断其依赖文件的加载情况。判断依赖文件都加载完毕后,可以通过API AssetBundle.LoadAsset同步的将asset获取出来。
  • 可以通过API AssetBundle.LoadFromFileAsync异步的加载AB,由于该API为异步加载,所以多次轮询确定AB已经被加载成功后,再通过API AssetBundle.LoadAssetAsync从该AB中异步的将asset获取出来。
  • 可以通过API Resources.Load从Assets文件夹中的任一Resources文件夹中同步的加载asset。
  • 可以通过API Resources.LoadAsync从Assets文件夹中的任一Resources文件夹中异步的加载asset,当m_request.isDone为true的时候,asset = m_request.asset。
  • 如果被加载的asset是prefab,那么需要先通过API Object.Instantiate对其进行Instantiate。需要注意的就是内存占用,因为可以针对一个asset instantiate很多份资源,那么可以通过缓存池减少instantiate的开销,但是这样就会带来SetActive的问题。不适用的GameObject回到缓存池需要SetActive(false),复用缓存池的GameObject需要SetActive(true)。
  • 无论是通过AB模式,还是Resources模式,从disk读取到内存的资源(AB/asset),要注意释放,其中AB只能通过API AssetBundle.Unload释放(目前我们的项目只有在游戏退到登陆界面的时候才使用),asset可以通过API AssetBundle.Unload(true)(一般不这么做)释放,或者通过API Resources.UnloadAssetResources.UnloadUnusedAssets或者场景切换的时候释放。

Unity资源加载与释放(转载自liudq__,链接:http://www.cnblogs.com/liudq/p/5550273.html)

AssetBundle 与 Resources的区别

Resources相当于Unity一个缺省的AssetBundle。

AssetBundle可以在使用时动态加载。(网易的荒野求生,就采取了打开游戏的时候只加载部分AB的方式,剩余的AB可能是在用到的时候再加载)

Resources.load();在没有第一次Instantiate之前没有完全加载Asset资源。所以相对AssetBundle去实例一个资源,Resources会在第一次Instantiate时出现卡顿现象。(Patrick:这个蛮有意思的,真的么?需要验证。)

AssetBundle 加载与释放

AssetBundle的加载方式有:AssetBundle.LoadFromFile(“”); AssetBundle.LoadFromMemorty(byte[]);及WWW方式。

AssetBunddle.LoadFromFile(“”);只有在standalone上可以使用。WWW在加载AssetBundle时,会在内部创建assetBundle实例。

当AssetBundle被加载到内存后,其实内存中只有AssetBundle文件的镜像内存。//todo 该镜像文件是AssetBundle原文件大小?(Patrick:确实是todo,需要验证。)

当需要具体某个Prefab等资源时,使用assetbundle.Load(“”);加载具体资源。此时内存中会存在此资源的 GameObject、shaders、材质、贴图等内存。当具体Instantiate一个资源时,会对此资源的Asset资源进行clone+引用操作。Clone的资源包括GameObject、Tranform。引用的资源包括Texture、TerrainData、Shader。引用与Clone同时存在的包括 Mesh、material、PhysicMaterial、noxss。引用的资源只是指针的指向。Clone的会重新生成新的内存。

根据AssetBundle资源的加载方式,当资源释放时,如果只是Destroy,只会释放生成这个资源时Clone的那部分内存。assetBundle.Load(“”);加载的Asset内存没有被释放,AssetBundle的镜像内存也没有被释放。所以如果在该资源被销毁时需要使用Resources.UnloadUnuseAssets();去释放没有被使用的Asset资源内存。如果该AssetBundle不会被再使用可以使用AssetBundle.Unload(true)销毁内存。AssetBundle(true);代表销毁该AssetBundle镜像内存及所加载的Asset内存。参数为false时,代表只销毁镜像内存。

下面的图片可以更形象的理解AssetBundle的加载与释放。

Resources

备注

当场景在切换时,Unity 会自动把托管的内存释放,这其中包括Asset内存、Instantiate内存。但不会被释放的是AssetBundle镜像内存。

assetBundle.Load如果多次加载相同的资源,除第一次,其它都会返回第一次的对象。

虽然并非全部原创,但还是希望转载请注明出处:电子设备中的画家|王烁 于 2017 年 11 月 6 日发表,原文链接(http://geekfaner.com/unity/blog8_Resource.html)