unity开发小贴士之十 项目优化

原文连接:python

https://zhuanlan.zhihu.com/p/36930662android

因此这篇博客的主题仍是以此次分享的核心内容为主线,顺便把PPT的内容也提供出来,可能添加少许当时没聊的私货(以引用块或者(补)的方式标注,以方便听过度享的朋友阅读)。ios

0. 题目和提纲

此次分享的题目是《Connecting The Dots——基于团队的持续优化之道》。优化是一件琐碎而又繁复的事情,它一般是一个又一个的点,而咱们若是要让一个好比说30人甚至50人的大型团队,花1到2年的时间去开发一款大型的手游项目,不但须要了解和掌握这些优化的细节,并且要借助团队的力量在整个游戏开发的生命周期过程当中不断地进行持续优化,才可以让优化效果能够保持下去。git

在聊具体的分享内容以前,我想先抛出这样一个问题——在优化这样的过程当中,程序应该承担一个什么样的角色?程序员

这也是和我刚才所想要聊的持续优化关联性很是大的问题。在优化过程当中,程序来可能有两种角色,一种是像救火员这样的角色,哪里冒火了,就去扑灭它。我本身也有过做为救火员去作优化的经历。另一种工做方式是像园丁同样,可能不须要特别紧急地在最后关头才处理那些性能问题,而是从项目的初期就开始规划整个项目的性能指标,而后让整个团队能够按照指标的要求合理地产出美术资源、编写代码,从而让整个产品一直处于一个比较健康的状态。github

救火员的职责颇有挑战性,同时也比较容易作出成就感,好比我也过用1到2周时间让游戏的性能有很是大提高的经历。而园丁在作的事情可能看上去比较普通,都是些平常且琐碎的事情。在游戏开发中,这两种角色并不冲突,而是同时存在的。可是在个人理解里,对于性能更友好的状态,是让团队里的成员都在以一个园丁的角色在工做,从根本上来保持游戏的性能一直处于一个较好的状态。由于——编程

每个神级优化的背后,都隐藏着一个2B的bug!

那此次分享我就想从这样三个方面来聊一下我所理解的以“园丁”的角色来让团队进行继续优化的方式。xcode

首先是美术资源的优化,美术资源是一个会对客户端运行效率产生很是重要影响元素之一,我想和你们聊一下,怎么样让咱们团队能够在美术资源产出方面一直保持一个比较好的状态。而后我想回归到程序的本职工做,从一个更加宏观的角度来讨论一下程序代码的部分要怎么样去作一些优化,对于代码质量和一些底层模块的设计,如何去作一些提早的思考和设计。第三部分,团队开发效率的优化,这块可能看上去跟性能优化的题主没有直接的关系,可是在我看来优化不只仅是游戏运行效率优化,并且应该包括整个团队开发效率的优化,这也是在项目开发过程当中很是重要的部分。性能优化

1. 美术资源优化

首先来看一下美术资源优化的部分。服务器

你们在平常开发中能够感觉到,对于美术资源部分,美术和程序,尤为是要进行性能优化的程序,他们的关注点会有不一样。美术更多的是关注美术效果是否是足够的好,而程序可能更多的关注美术资源在设备上的运行效率是否能够达到目标帧率,这其中就有一些目标导向上的冲突。我认为,解决这一冲突的一个很是重要的手段是尽早来创建合理的美术资源制做规范。

这实际上是一个美术资源制做的过程,首先经过在立项时制做Demo来肯定适合的美术资源规范,而后经过给美术灌输效率意识、提供辅助工具让美术在制做资源的过程当中能够严格按照规范进行,最后是要借助QA的力量对产出的资源进行检查,确保达标。

1.1 规范制定

为何制定美术规范这么重要呢?我想借本身以前的一个项目中的真实经从来聊一下。

《无尽战区·觉醒》这款手游是我做为主程带领团队开发的第一款手游项目。当这个项目进行到中期的时候,收包了一批场景的资源。在使用这些资源的时候发现它们的运行效率明显偏低,Profile发现面数和Drawcall都超标了。面数很简单,使用减面工具进行减面便可,Drawcall也使用相似Unity的Static Batching的方式进行优化,可是结果发现一些场景的Drawcall只能从400减小到300多,并不能达到预期。仔细检查发现不少模型没法合并的缘由是使用了端用经常使用的一种贴图制做方法——四方连续的贴图。这种方式虽然可使用很小的贴图尺寸经过tiling制做出精细的效果,可是对于Drawcall敏感的手游来讲并不合适。最终项目又请了好几个外派美术进行资源的整合和制做,致使多花费了几个周时间和几十万的美术成本。虽然对于大公司来讲几十万的美术成本不算什么,可是我依然以为这是我做为一个主程的失职——由于没有提早和美术沟通好制做方法,致使了这样的问题发生。因此在出来创业的项目中,在项目最初期Demo制做完毕以后,客户端团队就和美术一块儿制做了很是详细的美术资源制做规范。

制定规范的步骤大体能够整理为以下的几个部分:

在制定美术规范的第一步是要进行游戏信息的收集,由于不一样的游戏类型以及镜头视角会对美术资源的制做产生很是大的影响,好比2.5D和3D自由视角的游戏是有不一样的制做和优化方式。

在收集了足够的信息以后,须要和美术敲定一些大的技术方向,好比线性空间、HDR和动态光影等。线性空间是团队应当提早关注的一个点,我我的的观点是在写实的游戏风格中,能上线性空间仍是尽可能使用线性空间,对于美术效果的提高仍是颇有帮助的。在正确的基础上,才更容易出正确的效果。Unity目前的版本和OpenGL ES 3.0的普及率,我的观点是彻底能够在移动平台上使用线性空间的。固然这也要考虑具体项目的内容以及开发团队成员的能力和经验等因素。HDR的开关在Unity中仍是比较简单的,但在性能方面对于带宽的影响比较大,收益也不小,建议追求效果的团队提早考虑。动态光影是咱们研发团队的美术特别想追求的一个效果,可是由于咱们项目今年要上线,并且要考虑低配的效果,因此程序一直卡着不让用场景使用全实时的动态阴影,而是尽可能使用烘焙的方案。

肯定目标机型就很少说了,考虑主流的高中低三档,建议提早购买一些设备方便后续的性能测试。我对于这三档的基本分类大体以下:

高档:大部分iOS设备,主流的安卓旗舰设备;
中档:少部分但愿支持的iOS设备,好比目前的iPhone 5s、6/6s,ipad mini等,1-3年前的旗舰以及1-2年的1500元左右的安卓设备;
低档:1-3年前的千元机。
这个也能够参考UWA测试中的机型选择。

接下来是要针对面数和drawcall这两个核心点,对总体的标准进行定义,并将这些标准分配到场景、角色、特效、UI等资源分类上。由于这些不一样类型的资源是由不一样的美术同窗产出的,他们之间一般不会去沟通各自的性能消耗,所以须要针对每部分进行规则的细化。

场景部分强调一下贴图“像素密度”的概念。在制做场景的时候须要提早定义贴图使用的像素密度,不然的会致使贴图精度太高或者不够等问题。所谓的“像素密度”是指在正常的游戏视角下,一个一立方米的Cube应当使用多少尺寸像素的贴图。这个在场景制做的初期应当定义好,美术才好去使用正确的贴图尺寸,不然到后期优化可能发现大量超标的贴图在使用,若是此时已经进行了贴图的合并,美术修改的工做量会很是大。

角色部分的规范须要把面数和drawcall的标准细化到每一个角色中,这时候须要和策划沟通指望的同屏显示人数,根据不一样的游戏类型会有很大的差别。好比《崩坏3》不会同时有不少战斗单元,所以能够给每一个角色很是高的精度,而像《御龙在天》这样的国战游戏,追求百人同屏的效果,对于每一个角色的消耗限制就会比较大。

骨骼数量的部分最好在前期和美术沟通好,好比手指骨骼的减小和合并,脚部骨骼的简化,来减小CS骨骼的数量,给飘带等Bone骨骼留出足够的空间。所以美术规范制定的过程并非单纯的程序给美术添加制做限制,而是一个和美术一块儿来讨论如何在有限的资源下制做出更好效果的过程。

UI制做规范是咱们初期没有特别关注的部分,也所以踩了一些坑:

1. UI组件数量较多致使加载顿卡。咱们有些复杂界面有上千个GameObject,几百个UI组件,这是很是恐怖的。咱们测试发现UI的Prefab加载的时间消耗在Unity 5.5.6上和UI组件的数量几乎成线性关系。这里补充一张咱们测试的GameObject数量和加载时长的测试结果图(横轴是GameObject个数,纵轴是单纯的Prefab加载的时间消耗,单位秒,测试环境:小米Max2):

所以建议其余团队提早考虑UI部分的异步加载和分块加载。(或者升级引擎到5.6.5+或者2017.3+,对于Prefab的加载速度有了很大的提高)

UI中的粒子特效建议提早考虑异步加载,咱们最初直接放在了UI的Prefab中,不但由于Unity不支持Prefab的嵌套致使维护麻烦,并且由于每个ParticleSystem的初始化都占用一个几乎固定的时长(咱们本身测试在PC上也大约有3ms左右),致使UI初始化的时候很是卡。使用一个间接的引用,就能够比较容易地作到既方便更新又能够异步加载。

最后针对标准,要进行真机的压力测试,而且在组内推广,造成你们都承认的美术资源制做规范。

1.2 美术资源制做

在肯定好美术资源的制做规范以后,就是须要在美术铺量制做的阶段让美术能够按照规范进行资源的制做,在这个阶段程序须要注意去作的事情我以为有两点:

  1. 帮助美术创建效率意识;
  2. 为美术提供便利的制做和检查工具。

在咱们这样的一个初创团队里,没有经验丰富的TA存在,并且有部分美术同窗以前来自外包团队,对于游戏运行时的效率意识比较薄弱,所以须要客户端程序同窗不断和美术沟通来灌输对于游戏运行效率的关注。一般的作法包括平常的沟通、规范和技术点的分享等。

而在提供便利的工具方面,除了教会美术使用Unity已经提供的那些性能检查工具(Batches和面数查看、Overdraw、Mipmaps、Wireframe等渲染模式)以外,咱们也为美术提供了很是多的开发和检查工具。

从截图中能够看到咱们为美术提供了大量的工具,选择几个来着重介绍一下:

1.2.1 场景镜头同步功能

在场景资源制做的时候,须要美术去关注的除了常规的Drawcall以外,还有Overdraw、Mipmap以及面数。Unity已经在Scene View下提供了这三种渲染方式的检查工具,可是在咱们的使用中发现,因为游戏运行时镜头规则的复杂和多变,致使美术在Scene View下没法准确判断是否存在资源不合理的状况。所以美术提出但愿能够在Game View下查看这些状态。

最初的时候咱们也是想在Game View下实现这些不一样的渲染方式,而且已经集成了OverDraw的检测方式,基于的也是钱康来建议的Unite2017/OverdrawMonitor。可是后来以为这种方式会影响美术对于游戏的操做,程序实现起来也要多花一些时间,好比Mipmap的实现效果就不是很满意。后来就想到另一个思路——在Scene View下来作镜头和Game View下的镜头同步。结果就很是简单,只须要为Game View下的Camera身上添加一个Component就好:

namespace ThorProfile { public class SyncSceneView : MonoBehaviour { #if UNITY_EDITOR private SceneView view = null; // Use this for initialization void Awake() { view = SceneView.lastActiveSceneView; } private void LateUpdate() { if (view != null) { view.LookAt(transform.position, transform.rotation, 0f); } } private void OnDestroy() { if (view != null) { view.LookAt(transform.position, transform.rotation, 5f); } } #endif } } 

核心的代码只有LateUpdate中的那一句:"view.LookAt(transform.position, transform.rotation, 0f);",实现的效果就是正常操做游戏,Scene View下的Camera能够一直跟随移动,视角和Game View下很是接近。(知乎对于gif上传支持不太好,放一张静帧图感觉下,有须要本身使用上述代码进行试验便可。)

1.2.2 批量烘焙功能(补)

Unity的烘焙在Unity 5.X的版本速度仍是比较低,咱们虽然为美术专门购买了CPU强劲的烘焙机,可是好比在制做大世界的时候,由于场景拆分得比较细因此须要一次性连续烘焙多个场景,因而为美术提供了批量烘焙的功能。以前在知乎上也有朋友问过相似问题,代码很是简单,直接贴一下,须要的自取好了:

 [MenuItem("美术工具/烘焙选中场景(同步)")] public static void BakeSelectedScenes() { Object[] selectedAsset = Selection.GetFiltered(typeof(SceneAsset), SelectionMode.DeepAssets); foreach (Object obj in selectedAsset) { string scenePath = AssetDatabase.GetAssetPath(obj); Debug.Log("开始烘焙场景: " + scenePath); EditorSceneManager.OpenScene(scenePath); Lightmapping.Bake(); EditorSceneManager.SaveOpenScenes(); //若是有更新Prefab的需求,能够放这里。 EditorSceneManager.SaveOpenScenes(); Debug.Log(scenePath + " 场景烘焙完成!"); } } 

使用同步的方式烘焙,会卡住Unity,可是烘焙速度应该会有些提高(没有对比过……)。

1.2.3 场景合法性检查(补)

咱们为美术添加了场景的合法性检查工具,由于咱们对于场景中的相机设置等有些特别的要求。这块跟具体项目相关,只罗列一下咱们在检查的内容以及要检查它们的缘由。

  1. 顶点格式,对于带有color等逻辑上不须要的数据的部分给出警告。由于咱们的场景是采用静态合批的,若是有资源不当心导入了color等不须要的顶点数据,会致使整个合并以后的mesh变大不少。
  2. 地形数据,咱们使用了T2M的工做流程,美术使用Terrain进行地表的制做,而后经过T2M插件导出成mesh,为了保留原始的编辑数据便于之后修改和调整,Terrain会保留在原始场景里,可是在发布场景里不容许带有这块数据,即便Disable也不行,会对场景加载带来额外负担。
  3. 光源参数,不容许设置光源的ShadowType的Resolution属性,必须为Use Quality Settings。在作UWA的深度优化的时候,咱们发现有些状况下ShadowMap的尺寸从8M增长到了32M,检查后发现是美术为了更好的效果本身调整了这个参数致使的。脱离程序高中低效果控制的配置是不被容许的。
  4. 物理碰撞体,在目前的手游上,咱们的但愿是尽可能少地使用物理,所以咱们不管是在寻路仍是在动态阻挡方面都尽可能少地使用物理,一样在场景里禁止摆放MeshCollider组件,对于其余类型的Collider组件也进行检查,对于咱们项目一般都是不须要的。
  5. 摄像机设置,咱们游戏中镜头是彻底由程序逻辑控制的,所以不容许在场景中遗留用于预览的Camera组件。

就像以前说的那样,这部分很是零散,一般是在项目开发中不断发现的各类问题经过统一的检查工具来让美术在上传以前进行自查,确保资源提交到svn上的时候就是正确的。

1.3 美术资源检查

在美术大量产出资源的过程当中,除了美术自查以外,还须要其余职位的同事同时对美术资源进行检查。

在咱们项目中,对于美术资源的检查主要有三个部分:

  1. 程序检查。程序会根据发现的性能问题进行针对性的检查,好比会使用Profile、FrameDebug工具等进行问题的排查。
  2. 咱们也是UWA产品的深度用户,购买了专业会员,几乎每月都会提交一份安卓版本的包让UWA团队帮忙进行在线的性能诊断与优化,频繁的优化周期内可能会每周提交一份包。去年的年末也邀请UWA团队来公司针对咱们项目进行了深度优化,发现和解决了很多美术资源的问题。
  3. QA每周的性能测试。每周QA团队会在周版本以后,生成《性能测试报告》,以邮件的方式发送到全公司全部人的邮箱里。

这里以咱们的《性能测试报告》为例来讲一下QA团队进行性能监测的内容。

1.3.1 包体大小监控

咱们经历过在测试上线以前要进行包体大小优化的状况,所以将包体大小的监控放到了每周的性能测试报告里。

咱们会统计总体包体大小、资源占用大小、按照场景、UI、角色、特效等分类以后统计各自的大小,以及若是包体大小有变更,主要缘由是什么。这部分使用的工具主要是基于打包时候产生的中间文件来统计分析。以下图所示,左侧是按照文件夹分类的列表,右侧是选中的节点下的全部文件,能够进行排序、关键字过滤,以及多选统计等操做。

在进行资源大小统计和分析方面,咱们也开发了一套在Unity内的根据资源的引用关系进行分析的工具,统计对象为全部要打包出去的资源,能够查看资源之间的引用关系、被引用关系、资源消耗统计(粒子系统数量、GameObject数量等)。借助这个工具能够方便地进行一些再也不须要的资源筛查等分析工做。

1.3.2 游戏帧率统计

另一块须要持续关注的是游戏在设备上的运行帧率。

咱们会分别在高配和低配机器上对战斗外、不一样的战斗类型进行帧率的统计,并和以前的记录进行对比。

1.3.3 具体资源的检查

在具体资源的检查方面,由程序提供尽可能简便的测试和统计工具,由QA进行自动化的检测,主要包括场景资源检查、场景合法性检查、技能特效检查、UWA GOT性能数据等部分。

场景部分主要是统计Batch和面数。咱们制做了一个工具,按照填写的检查点在默认镜头下统计四个方向的数据,同时截图记录。这个工具也提供了跳转到指定坐标直接查看检查的功能。

前文已经介绍了场景的合法性的部分,这里是由QA每周进行一次合法性检查,对于不合法的场景反馈给美术进行修改。

特效检查主要集中在Drawcall、描述以及粒子数量这几个比较常规的方面,一样会列出不合格的特效让美术修改。

对于特效的Overdraw的统计,咱们是针对技能进行的,由于技能释放过程当中会有镜头的轨迹,所以这种方式更加合理。工具的功能是逐个释放技能,而后记录技能释放过程当中最大的那帧Overdraw的数据和截图,方便美术排查。

咱们也让QA团队在时间充裕的状况下使用UWA的GOT工具进行性能数据的记录,方便程序进行对比和检查。对于内存的统计数据也会从Overview测试中获取。

PPT截图的右下角是工具的制做者在咱们组内的外号。标注在这里也说想说明这些工具的开发工做是整个团队一块儿来分工协做完成的,而不只仅是分享者的功劳。

1.4 小结

第一部分的最后,咱们作一下小结。通过规范制定、规范执行以及资源检查这些步骤,美术资源的优化就造成了一个闭环,这个闭环中的各个环节是由不一样职位的同事协做来完成的,程序在其中起到了穿针引线的做用。在规范制定阶段,是由程序和美术主导,同时注意从策划和运营等处收集游戏设计的信息;在美术制做阶段的主角是美术,程序则为美术提供更好用的检查工具辅助美术自查;资源检查阶段由QA主导,程序负责提供便利的自动化检查工具,美术则须要对发现的问题进行修正,同时这个阶段的检查结果也可能反馈到规范制定的内容上,对一些规范细节可能会进行修正和调整。

2. 程序代码优化

在聊完美术资源的优化以后,咱们回归到程序的部分,从一个比较宏观的角度来看一下程序代码的优化部分。

Donald Knuth在他的一篇文章里说:

We should forget about small efficiencies, say about 97% of the time: premature
optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%.

这段话是那句颇有名的——“过早优化是万恶之源”的出处。首先我很赞同这句话,在没有必要的状况下进行盲目的优化浪费时间并且有可能有负面做用,可是正如这句话后半句所说,在少许的状况下,咱们也不该该放过那些会产生严重影响的机会,而这些地方,每每是踩了坑以后才知道的,好比前文所说的美术大量使用四方连续的贴图这样的例子。因此这部分我想聊的主题内容着眼在“过早优化是万恶之源”的另外一面,在程序中越早关注我以为收益越高的部分。

  1. 底层模块。这点比较容易理解,即使是在作好层次划分的状况下,越底层的模块对于上层的影响越大,所以早期花费较多的时间和精力在重要的底层模块上,对于后期的优化可能会有意想不到的收益。
  2. 代码质量。我以为保证团队的代码质量,对于优化和开发效率有着潜移默化的重要影响。
  3. 全员参与(补)。在一些团队里,优化主要由那么1-2个资深的同窗主导进行,而在咱们的团队里,优化的工做是一件全员参与的事情。

2.1 底层模块

首先来看下底层模块,这一部分的重要性不言而喻,我想举两个例子来讲明一下它们设计得好坏对于咱们项目的影响。

其中一个是我以为咱们作得比较好的地方——Lua与C#的职责划分,另一个是咱们踩了不少坑的资源管理模块。

2.1.1 Lua与C#的职责划分

Lua与C#的职责划分可使用这样一张图来描述:

这里涉及到的语言有C#、Lua和C三种,在项目最初期,咱们就决定将大部分的业务逻辑放在Lua层来作。这样作的缘由和大部分使用Lua的项目同样——为了保证大部分的业务逻辑能够被Hotfix以及Patch更新。同时咱们客户端团队有大量的Python脚本使用经验,所以对于Lua来讲上手也是没有特别大的问题。

当决定了核心的业务逻辑存放位置的时候,数据的存放也就比较明了了。咱们遵循的一个设计理念是——

让数据尽可能靠近它的使用者。

数据越靠近最终的使用者,中间须要进行转换的CPU和内存消耗就越小。所以咱们使用Lua这种原生就支持数据存储的方式,即Lua Table的方式来存储客户端使用的数据。

接下来是网络部分。在项目最初的时候我也考虑过是否应当将网络放置在C#层,由于对于Unity引擎来讲,C#是更加原生的语言,对于一些库的支持也特别方便。可是通过一些思考和讨论以后,咱们决定将网络放在C层,这样作的缘由和数据放置在Lua层是同样的。由于对于客户端来讲,网络是数据的来源和发送者,至关于一个数据源的角色,所以也应该更加靠近它的使用者。不放在Lua层的缘由是网络传输中仍是有很多计算量的,加密、内存拷贝等,所以放在C层效率更高。同时咱们在C层集成了一些Lua中缺乏的扩展库。

选择放在C#层的部分有这么几种:

  • 引擎功能,这个毋庸置疑,Unity自己就是经过C#来提供引擎接口的;
  • 计算密集型逻辑,对于一些可能涉及到CPU消耗的计算密集的逻辑,封装成C#的接口供Lua调用;
  • Tick逻辑,即一些须要持续Update的逻辑,放在C#层经过Component的Update函数来实现;
  • 交互频繁的逻辑,一些须要Lua和C#频繁交互的逻辑放置在了C#层来实现,一样封装成接口供Lua调用。

这样的设计依据另一个设计理念——

将Unity做为引擎层来使用,让C#层尽可能少地关注具体的业务逻辑。

在这样的设计下,C#和Lua之间的交互就很是清晰:

  1. C#经过tolua# warp出来的接口让Lua调用,Lua是逻辑的驱动者;
  2. C#提供每帧一次的Update/LateUpdate调用,具体内部须要的分发由Lua本身来作,大量的间隔逻辑经过Timer模块来实现,减小每帧的tick逻辑;
  3. C#对于Lua的感知仅仅是一些异步操做的回调,好比按键点击事件、异步加载完成的回调逻辑。

在这样的设计之下,UWA对咱们项目进行深度测试的时候给出的测试报告结论以下:

UWA团队告诉咱们在他们测试的重度项目里,这个数据已是很是好的了,咱们项目在这块也只使用本身开发的调用次数统计工具进行常规的优化,在项目后期并无花费太多的时间。

同事增练开发的调用次数统计工具

这里提一下跨语言编程的时候关于对象/资源生命周期的设计理念:

谁建立谁销毁。

简单明了,若是一个对象是由Lua建立的,那它必定要由Lua来显示地负责销毁;而若是一个对象是由C#逻辑来建立的,那必定由C#来进行销毁。只有这样才可以避免生命周期错乱致使的泄露或者提早销毁的错误,对于泄露的检查也更加明了。

2.1.2 资源管理模块

咱们项目中的资源管理模块是我以为因为最初设计不够致使后期踩了不少坑的一个部分,其中一个表现就是每次游戏要进行测试上线以前都要花时间解决加载模块的各类奇怪问题。咱们熬夜修复的问题有这么一些:

最初的版本由于一些迭代致使资源的引用计数存在问题,出现了Asset被卸载了可是又被使用的状况,再次尝试加载资源就报错了。后来又发现很是严重的内存泄露,表现是几乎全部的资源都残留在了内存=_=……为了修复泄露问题,咱们将底层AssetBundle的管理从原来的Unload(false)修改成了Unload(true),虽然解决了泄露问题,可是须要上层逻辑有一些迭代工做。后续咱们还尝试处理同一个资源在异步加载过程当中有同步加载请求的问题。

如今回头来看,对于资源管理模块能够进行反思的内容有以下几点:

最初的时候因为团队内对于Unity的经验不是很足,面对资源管理模块这个很是重要的部分,想法是借助比较成熟的开源框架来弥补经验上的缺失。因此在大体了解了基本原理以后,选择了KSFramework这套开源框架。它对于资源管理模块有一套基于Loader设计的封装,咱们又根据本身的需求和发现的问题进行了一些迭代工做。在初期编辑器模式下,这套东西帮助咱们快速创建了Demo和推动前期功能的开发,可是也隐藏了不少设备上的问题。这应当说是很是标准的技术债务,只是没想到须要付出这么高昂的利息。。。

在后期的维护中,由于技术团队的扩张和一些“不可抗力”的缘由,这个模块前后经手三个负责人的维护,在交接以及讨论中由于理念不一样也产生过一些设计上的误解,埋下了一些问题。

最后,由于编辑器模式下没有使用异步加载的方式,所以运行逻辑和设备上是不一样的,致使不少异步的问题在真正进行大范围的真机测试的时候才暴露出来,须要在比较高压的条件下进行修复,带来了不少挑战。

最后,想说的一个点是——

即时面临很大的压力,对于一些奇怪的问题,不要尝试用一些临时手段进行掩盖和容错的方式进行处理,而是尽可能地去找到问题产生的根源,从根本上进行解决。

有时候在没搞清楚根本缘由的状况下贸然经过“补洞”的方式来进行问题的修复,可能会把坑埋得更加深,让问题更难复现和排查。我在项目中就经历过这样的状况,也都是血与泪的教训。

2.2 代码质量

代码质量的重要性我依然想讲一个我在《无尽战区·觉醒》这样一款手游开发项目中的例子来进行说明。

在项目中后期,咱们进行Python层性能优化的时候发现:__dict__这样的属性访问占用很是高。用过Python的同窗可能都知道这是Python中进行属性访问的方式。排查调用源发现不少优化的点都是相似上面这样的局部变量的优化。(图中的代码只是示例,并不是真实代码。)

而对于每个入职的同事,在进入公司的Python课程里,都会学习到在脚本语言中尽可能使用局部变量来进行性能优化的方法和原理。然而在真正的项目开发中,仍是会有不少人忽略这种优化,这是代码质量偏低的一种表现。

在接下来的三四天时间里,咱们不断地profile、修改,对__dict__性能消耗比较大的地方使用局部变量的方式进行性能优化以后,整个脚本的性能有了大约10%-20%的性能提高。这是很是大的一个优化了,并且彻底是无损的优化。若是咱们的开发人员能够在平常的开发中就注意维护代码质量,对于这些优化时间的消耗就能够节省掉很多。

在我看来,在项目开发中能够提高程序团队的代码质量的方式包括以下几个方面:

  1. 针对性的培训和按期的技术分享。技术分享可能会花费挺多的时间,可是在时间相对宽松的研发期坚持进行技术分享仍是会给团队带来有多正向的收益。咱们一年多的创业时间内,技术分享大约作了十几场,虽然和大厂的分享相比不算不少,可是在促进团队技术进步、提高代码和设计质量等方面仍是起到了很好的做用。
  2. 代码Review。也有很多人和我讨论过在团队内进行大范围的Code Review的可行性。首先Code Review对于提高代码质量确定是有很大帮助的,可是从我我的的项目经验来讲,要在手游这样一个须要快速开发迭代的团队里推行严格的Code Review代价仍是很是大的。好比工做压力比较大的状况下,咱们一个同事可能会在一天产出上千行的Lua代码,若是想要另一个一样有这样大工做压力的同事抽出时间来进行完整的Review,几乎是一件不可能的事情。所以咱们选择只在关键节点进行Review,包括核心代码和线上Bug修复代码,以及新同事入职的第一个月提交的代码。咱们有过一次集体Review和迭代的过程,对于项目中会由多人共同维护的一段逻辑,你们都花时间进行迭代,而后分享本身迭代的思路。这种方式虽然会花费团队挺多时间,可是偶尔针对特定代码进行仍是比较有效果的,能够统一你们对于关键部分代码的设计理念和使用方式。
  3. 静态分析工具。这块咱们在使用的有LuaChecker和UnityEngineAnalyzer,针对代码进行检查,能够发现一些优化的点。

2.3 全员参与的优化(补)

咱们客户端程序团队在进行优化的时候和一些团队不一样的作法是你们都针对本身负责的部分进行优化。这样作和团队自身的特色有关,咱们客户端团队对于一个创业团队来讲算是经验和技术能力都不错的一个团队,每一个成员都有多年的游戏开发经验。所以每个同事都负责一些比较底层的模块,也会负责各个玩法系统的开发,是一个纵向的结构。总结起来,让全部同事都参与优化的好处主要有:

  • 让团队中的每一个人创建优化意识;
  • 每一个人做为本身负责模块的优化负责人;
  • 组织专门的优化周期,横向对比,互相学习。
组织专门进行优化周期的Evernote记录

3. 团队开发效率优化

终于来到第三部分,也就是以前说的看上去和性能优化并无直接关系的团队开发效率优化。

在聊这部分以前,我想让读者思考一个问题——咱们为何要作优化?

是为了让游戏的运行更加流程?让游戏更加省流量?更省电?让游戏包体更小?这些都是咱们进行优化的目标,但归根节点,咱们作这些优化的目标都是——为玩家提供更好的游戏体验。

因此在我看来,若是一个优化,不管使用多么高超的技巧,若是它的优化结果没法直接或者间接地被玩家感觉到,那这个优化可能就只是一个程序员的“自嗨”,没法为游戏提供真正的价值。反过来讲,若是咱们能够优化团队的开发效率,让团队有更多的时间来开发新的功能、制做更多的游戏细节,那对于游戏来讲也是一种优化。

所以在我看来,进行团队效率的优化是一件很是重要的事情,也是程序的职责之一。我主要想从这样三个方面来聊一下如何进行团队开发效率的优化:工做流的构建、程序团队、策划团队。

3.1 工做流的构建

我以为在项目中构建更好、更顺畅的工做流能够很大地提高总体团队的工做效率。我以咱们团队如今一个功能的完成流程为例来分享一下咱们团队使用的工做流。

  1. 策划提早和程序、美术沟通需求的可行性,在可行性肯定以后,经过Redmine这样的管理软件提单,将需求详细地描述在任务单里;
  2. 咱们在Redmine中集成了Webhook的功能,当有任务提出的时候,Redmine会经过钉钉的接口通知到对应的程序;
  3. 程序根据本身手头的工做安排进行排期和功能实现,当任务单完成并进行自测以后,会将代码提交到svn上,同时将Redmine上的单子修改成“已完成”的状态,状态的变动会一样通知到相应的策划和QA;
  4. SVN经过SVN hook的方式,自动触发Jenkins的Lua代码编译指令,Jenkins调用咱们部署在公司内网的一套分布式打包服务,进行脚本编译。咱们团队中只有程序有Lua代码的svn访问权限,其余职位统一使用编译好的Lua bytes code。
  5. 当打包完成以后,分布式的打包服务会调用钉钉接口将完成消息通知到特定的群里。
  6. 策划须要进行导表、更新服务器,或者QA同事须要进行安卓/iOS打包的时候,都是经过Jenkins进行请求,Jenkins继续调用分布式打包服务进行打包,并将结果通知到群里。
Jenkins上的部分服务
对于Jenkins部分,提醒一下要作好权限控制,对于其余职位可能须要的,尽可能避免参数式的执行方式,而是以多个任务的方式提供。而程序部分则能够尽可能灵活地使用参数进行构建。对于发布版本打包、分支建立等功能,经过权限控制不要让策划/美术/QA误操做点击到。

咱们的分布式打包服务是基于Python构建的,经过简单的RPC服务进行内网跨机器的互联,经过argparse模块进行参数化的提供,方便扩展:

def ParseArgs(args): parser = argparse.ArgumentParser(description = 'Build App') parser.add_argument('-p', '--platform', choices=('android', 'ios',), required = True) parser.add_argument('-c', '--channel') #all, xiaomi parser.add_argument('-b', '--build-type', choices=('dev', 'pub',), default="dev") parser.add_argument('-sp', '--spmark') parser.add_argument('-hm', '--headmark') parser.add_argument('--non-sdo-server', action = 'store_true') parser.add_argument('--nopatch', action = 'store_true') parser.add_argument('--onlyab', action = 'store_true') parser.add_argument('--uwashipping', action = 'store_true') #单独为uwa测试准备的发布参数,临时添加 parser.add_argument('--make-base', action = 'store_true') parser.add_argument('--il2cpp', action = 'store_true') parser.add_argument('-xp', '--xcode-profile-type', choices=('development', 'addhoc', 'appstore',), default="development") buildArgs = parser.parse_args(args) 

我以为这样的工做流的好处主要有:

  • 程序将更多的事情推出去,交给工具,本身能够更加专一在程序开发的工做上;
  • 其余职位拥有更多的自主权,在不须要程序参与的状况下能够完成本身的不少工做;
  • 经过钉钉这样的IM的通知功能,将轮询的消息变成通知,再也不须要等待和关注Jenkins任务的完成进度,完成以后天然就会收到通知。

3.2 提升程序开发效率

这块基本都在PPT里了,不赘述了,其中调试工具部分再次推荐一下:Hdg Remote Debug这样的设备调试工具,关于Lua的部分在3月份的博客中已经说得很是详细了,也再也不重复。

3.3 策划工做效率优化

策划工做效率的优化部分想讲两个切身经历的事情。一个是很是小的一个优化,帮助策划实现NPC坐标从Unity中拷贝到Excel中。

咱们由于开发周期比较紧,并且服务器须要一些NPC的位置数据作验证,所以没有在Unity内部为策划实现NPC编辑器,而是须要策划手动去Excel表里填写。这里就有一个填写坐标的过程,最初的时候策划手动填写很是费时间,并且容易出错,后来帮助他们实现了一个点击GameObject节点拷贝坐标到粘贴板的功能,策划使用后表示极大地提高了填写NPC表格的工做效率。

有时候程序只须要经过很简单的代码就能够帮助其余职位的同事解决一些工做中的痛点,提升工做效率。

第二件事情是以前在大公司工做的时候的一个亲身经历。当时在带新人作mini项目,一个新人策划就在公司的KM知识分享平台上提了一个问题——他表示如今的策划填表的工做效率很低,须要经历这样几个复杂的步骤:

在Excel中编辑数据,而后提交到SVN上,经过导表将数据转换成程序代码读取的资源,而后更新服务器,更新客户端,启动客户端链接服务器才能查看结果,这些步骤要花费大约10-20分钟的时间。他问可否编写完数据以后就能够直接在游戏内看到结果?

当时的我做为自觉得在游戏行业已经有几年工做经验的“过来人”,看到新人策划有这样的疑问,内心实际上是有一些嘲讽的。因此去“耐心”地回复他:对于客户端来讲,能够作到本地导表而后不重启客户端就能够直接Reload数据查看结果,可是若是你不把数据上传到svn上,服务器如何知道你本地修改的结果?这就像那样一个笑话:

“是这样的,张总, 您在家里的电脑上按了ctrl+c,而后在公司的电脑上再按ctrl+v是确定不行的。即便同一篇文章也不行。不不,多贵的电脑都不行。”

这个笑话后来的结果是本身成了一个笑话,由于虽然时代的发展,网络硬盘等云服务的普及,也有了跨电脑进行粘贴拷贝的功能……张总再也不须要很贵的电脑就能够实现本身的操做。

这个故事的发展和这个笑话有些类似,在大约半年以后,我和工做室的另一个同事将rpyc这样一套中间件引入公司并基于它实现了跨进程的外挂式编辑框架。基于这套框架就实现了策划在编辑器内编辑完数据,只须要点击重载数据的按钮,就能够自动更新本地的客户端和指定ip的一台服务器中的数据,再也不须要提交到svn,甚至不须要重启客户端就能够看到修改以后的结果。

通过样的改进以后,以前须要10-20分钟左右时间的操做,如今只须要2-5秒就能够实现,极大地提高了策划的工做效率。我和那位同事也所以拿了当年公司内部的技术分享奖。

这个故事对于个人触动还蛮大的,由于最初我所嘲讽的一个新人的想法,最终由我和另外的一个同事一块儿进行了实现,这对于我来讲也是一种讽刺。所以在以后我再听到策划或者其余职位的一些看上去“异想天开”的想法的时候,不会急于反驳或者指出其中的漏洞,而是先想一想是否本身的思路被本身了解的技术所禁锢,是否有别的方式能够真的实现这些想法。

经过这两个故事我想表达一个观点,对于程序在团队效率优化方面应当承担什么样的角色?借用《蜘蛛侠1》里很是有名的那句话来讲——能力越大,责任越大。

由于程序是整个团队中最了解技术和开发的人,也最有能力开发一些工具或者引入一些方法让整个团队的工做效率获得提高,所以也应该肩负起相应的责任。

 

4. 总结

最后,咱们聊了这么多,进行一些总结。

我在游戏行业里也作了五六年,特别是本身在创业的这一年多的时间,让我更加深切地感觉到游戏开发很是符合这样的冰山理论。

浮在冰面上的这一部分是玩家能够感觉到的游戏内容,好比精致的美术资源、有趣的玩法,而在这之下,有更多没法被玩家直接感觉到的内容,好比被迭代掉的玩法。而今天咱们所聊的这些优化的内容,好比美术规范、代码质量、团队的工做流构建,它们大都是水面如下,没法被玩家切身感觉到的部分。但它们又是如此地重要,是整座冰山不可或缺的一部分。

就像我以前所说,经过刚才的分享你们应该也能够感觉到,这些优化的内容很是的琐碎繁杂,就像散布在各个地方的一个又一个点,是团队的协做让这些点能够链接成线,造成相似于美术资源的规范制定、规范执行和规范检查这样的闭环,而在整个游戏的开发周期过程当中经过团队锲而不舍地去作这些事情,让这些线链接成一张大网,将水聚拢在周围凝结成冰,托起了整座冰山,使得海面上能够被玩家感觉到的内容愈来愈多,这就是我眼中基于团队的持续优化之道。

最后的最后,我仍是想把我在上次分享中也说过的一句话送给你们。

这一年多的创业经历让我更加深入地体会到游戏开发是一件艰难并且辛苦的事情,有一些朋友或者同事也找我聊做为一个程序进行游戏开发的迷茫,我本身心里也曾有过彷徨和纠结。由于不少事情太过琐碎,带给咱们的成就感可能也会偏低。可是我也发现,如今作过几年游戏行业以后,依然留在游戏行业中的人心中都有着对于游戏发自肺腑的热爱和激情。它们或从小就喜欢游戏,或曾经被游戏感动过心中最为柔软的那个部分,在游戏行业内坚持作这些着看似平凡的工做。

因此,我想把这句话送给全部依然在坚持的游戏开发者们——不忘初心,不愧平凡,相信经过团队的协做和坚持不懈的努力,能够给这份平凡以不凡!

谢谢!

 

2018年5月17中午 于杭州海创园 心光流美公司

相关文章
相关标签/搜索