作者归档:贺 利华

关于贺 利华

正在学习编程,享受编程 热爱文学,闲来读读《读库》 有思想,没理想 正在学会专注

拾人牙慧 · 壹

关于同好和同恨

我们爱一个对象的时候,一般不会寻找同好者,甚至会把跟我们爱同一对象的人视为竞争者和侵犯者。但我们恨一个对象时,却总会寻求有志一同的人。

—— 霍弗 《狂热分子》


克鲁伊夫说

我们不会被意大利队击败,但我们会输给他们。

——克鲁伊夫的足球观念颇有哲学意味


戈林关于独裁的证词

不论是民主党、法西斯独裁的、议会制的,还是共产主义专政的国家,人民总是被教导要听从领袖的召唤。领袖所做的,就是告诉人民,他们正遭到攻击,然后谴责和平人士缺乏爱心。

—— 赫尔曼 · 戈林 1946 年 4 月 18 日在纽伦堡审判中的证词


没有无神论者

我是不可知论者,不要叫我什么无神论者。你听过一个不打高尔夫的人自称不打高尔夫球者吗?你见过他们聚集在一起讨论为什么不打高尔夫吗?

——美国天体物理学家兼科普作家尼尔 · 德格拉斯 · 泰森


傻瓜和聪明人

傻瓜不跟聪明人学,聪明人从傻瓜那里学到很多。

—— 瑞士人拉瓦特的名言


Unity3D中如何通过位运算来开启和关闭指定层

在 Unity3D 开发的过程中,我们经常会有选取指定层的需求,例如设定某个摄像机只渲染某几个层的对象,UI 摄像机通常就只选取 UI 所在的层进行渲染,而对于其他层的对象进行忽略;场景摄像机通常会对非 UI 层的所有对象进行渲染,那么除了在 Unity3D Editor 中对场景中已有的 GameObject 直接进行设置之外,我们还有更为灵活的方法吗?当然有了,只要通过简单的位运算就可以把这个需求满足了。

我们先用摄像机来举例吧,Camera 的 cullingMask 属性就是用来设置摄像机渲染层用的,例如我们想设置名为 mainCamera 的摄像机的渲染层为 UI 层和 PopUI 层,那么代码应该如下:

mainCamera.cullingMask = LayerMask.GetMask ("UI", "PopUI");

上面的这段代码非常容易理解,如果我们的需求就是针对有限的少数几个层进行设置的话,这种方法是最好用的。但是,我们通常都会有一些针对特定层进行动态设置的需求,例如我们需要动态地将 PopUI 层从 mainCamera 的 cullingMask 中剔除掉,那么我们应该怎么做呢?可以简单地依照上方的代码来做:

mainCamera.cullingMask = LayerMask.GetMask ("UI");

这样确实能满足我们的需求,但是这是在我们已经完全知道 mainCamera 具体需要渲染哪些层的前提下才可行的。例如我们有一个名为 fightCamera 的摄像机,其对非 UI 和 PopUI 层之外的所有层进行渲染,由于游戏中设计的层数较多,其渲染的层总数可能达到20个,那么此时使用上面的这种方式来关闭某个层那么就显得非常的蛋疼了,我们总不能把那20层的名称都写到代码里头吧,咱们暂且不说可能会写错,这看上去就不是个聪明的办法啊。

这个时候,我们就需要使用到位运算了,例如我们想确保名为 FX 的层在 fightCamera 的 cullingMask 中,那么我们只需要使用下面的代码就可以了:

int layerFXMask = LayerMask.GetMask ("UI");
// 这段代码和上面的代码功能是一样滴
// int layerFXMask = 1 << LayerMask.NameToLayer ("UI");
fightCamera.cullingMask |= layerFXMask;

如果想把 FX 所处的层给关闭了呢,那么使用下面的代码就行了:

int layerFXMask = LayerMask.GetMask ("UI");
// int layerFXMask = 1 << LayerMask.NameToLayer ("UI");
fightCamera.cullingMask &= ~layerFXMask;

如果是想开/关,这样来回切换 FX 所在层的开启状态,那么使用下面的代码就行了:

int layerFXMask = LayerMask.GetMask ("UI");
// int layerFXMask = 1 << LayerMask.NameToLayer ("UI");
fightCamera.cullingMask ^= layerFXMask;

以上这三种情况,分别为开启、关闭和开/关这三种状态,对应的是位运算的按位或、按位与(取反)和按位异或操作。

以上三种情况中最终得到的 cullingMask 值实际上就是一个 int 类型的数字,该方式适用于 Unity3D 中所有接受 LayerMask 作为参数的方法,例如 Physics.Raycast 方法:

public static bool Raycast(Vector3 origin, Vector3 direction, float maxDistance = Mathf.Infinity, int layerMask = DefaultRaycastLayers);

这个方法最后的参数就可以传入我们通过位运算获得的 LayerMask 值,用于指定我们需要进行碰撞检测的层。

如何在Unity3D中优雅的重命名脚本中字段名而不丢失其引用

在进行 Unity3D 开发的过程中,我们难免会碰到想要修改脚本中变量名的情况,可是呢,我们在代码编辑器或者 IDE 中对代码文件中使用到的变量进行 Refactor 之后呢,代码倒是好看了,也是不出错能编译通过了,但是到了实际运行的时候就发现出现各种空引用的问题了。

这是神马鬼啊?估计初学 Unity3D 开发的童鞋还会觉得尼玛简直不知道碰到什么鬼了,熟悉了 Unity3D 开发的童鞋呢,就会会心的一笑,尼玛的 Unity3D 简直不能再坑爹了,好吧,我再重新关联一下变量的引用吧。

这个问题的根源是啥呢?就是我们通常在脚本文件中使用了 Public 的变量,这些变量都会被 Serialized (序列化)保存到对应的 Prefab 文件并持久化到磁盘上,然后每次我们运行游戏的时候呢,Unity3D 会讲这些变量对应的资源引用加载到内存中来,这样我们就可以非常方便地在一个 Prefab 中愉快地使用另一个需要动态显示或者隐藏的 Prefab 或者 GameObject 了。这是我初次进行 Unity3D 开发时候,惊异于 Unity3D 的魔法之一。当然我现在每天都在施展这个魔法啦,不过有的时候呢,总会觉得有些不太爽,因为我有轻微的代码洁癖,只要我觉得这个字段命名的表意不准确,当我发现有更为准确的表意的变量名时,我一定会将变量名或者 Prefab/GameObject 的名称修改过来。修改 Prefab 和 GameObject 的名称倒是完全 OK,Unity3D 会自行帮你把事情做对,但是修改变量名就不一样了,例如在进行改名之后,GameControl.cs 文件中有一个名为 player 的 Public 变量,这个 player 变量指向一个场景中名为 Player 的 GameObject,此时我们的需求发生了改变,这个脚本需要控制两个 Player 了,原本名为 Player 的 GameObject 更名为 PlayerFemale 了,按照正常情况,我们的代码通常也是需要进行修改的,这样变量名和 GameObject 的名称就能保持一致了,方便阅读和理解代码,但是player变量一改名为playerFemale之后,再次运行游戏,就会得到一个空引用异常了,Unity3D 会提示你 playerFemale 变量的引用为空。

叨逼叨了这么多,那么怎么解决呢?尼玛我自己肯定解决不了,这个完全就依赖于 Unity3D 官方的解决方案了。原文链接在这里,大家直接前往围观。

我这里自己也备个忘,做法入下。

如果原先的变量名为 player,修改之后的变量名为 playerFemale,那么我们只需要在使用 Refactor 工具将 player 字段名重命名为 playerFemale,确保所有引用到该变量的代码文件中的 player 字段名称都修改过来了之后。在这个新的变量 playerFemale 上方一行,加入 [FormerlySerializedAs(“player”)] 即可。代码如下:

[FormerlySerializedAs("player")]
public GameObject playerFemale;

如果后续我们还要再将这个 playerFemale 字段名再修改为 playerAssassin 呢,那又肿么办?那就依顺序再加一行 [FormerlySerializedAs(“playerFemale”)] 在原有的 [FormerlySerializedAs(“player”)] 下方即可,代码如下:

[FormerlySerializedAs("player")]
[FormerlySerializedAs("playerFemale")]
public GameObject playerAssassin;

好了,奏是这个样子滴,好棒哒,希望 Unity3D 能变得越来越好用,省得被我们耀华一天到晚问候各种亲戚朋友,是吧。

【撒利学 Shader】之一 UV Mapping 的概念

UV Mapping就是UV贴图的意思,说白了就是将3D中的不规则多面体沿着某条线剪开之后,然后摊开到一个平面上,这样就可以将3D物体上的每个点映射到一张2D的平面贴图上了。

当然这个显然木有我说的这么简单,毕竟3D物体那么不规则,要将一张2D的贴图上的每个像素与3D物体上的各个顶点以及顶点之间映射上也不是件容易的事情,不过显然这个不是我能讨论和想讨论的事情了。我们只需要知道制作UV贴图是3D建模软件一个高级的基础功能就好了,Unity3D拿到这些UV贴图之后直接往模型上贴就好了,不过Unity3D又是如何读取2D贴图中的UV信息,并且能将这个贴图很好地贴到这个模型表面上捏?好吧,其实我也不知道,希望在这个学习Shader的过程中,我们最终能解答这个问题。

【撒利学Shader】写在前面的话

作为一个Unity3D开发者,我时常提醒自己并不是一个3D 游戏开发者,甚至都不是一个游戏开发者,更多的时候只是一个脚本程序员。这是我对自己的一个定位,也是一种现状的描述,我身边有不少做 Unity3D 开发的朋友,他们对于3D 程序开发实际上了解得并不多,但是貌似也能胜任他们正在做的工作。

这是 Unity3D 带来的一种效应,有其积极的一面,同样也有其消极的一面。积极的一面是,更多原本对3D 编程敬而远之却对游戏编程怀有兴趣的人,可以就着 Unity3D 直接开工,上手去做一些有趣的事情了,降低了整个3D 游戏开发的门槛,让游戏编程更加的亲民了;消极的一面呢,这部分半路出家杀到 Unity3D 开发圈子里头来的童鞋们呢,由于 3D 编程基础几乎为 0,所以在涉及到 3D 图形技术领域很可能直接就抓瞎了,整半天完全摸不着头脑的并不是啥奇事。我就是这半天摸不着头脑的人之一,为了让自己能摸着头脑,那么就只能从零开始,一点点琢磨了。

3D 编程中诸多的技术问题,Unity3D 通过它那万能的 Editor 实际上已经解决了很多 3D 编程中需要去面对的问题了,例如摄像机、坐标运算和坐标系转换、光照、投影,渲染等等等等。而在 Unity3D 开发过程中,可能让我这种非正规 3D 游戏开发者最为头疼的可能就是对 Shader 的一知半解了,而在一个 3D 开发者的成长之路上,这个问题早晚得是我们需要直接面对并且勇敢翻过去的一座山,唯有如此我们才能看到更加美丽的风景,如果我们有女朋友的话,还可以带着女朋友一起看。

作为一个 Shader 小白,我们最好的学习方法是啥?当然是看书咯,不过貌似这方面的书真心不多,曾经有一本《GPU 编程精粹》如今依然绝版,今天在网上搜寻了一番,最近貌似新出了两本 Shader 相关的图书,直接立刻下单《Unity 3D ShaderLab 开发实战详解(第 2 版)》和《Unity 着色器和屏幕特效开发秘笈 [Unity Shaders and Effects Cookbook],买回来看看先。

不过作为程序员的我们,在碰到问题之后的第一反应通常都是找文档,第二反应就是找 Google,对吧。不过相信所有 Unity3D 的开发者都会有一个这样的感受,Unity3D 的开发文档跟翔的区别可能都没有半毛钱那么多,所以我们也就能从官方文档中获得有限而又可怜的帮助了。剩下的我们就交给 Google 吧,Google 了一番之后,我倒是发现了两个比较不错的教程。

一个是猫大@onevcat的猫都能学会的 Unity3D Shader 系列文件两篇:

另一个是90后大神@浅墨_毛星云的【浅墨 Unity3D Shader 编程】系列文章了:

感谢两位非常棒的分享,让我能快速地理解了 Shader 的基础用法,用了差不多3天时间消化了一下这几篇文章,现在已经能对着各种 Shader 插件中提供的 Shader 逐句分析其用意和用途了。

不过本着记录和分享,以及锻炼一下自己技术写作能力,敦促自己继续学习的目的,我想把我自己持续学习 Shader 的整个历程记录下来。所以这个系列并不会像浅墨_毛星云或者 onevcat 的文章一般抽丝剥茧地从头细说 Shader 的学习路线,而是会以一个对 Unity3D 开发已经较为熟悉,但是对 Shader 不是那么了然的程序员的视角来记录学习过程中遇到的小问题,以及在解决这些问题的过程中获得的信息以及其相应的延伸内容。