为游戏物件分类
方便查找游戏物件
与其他游戏物件碰触时的判断
在 Tag Manager(Unity 5 称为 Tags & Layers)没有限定所定义的数量
Layer:
为游戏物件分类。
让 Camera 指定哪些物件要被画出来。
让 Light 指定哪些物件要被照明。
让物理射线确认哪些物件要被侦测到。
在 Tag Manager 有限定最多设定 32 个,且前 8 个预设不可让使用者变更。
Layer 限定 32个
以上,似乎已经很清楚地表明 Tag 与 Layer 之间的不同,同样都是为物件分类,我们可以依照其特徵来选择适当的使用时机,但是在写程式和一些设计上,光这样的认知是不够的。
我们从 Inspector view 展开 Tag 以及 Layer 的选单来看,好像没什麽不同,每个游戏物件只能设定一个 Tag,同样的,每个游戏物件也只能设定一个 Layer,但如果从 Camera 及 Light 的 Culling Mask 栏位来看,Layer 选单不只多了 Nothing 及 Everything 可以选择,而且是可以一次选择多个 Layer 项目,从这个部分来看,可以得知 Layer 就比 Tag 复杂许多。
首先,Tag 使用字串来定义,并直接从 Inspector view 的下拉选单来设定物件本身的 Tag,在使用上,Unity 内建并没有任何 Component 有暴露出的栏位用来指定使用 Tag,所以通常是在我们自己写的程式码中才会使用到,而使用的方式则是直接给予 Tag 定义时的字串值,所以较单纯些。常见的用法如下:
在场景中找出一个 Tag 为 Respawn 的 GameObject:
GameObject respawn = GameObject.FindWithTag(“respawn”);
在场景中找出全部的 Tag 为 Respawn 的 GameObject:
有勾选 Is Trigger 的 Collider 在碰触到其他物件时,判断该物件的 Tag 是否为 Player:
取得 Main Camera(Tag 为 MainCamera 的 Camera):
在此先讲个题外话,写程式应该要养成一种习惯,避免让程式码包含「不可思议的值」,这是什麽意思呢?举例来说,如果程式裡写了一个 if(val > 12.5f),另一个地方又写了 if(val < 5.0f),也许刚写完程式的时候,还能记得 12.5 跟 5 分别代表什麽,但是几个月之后呢?或是转交给他人维护时呢?还能清楚的知道这些值的意义分别是什麽吗?不是要从相关的程式码慢慢推算,就是要有详细注解才行,但是如果换成 if(val > speedMax),是不是就清楚许多,另外一个相关的习惯是,应该避免在程式码中直接写明值,假设前述的 12.5f 没有使用 speedMax 代替,如果有一天该值要改成 25.78f,那麽所有相关的地方都要依序手动改程式码,这样很容易发生遗漏而产生未知的 Bug。
为什麽要特别说明这个部分呢?Tag 在设定时以及在程式码中的使用,都是很清楚的字串,可能不会发生「不可思议的值」这种问题,但是它毕竟是个字串值,而不是统一定义的变数或常数,如果前面常用例子裡面的 Tag 名称 “Player” 在很多 Script 都有使用到,只要有任何地方不小心打错一个字母,就会出现未知的 Bug,而且 IDE 无法帮我们发现这个问题,所以,可以在程式码中宣告一个 public 变数栏位,统一在 Inspector view 来输入这个字串值,那麽在同一个 Script 裡面就可以大大降低这种问题发生的风险,不过,在 Tag Manager 与我们撰写的程式之间,还是要个别定义相同的字串值,如果在 Tag 应用广泛的情况下,打错字的风险还是有的,更何况有时候还是会有为了表达更明确而变更命名的情况,幸好 Unity 可以自行製作自定义编辑器来避免这种状况发生。
接下来,来看看关于 Layer 的部分,在程式码裡面只要宣告一个型别为 LayerMask 的 public 变数栏位,在 Inspector view 就可以像 Camera 或 Light component 一样,出现可複选的 layer 选单栏位,随著 Tag Manager 中的 Layers 设定或者命名的不同,这个选单也会跟著改变,所以就没有前面所述像 Tag 那样的问题,在设置栏位值上更为方便,而且不易出错,不过 Layer 在一般程式判断上的使用,倒是没有 Tag 那麽简单。
不同于 Tag 直接就是字串值,Layer 限定只能设定 32 个也是有原因的,因为 Layer 本身是个 32 位元的值,我们可以把它视为 00000000000000000000000000000000 这样的值,从 Unity 预设已经定义的 Layers 有 Default、TransparentFX、Ignore Raycast、Water、UI 这几个,如果我们在 Inspector view 将 Component 的 Layer 栏位勾选 Water 及 UI,那麽这个栏位值就可以视为 00000000000000000000000000110000,由此可知,Layers 所定义的其实是二进制的 32 个开关,所以如果将只勾选 Water 及 UI 这个 Layer 栏位值转型为 int,将会获得 48 的结果。
现在,既然已经瞭解到 Layer 值的原理,以下就来举例相同状况时 Tag 与 Layer 在程式码上面的差异。
假设我们将敌人分类为 5 种,然后只对碰撞到其中三种敌人做出反应,那麽我们在 Tag Manager 中可以个别为 Tags 及 Layers 定义五个敌人种类名称。
Tag
使用 Tag 来做的话,单纯利用阵列判断字串是否相符。
而在 Inspector view 则需要将预计做出反应的 Tag 名称准确的一个一个输入到 enemyTags 阵列栏位中,如果输入字串不相符的话,就会判断错误。
Layer
使用 Layer 来做的话,则是利用位运算子来使 layer 栏位值往右移位并与 1 进行逻辑运算来判断 layer 是否相符。
而在 Inspector view 则是直接在 enemyLayer 栏位的选单中直接勾选出预计做出反应的 layer。
以上的差异不难看出,Layer 要更方便些、安全些,只是对于很少或不曾使用位运算子的人来说,可能会较难理解一些。
另外,Layer 在 Unity 中还有另外一个用途,即是做为物理射线判断哪些东西要被侦测到。例如 Physics.Linecast 的最后一个参数,我们可以指定射线的起点到终点有哪些 layer 的物件会被侦测到。
在 Physics 以及 Physics2D裡有很多 Functions 都会使用到,另外 Physics 和 Physics2D也都有预先定义了 DefaultRaycastLayers 以及 IgnoreRaycastLayer 来提供给 layerMask 参数使用,因为 layerMask 参数的预设值为 DefaultRaycastLayers,所以一般情况下,直接省略这个参数,只要是 Layer 设定为 Default 的游戏物件都会被侦测到。
那么 IgnoreRaycastLayer 呢?从名称上来看是要被忽略、不要被侦测到的,所以就不能直接将它给予 layerMask 参数,而是要再多加个反转运算子。那麽只要是 Layer 设定为 Ignore Raycast 的游戏物件都会被忽略,未被忽略的则都会被侦测到。
因此,我们也可以自行定义哪几个 layer 的游戏物件是要被忽略、不被侦测到的。
再拿前面比较 Tag 与 Layer 差异的例子来说,我们也可以反过来设置哪几种敌人碰触时不做任何反应,其馀种类的敌人碰触时才有动作。
以上,对 Layer 以及 Tag 的差异有更详细的瞭解之后,在游戏的实作设计及应用上应该就能更加弹性,只要能好好运用 Tag 以及 Layer,对整体的设计製作上会有很大的帮助,而不是都用程式写死的方式或是总是以物件名称去做判断,导致程式码过于拢长及複杂。
最后,再补充一下设定 Layer 在 Unity 编辑器使用上的好处,对于放置很多物件的场景,我们有时候可能会因为场景的东西太多而造成观看以及编辑上的不便,如果有好好的把每个游戏物件分类并设置好 Layer 的话,在编辑器右上方的 Layers 选单可以调整哪些 Layer 的游戏物件可以在 Scene view 看到,但是并不影响到 Game view 的内容,如果能善用这个功能,在工作上会方便很多。
作者:viva158
链接:https://www.jianshu.com/p/ebb76e07b5ae
来源:简书