资源大湿 发表于 2020-11-23 18:52

使用 Unity 引擎时有哪些禁忌?

美术和程序两方面描述。

alicomes 发表于 2020-11-23 18:53

补充,感谢评论区留言 @周源@张章(为什么找不到……),之前做动画的时候没有发现这么好用的接口,试了一下只要建立AnimatorOverrideController,引用模板的AnimatorController就可以直接创建成功了。




看到这个想到的并不是什么禁忌,Unity引擎的很多问题楼上各位都已经讲解差不多了,我就补上几个和游戏开发相关的优化方案,应该是任何引擎都适用的。
尤其针对初创团队,没有核心的技术骨干时,经常会走一些弯路,有时候游戏做出来Demo了效果很棒,但是要开始上线运营了,要修改资源和扩展功能做适配的时候,开始头疼花太多的时间去处理资源,往往一优化就是一两个月。
我讲一些简单却有必要的点吧,概念本身没有任何的技术难度
1. 资源命名
资源命名其实很多朋友忽略掉了,觉得资源命名没有必要。但是其实这个很有必要,因为我们在项目后期会涉及到批量操作资源,比如我们要将某系统的图片格式资源批量替换格式呀,我们要将动画批量创建动画控制器呀。还有一些程序自动化处理一些工作,比如我们对某种格式命名的资源设置不同的参数。例:图片根据后缀名字不同设置不同的图片格式。这是非常节省时间和避免人为出错的方法。





(输入eff就出来和特效有关的所有贴图资源,后面为该图片是否带有透明通道等后缀)





(有了规范命名直接可以在编辑脚本里面批量设置好格式了)





(游戏中的角色动画总是有很多状态和连线,如果每个都让美术人员来连线设置可是要累死人的)





(而只要我们做好一个正确的模板,美术的动画Clips命名和第一个模板的命名一样,我们就可以脚本批量控制直接拷贝复制参数就可以了,后期如果要修改成批的动画控制器,只用修改模板的属性批量转换一次就搞定)









(同时加载资源的时候也非常方便,只要命名的结构合理,我们就可以直接传入类型,让功能函数帮我们同时加载好资源并挂载相应的脚本)
2. 文件夹规范
很多团队的文件夹其实很多时候都是乱的,尤其是美术资源。
如果项目需要做热更新功能,都会无法避免的面临如何分包资源的问题,这个时候文件夹规范就尤为重要了。常见的方法是根据单独文件夹作为一个AssetBundle包,如果文件夹混乱,资源位置混乱,打的包将会互相依赖严重,导致加载资源缓慢,资源冗余,更新数据量大。
即时我们的游戏不做热更新功能,后期也会删减掉很多无用冗余的美术资源。而文件夹混乱的话你将会很难去把旧的资源剔除出项目。如果各位有太多冗余资源在项目里,这可以介绍一个简单的剔除方法。将你不用的资源从Resources目录下剔除,右键Resources依赖打包unitypackage,然后删除所有美术资源重新导入unitypackage(当然得做好备份,避免删除重要文件)。





(如果要剔除某个不用的特效只用删除该个文件夹就好了,当然特效的贴图如果是公用的是放公用文件夹下)
3. 不要直接用Unity的接口或者资源。因为Unity底层并不开源,我们要优化某个公用资源或接口,如果之前没有做一个中转接口就会变成全部人工替换。这里主要举例Shader,Shader应该是我们自定义的。就算我们用的和Unity的功能是一模一样的,也要复制一份出来改成我们自定义的Shader。这样后期做优化,就不用新创建好Shader让美术一个一个给成千上百个材质球重新替换Shader。





(实现效果和Unity是一样的,只不过我们重写了一次)





(后期如果要做扩展和优化,就可以在我们自定义的Shader中修改了)


最后……
游戏开发不易,且行且珍惜。

maxleung 发表于 2020-11-23 18:53

前言:最大的禁忌是误认为“Unity入门很简单”。想当程序?C语言基础,数据结构,设计模式,内存管理,编译原理,多线程并发,实时图形学这些不学好还想做程序?你想你horse呢?基础不行的关掉回答吧,这篇回答对你毫无帮助的。基础OK但是引擎可能不熟悉的大佬们请继续往下看。
程序方面:
禁忌1:错误的认为Unity有Terrain这个功能。
Unity没有这个功能,你看到的那个组件是个玩具,要常常这么告诉自己。想做开放世界大地形?成,自己撸Procedural Virtual Texture,自己撸四叉树LOD,流式加载,Terrain Decal……Unity是不会有的,反正我目前就是在自己做这些工作,因为这个组件真是完全不能用。。。团队需要做这一方面,别多想马上拉一支渲染团队自己搞吧,毕竟自己的轮子才是靠谱的。
禁忌2: 使用Resources文件夹。
我的经验告诉我,没什么使用这个文件夹的必要,如果是美术资源或Shader,应该使用ScriptableObject进行全局管理,如果是二进制文件应该直接使用.Net提供的文件读取,这个功能官方都不推荐使用,无论是从实现效率,还是项目管理上来讲,直接用地址去读取项目资源看起来都是一种非常低效的办法。
禁忌3:写代码时搞混FixedUpdate与Update的关系。
其实这个设计本身是一个非常聪明的设计,将物理计算使用一个单独的固定帧时间。假设默认FixedUpdate是20ms一帧,而每帧实际执行时间为10ms,那么FixedUpdate就会每两帧执行一次,如果每帧实际执行时间为40ms,那么FixedUpdate则会每帧执行两次,这样物理引擎就不会出现时钟问题。所以诸如输入输出一类的代码写在FixedUpdate中是错误的,因为有可能触发的时候FixedUpdate完全没有被执行,所以正确的做法是统一管理输入输出,将键盘鼠标的输入以消息方法的形式发送出去,等到执行FixedUpdate时处理消息。这点许多老程序刚刚转到Unity时很容易阴沟里翻皮水,要特别注意。
禁忌4:逻辑都怼主线程。
其实现在Unity的多线程已经放的比较开了,甚至连Transform都有了一个TransformAccess这么个东西,而且多线程计算逻辑在大多数时候都能带来明显的性能提升,毕竟现在四核都算少的,六核八核都快成主流了。不能在分线程访问的一般是一些内置组件的属性值,比如Light.intensity{get;set;},而这种赋值类型的计算其实消耗很小,完全可以把最耗时的计算逻辑放在Job System中计算,大大降低主线程的负担。
禁忌5:粗放式的内存管理。
一般游戏开发中是绝对不允许GC带来的高昂消耗的,动辄几十毫秒的GC时间就是顶配I9处理器也得乖乖的跪下,而高昂的GC消耗实际上99%来自开发者工作的疏忽。譬如unmanaged数据类型可以使用引擎自带的Memory Allocator进行分配,每次new的时候都要想清楚自己这个new是否可以复用,是否会像黄河的河床一样越埋越高,隐患越来越大,最终造成“决堤”,带来不可挽回的毁灭性的损失。如果是UI带来的GC压力,可以考虑魔改UI层源码,通过提交一个字符串的引用而不是强硬的深复制,一般正确的做法是直接调用UnityEngine.Scripting.GarbageCollector关掉自动GC,并在需要的时候手动调用GC,如果在游戏运行过程中内存直接炸掉,这就要准备分锅了,看谁写的代码在不断的制造内存垃圾,引发内存泄露。
禁忌6:Shader中使用大量的multi_compile分支。
multi_compile是一个比较尴尬的设计,是会导致包体体积巨量增大的一个隐患,如果需要做分支功能,建议使用多个Pass等操作,尽可能减少multi_compile的数量。
禁忌7:项目过度封装。
过度封装是一些开发经验不足的团队常出现的问题。过度封装很多时候是开发团队成员之间互相不信任,通过多层保护和限制使项目能work,然而这样会使得项目开发效率和运行效率严重低下。如果老板为了省成本招了一堆工资低水平低的搬砖工,然后指望依赖少数的高手做框架把项目封装死让搬砖工们老实搬砖不出幺蛾子,那么这个项目的下场已经可想而知了,反正高手们到哪都能混的风生水起,如果实在改变不了老板的主意就另作打算吧。。
美术(TA)方面:
禁忌1: 导入模型时有多套UV。
自己在DCC软件中分UV2当然是一种可行的方案,如果自己分光照UV,那么最好直接在软件中制作好光照贴图,并在引擎中将光照贴图打包成一个一整块,比如TexArray,供场景使用。如果不是为了提前烘焙光照则尽量不要在DCC中分UV2。
禁忌2:使用大量SubMesh/SubMaterial。
Drawcall中比较昂贵的部分就在重新绑定材质资源属性上,也就是SetPass Call,同一个物体上有多个材质将会迫使引擎在每次进行多次SetPass Call。如果只有一个材质,那么引擎可能将周围一片使用相同材质统一进行SetPass,绘制效率可以大幅度提高。因此在确保使用了统一的基于物理的着色模型后,可以大幅度降低材质种类和Shader种类,尽可能消灭SubMesh。
禁忌3: 慎用GPU Skinning。
Unity提供的GPU Skinning比较弱,有时候不仅没有优化反而负优化,这是历史遗留问题。如果希望使用GPU Skinning建议使用刚才上方提到的TransformAccess在Job中导出骨骼矩阵数据,并传递到Compute Buffer在Compute Shader中计算,我在文章中讲过这方面的内容:
MaxwellGeng:GPGPU Computing Animation & Skinning禁忌4: 模型LOD处理不恰当。
Unity内置的LOD Group组件并不实用,毕竟是一个很古老的组件了。实际开发过程中应该根据需求采用不同的LOD方法,如开放地形的建筑群可以使用HLOD,人物可以使用动态曲面细分,平坦地形和海面可以使用四叉树等……按需分配才是正解。
先说这么多,想到什么再补。

李子园 发表于 2020-11-23 18:54

个人目前只在做2d游戏,而且是初学,回答很具有局限性,甚至有可能有错误,见谅。


程序上


1 2d游戏不分层靠改z坐标分前后,刚开始可能可以用,之后多半会出问题,比如粒子特效之类的改z坐标不现实。
2 在继承了MB(MonoBehaviour)的类里,进行一次巨大的内存申请(new等方式),在MB里申请内存好像是不会“自动”释放,也不会自动还给系统。一次巨大的new之后就会一直占用着了。
感谢评论区提醒,注意是不会自动释放的,因此要手动做GC(Garbage Collection),个人用的方式是在切换场景的时候调用System.GC.Collect ();注意这一定不是最好的方法,个人水平有限,只能暂时避免使用过多new。
3 在Update里进行复杂循环,计算,new大量东西每一帧申请大量内存等。
4 一个物体上挂了几十个通道几十个脚本。在风马牛不相及的物体上挂脚本。
5 完全不懂优化,例如图片等美术资源不压缩就丢进游戏,放进去没做图集等等。或者压过头造成破坏性压缩,比如产生严重锯齿,图片颗粒等等。
6 字符串直接“加”字符串。
7 完全不懂design pattern和设计准则,一个脚本几千行。或者每个脚本就几行,一个小游戏弄了几千个脚本。(如果代码都是有效的话,相比于前者来说无伤大雅。)
8 不会命名法,什么中文拼音,英语甚至日语罗马音全混在一起,变量就叫a b c d aa1什么的也不加注释过一天自己就看不懂了。或者同样的物体叫完全不同的名字,同一个名字的物体在同一个场景里有一大堆。
9 程序耦合度过高,随便需要改变或者删除一部分功能就会导致大崩盘。
10 无脑继承MonoBehaviour(某些极端简单的2d游戏可以全部继承MonoBehaviour) 和无脑黑MonoBehaviour而完全不继承MonoBehaviour。
11 无脑滥用插件。
12 Hierarchy里东西乱放,没有考虑好层级,所有东西都堆在最上层啊,没有空的游戏物体做上级啊等等。
补充:有几十层级的游戏物体,把脚本藏在几十层的物体上,
13 虽然MonoDevelop一类是有自动对齐的,但是还是有人三五句程序堆在一行啊,不明所以的换行啊等等排版出现问题的情况。虽然不会直接影响程序运行,但是可读性大幅度下降。
14 完全不进行版本管理。
15 没有备份的情况下做一些危险或者不可撤回操作,例如delete。
16 完全不做继承,相同的功能或者稍微有改动的功能重复写了数遍。
17 稍微复杂的功能没法实现的时候,用一些邪门方法解决,可能当时可以,之后必定会出现严重问题。(比如预想是某个计数器到10出现新一波敌人,不小心在三个不同的地方重复计算了这个计数,然后没发现具体原因,只看到结果变成了12,就把“等于10”的条件改成“大于10”甚至“等于12”)
18 完全不关心优化,例如一个小游戏做成几G容量+运行时占用将近1G内存(我真的没黑枪某游戏)。
补充:
19 使用数组之前不new。
20 无视自己看不懂的warning信息甚至无视自己看不懂的error信息。
21 不会排版,例如对齐,空格,换行,虽然没python要求那么高,最基本的排版要有。
22 无脑用轮子和无脑排斥轮子,比如完全不懂轮子怎么用的,凑合当前测试通过就用了。还有一种是觉得别人轮子都不如自己,不进行任何测试就重造轮子,结果往往不如原来的轮子(得到广泛认可的轮子多数情况下是有理由的),浪费时间浪费精力。
23 完全不会注释,或者希望通过注释改善原本质量很差的代码。
24 代码一行无比长,不换行不优化命名。
25 无脑用别人shader,自己连为啥shader里常用的向量点乘具体意义都不懂。
26 我自己碰的一个禁忌,有一次往git上commit,有一段时间没传+自己大量改动+另一个人大量改动,导致merge时间以天计数了,不得已只能新建一个分枝来用。应该是每次工作有了阶段性进展,或者每天下班时,最好commit一次,不要攒一周两周再传。
27 压缩图片纹理的时候,往ios包里用仅android支持的格式(例如ETC1),反之亦然。而且好像这个错误某些情况下会被隐藏?
28 认为程序做好自己代码就行,完全不进行沟通。
29 完全不会数学,比如多数简单手游移动都是只会直线,好一点的有非完美曲线(例如人工添加无数个中间点)。贝塞尔曲线这样基本的数学要会一点。
30 程序中很多不声明的常数,例如Attack=Attack*1.3,这个1.3是个buff量,实际上最起码要AttackBuff=1.3;
Attack*=AttackBuff;
否则很容易过几天就忘了这个1.3是干嘛的了。
美术上
0 盗图盗素材等一系列非原创行为。


美术上其实不是很容易到“禁忌”的程度,以下几乎都不一定能算禁忌


1 做的图含透明部分后大小不一,还要程序员重新修正到透明最小(万一有不含透明部分都大小不一的那真的算禁忌了)或者修成大小一致。尤其按钮和血条进度条等,附了一大片透明就会有不大不小的麻烦。
2 比如类似相框一样的只有边框,中间全透明的,个人认为最好做成4个框拼接,中间透明的部分不要。
3 比如按钮等等只需要给原来颜色版本和如果需要的特殊变化版本等,不要只给一个按下去变灰色之类的特殊版本,单纯只整体变颜色unity程序员就可以做到,只给个灰色的还要开ps去擦。
4 如果不是只使用一次的文字,最好做成字体,否则程序就变成从控制字符串变成控制图片,而且之后如果又要追加文字又要重新做。
5 跟程序一样的问题,瞎命名,叫a1 a2 未命名png 什么的,虽然比变量名直观一点看一下能看出来是什么,但是管理起来还是很麻烦。
6 完全不懂程序也很麻烦,比如一个按钮含按钮和装饰的部分,然后美术把这两个做成一张图了,就可能要返工。或者比如无脑加mipmap,虽然程序点一下就能取消掉一个,甚至可以写脚本取消,但是确实增加了工作量。
7 配色不统一,甚至配色水平不如程序员的也是有见过的...风格不统一,分了好几批人做等等。
8 完全不懂渲染和优化,粒子数巨多,还开碰撞什么的。
9 没考虑是在什么平台上用的ui,做的分辨率过大或者过小,又没做成九宫格。
10 剪的素材不合理,比如把粒子特效和背景做在一张图里的,要粒子特效素材结果给了整体中间状态图。又比如说“攻击力提升”“防御力提升”的艺术字素材,并且“提升”两个字一模一样,个人认为应该是剪成“攻击力”,“防御力”,“提升”三个素材,但是往往收到的是“攻击力提升”和“防御力提升”,力和提中间有的时候又隔着很长的透明部分,就要程序重新修,等等。不过出现这种情况大多是因为中间沟通不畅导致的。
11 不算是禁忌,纯色背景做素材了,万一碰到不懂的程序还以为这有什么玄机加到素材里又浪费空间。
12 不算是禁忌,完全一样的或者高度相似的素材重复给了非常多个,比如有上述问题程序自己修正好了,换一个scene发现又一堆要修正的一模一样的素材,就有点烦了,也是增加工作量的情况。
13 不算是禁忌,在光照极强或者背景很亮的情况下用粒子特效,可能会导致效果变差。这种情况下注意选择粒子的shader,例如自带的alpha blended。(仅限2d游戏,3d游戏做法没研究过...)
补充:
14 注意一张图里各个部件分图层,例如光照,对应阴影,背景,衣服等等,需要改需求或者动态光照等等情况下会很少很多麻烦。
15 一定要和策划或者程序沟通好是在什么设备上用的,定好画板是 多少*多少,否则很可能所有ui做出来比例都不一样,导致要重做。

李子园 发表于 2020-11-23 18:55

把可口可乐放在鼠标线上

dannisliang 发表于 2020-11-23 18:55

随便说几个吧,不一定对:
1. 滥用物理。判断个距离的事,用碰撞检测,控制人物运动用addForce,射个子弹也用addForce...
2. update 里频繁调用GetComponent
3. 各种静态,各种单例(主要是说他们引起的高度耦合)
4. 500行的函数,而且还是一堆if else 连起来的
5. 动态光

=== 发表于 2020-11-23 18:56

程序:1、不少后期优化的东西其实可以在前期通过制定规范来让整租人规避掉。其他答案中提出的一些禁忌,在官方的一篇博文中已经提到过:Unity - Optimizing scripts in Unity games垃圾回收的可以看看这篇: Unity - Optimizing garbage collection in Unity games
2、继承自MonoBehaviour的类不要使用构造函数。
3、避免使用单例。评论里有人问到为什么要避免使用单例的问题。我简单说一下好了。首先单例都是静态对象,在单例中加载的资源和new的对象都要注意在适当的时候回收,不是所有人都能注意到的,也有管理成本。这点就容易造成内存泄露等问题。其次单例比较难做到完全解耦。比较典型的是时序耦合。假如你考虑到了前面说的回收问题,在适当的时候做了回收,比如调用的单例的Clear接口。但单例提供的就是全局对象,假如你有其他单例或对象还需引用该Clear单例的全局对象,或者存在A这个单例必须在B这个单例Clear之前Clear都是比较难管理,也很难一眼看出问题的。更多的单例的利弊,可以看看《Game Programming Patterns》关于单例模式的描述:Singleton · Design Patterns Revisited · Game Programming Patterns
4、做好Component的缓存,不要每次用到都调用GetComponent。
5、shader尽量程序搞定,浮点数尽量使用低精度。其他有关渲染的可以看看官方的这篇文章Unity - Optimizing graphics rendering in Unity games
6、如果使用git做版本管理,注意lineending字符的坑,这里提供一个讨论:GitHub 第一坑:换行符自动转换 · Issue #22 · cssmagic/blog
美术:1、如果目标平台是移动终端,美术效果不要太华丽并做好分档,高中端机型使用不同的美术效果。
2、模型注意减面,否则容易出现gpu瓶颈。不少移动端cpu和内存都够用,但是gpu比较渣。
先想到这么多,手机码字,连接稍后再给。
-------------------2017年6月29日12点16分更新了为啥避免使用单例的浅见和一些外部链接

lyppeterlyp 发表于 2020-11-23 18:57

既然是讲禁忌,我就讲一个:千万别把工程放在机械硬盘上。慢得那是一批吊糟。

我手上这个项目,资源弄得很乱,场景加载的内容很多。之前每次用Unity开游戏,都会在场景切换时卡好久。一直百思不得其解,后来把工程移到SSD上,如丝般顺滑。

iamtiger_515 发表于 2020-11-23 18:57

其实程序方面的禁忌还是挺多的。
先说一个比较常见而又冷门的:unity编辑器卡死。
早期开发由于很多的不规范,所以导致unity卡死情况是很常见的,最多时一天能卡死十几次。所以后来就总结了些经验了。
1.线程导致。如果在游戏关闭了,子线程没有关闭,那么在下次游戏启动时,很大情况下会卡死。
而往往有些线程,游戏不关闭是不会关闭的,例如socket的线程。解决方案是,让一个MonoBehaviour伴随游戏的生命周期,然后在移除的时候关闭线程。
2.死循环导致。这个不用解释了。
3.VS调试导致。偶尔出现,任务管理器显示VS内存在增加,等一段时间有几率正常。初步认为是unity在等待VS响应,所以卡住了。
解决方案是停止调试。
极端情况是VS也未响应,这时候应该要关掉VS,因为unity很有可能有未保存的修改。
4.大量log导致,伪卡死。log输出是挺耗时的,如果过多,会导致你整个界面卡住。
这种情况可以看任务管理器进行排除,如果是内存一直在增长,那么就不是卡死了。
解决方案,减少不必要的log,对update方法的逻辑排除,尽量避免出现大量的log。
5.开启游戏时卡死。有些时候场景初始化时间过长会让人以为是卡死。
建议适当的把初始化的逻辑分几帧执行,最好加进度条或者标识。
手机码字,先总结到这里了,有时间再填坑。6.运行时状态,修改代码,然后触发Unity重新编译,此过程可能会涉及大量代码序列化和反序列化,等待响应的时间有可能会比较长(伪卡死)。也有可能有些对象不能序列化,然后再编译完成后,运行的代码会出现大量访问空对象错误,大量的Error输出,导致类似4的情况。


2018.3.25
7.最近遇上的,线程死锁导致的,这个比较难查。只能细心的按照线程死锁的情况分析下代码。

shadow_qxz 发表于 2020-11-23 18:57

禁忌多了去了。
不能开线程,aji,一个21世纪的引擎了。居然还有main thread的报错,不上线程安全的靠用户保证不就行了。(哪怕你在一个非“main thread”的里面访问纹理的宽度和高度都不允许)
当然在手机上把CPU跑满,一会就过热性能BOOM了。
而这一点在PC上极为讨厌,四核八线程用Unity编辑器打包也好,导入也好,Player也好,其他七个线程永远都在看戏。可谓是垃圾中的垃圾了,他能打包流程很多地方多少能并行处理的,这也导致了一系列的注意事项。
实现/设计垃圾,SteamingAssetsPath在安卓下强行打入apk中,在安卓平台的时候,WWW读取偷懒走Java的读取文件实现,这样就导致一个问题:为了避免垃圾Java实现导致的Java堆内存增长,在Resource下和StreamingAssets下放置的东西越少越好。(我们游戏是采取了再次压Pak,分析天龙八部的Apk包,看起来也是做了类似处理,不知道是不是这个原因)
其他因为MONO导致的不能用foreach这种就不提了。
诸如Vector3/4的构造函数更是呵呵哒一样,
页: [1] 2 3
查看完整版本: 使用 Unity 引擎时有哪些禁忌?