UnityEngine.Object.Destory 和 NGUITools.Destroy 方法之间的区别

我们先来看看 NGUITools.Destroy 方法:

static public void Destroy (UnityEngine.Object obj)
{
    if (obj != null)
    {
        if (Application.isPlaying)
        {
            if (obj is GameObject)
            {
                GameObject go = obj as GameObject;
                go.transform.parent = null;
            }

            UnityEngine.Object.Destroy(obj);
        }
        else UnityEngine.Object.DestroyImmediate(obj);
    }
}

我们可以看到这个方法最终也是调用的 Unity3D 提供的 UnityEngine.Object.Destroy 方法,区别一是根据当前游戏是否在运行,然后选择调用 Destroy 还是 DestroyImmediate 方法,区别二是在调用 Destroy 的逻辑中加入了将销毁对象的 Transform 的 parent 设置为 null 了,这个小细节有什么作用呢?NGUI 这么一个成熟的插件为什么在这个地方会做这样的处理呢?之前我并不知晓其中的缘由,直到今天我碰到了一个跟 Transform.childCount 相关的问题,我才懂了。接下来我们来看一个栗子,这段代码中,我们要做的就是从一个父控件中移除子控件,直到父控件中子控件的个数与目标数量一致。

void DestroyChildenToCount (Transform parentTransform, int destChildCount)
{
    if (parentTransform == null)
        return;
    if (parentTransform.childCount <= destChildCount)
        return;
    while (parentTransform.childCount > destChildCount) {
        UnityEngine.Object.Destroy (parentTransform.GetChild (destChildCount));
    }
}

上面的这段代码,我们有看出什么问题来吗?我是没有看出来,所以我就运行了这段代码,然后我的 Unity3D 就把电脑的 CPU 全给吃掉了,Unity3D 就无法正常响应操作了然后就崩溃了。通常我们这个时候的第一反应就是死循环了对吧,那么问题出在哪儿,这段代码里头就一个 While 循环,那么肯定就是这个循环出问题了,对吧。我们再来看看这段代码可能出问题的地方,我们以为只要持续的每次删掉索引为 destChildCount 的子控件,直到父控件的所有子控件的数目与目标数一致之后退出循环,达到我们预期目的,但是事实上这个 UnityEngine.Object.Destroy 方法的执行并不是及时的,这就导致了 While 循环一直无法退出的问题。我们来看看 Unity3D 官方文档上对 Destroy 方法的描述:

Removes a gameobject, component or asset.

The object obj will be destroyed now or if a time is specified t seconds from now. If obj is a Component it will remove the component from the GameObject and destroy it. If obj is a GameObject it will destroy the GameObject, all its components and all transform children of the GameObject. Actual object destruction is always delayed until after the current Update loop, but will always be done before rendering.

红色字体部分明确说明了 Destroy 的操作会在本次刷新的 Update 循环之后执行,但是会在整体渲染之前完成,这就意味着这个里头还是存在时间差的,例如每帧 0.033333 秒执行时间,有这么多的时间留给 While 循环,它不给你整出事儿来都很难啊,对吧。

所以现在我们就能理解 NGUITools.Destroy 方法中在 Destroy 对象之前将其 Transform 的 parent 指向了 null 的意图了,这样就可以及时地将被销毁对象从其原本的父对象节点上移除了,这样我们的 While 循环就可以及时的退出了。如果你跟我一样有类似的销毁某个对象的指定数目子控件的需求的话,我想这会是一个蛮有意思的知识点的。

「起风了,好好活下去。」

读完渡边淳一的《失乐园》,感觉心里痛快了一些,那些焦虑和矛盾随着久木和凛子的陨灭,一一幻去。

通篇均匀分布的关于性爱过程极致的描写,细腻到位的心理变化的剖析,全书的故事情节起伏不大,从故事的发展上来看甚至显得有些冗长了,但是全篇并无废话,所有的文字都是必须的。

唯有如此细致详尽地书写,方可让久木和凛子两人最终在情爱的顶峰慷慨赴死成为顺理成章的自然事件,前面的众多安排让两人的赴死变成了不是安排,反倒自然了许多,这恐怕也正是作者的意图吧。

久木和凛子这对绝命鸳鸯,在遭遇现实社会伦理的挤压之后,依然选择了追求自由而极乐的情爱,一步步堕入那无底的深渊,深知这深渊下面是绝对的毁灭,而又心甘情愿地堕落,直到最后双双的陨灭,将自己的人生定格在了最幸福的顶峰,最绚烂的时刻。

全篇细致入微的性场景描写,会让你自然地产生生理反应,在此之余,久木和凛子面临的社会伦理的挤压,心中的矛盾、负疚和渴望,会让你在焦虑之中渴望事态尽快进入下一个阶段的发展,直至二人在轻井泽的别墅中对饮下那口猩红的玛歌堡葡萄酒。

「起风了,好好活下去。」

2015 年 11 月读书笔记

《皮囊》

忘了什么时候在哪儿听说过《皮囊》这本书的名字,原以为这会是一部小说。但是在多看上试读了之后,方才知晓这是一部非虚构文学作品,看到阿太对于皮囊的态度以及母亲对于房子的执念之后,买了一本纸书回来,每日在上下班地铁路上读。

蔡崇达的文字给我一种可怕的真实,毫无保留地将自己内心剥开来呈现在你面前,那一个个人那一件件事,其实并没有什么波澜,都是平淡又熬人的日子在前面一天天地等着你,你在迈出步子之前已经准备好了。而在这种真实面前,很多的时候我们只能是无能为力,或者欺骗自己「只要我能 XX 样,那么这个事情就能 XX 样」,然后义无反顾地一头扎进这滚烫的红尘中去洗练自己,我们努力,我们付出,我们最终小有成绩,我们偶尔沮丧,我们偶尔欣喜,我们梦醒。

回头一望,这些日子还是在前面等着你,感觉就像是在异乡车站偶遇多年未见的少时伙伴,一句话哽在喉头不知如何说起,然后车来,伙伴上车了,回头看了你一眼。

这样的书读来并不轻松,心情并不会太愉悦,但是读下来有一种出了一口浊气的感觉。


《尘埃落定》

这是一本打开之后就不愿意合上的小说,一周之内就读完了,觉得意犹未尽。整部小说的故事性非常好,读起来非常的畅快,通过傻子二少爷的眼睛看到整个土司群的没落。

文中诸多对于麦其土司官寨这个小社会组织以及土司领地,乃至整个土司族群的组织结构的描述让人仿佛置身其中,领略到这种古老落后而又存在了很长时间的社会组织结构的特别。麦其土司、土司太太、麦其家大少爷、卓玛、塔娜、索郎泽郎、尔依、书记官、管家、茸贡土司和拉雪巴土司等等,读完之后个个鲜活地存在你的脑海,随时都可能记起来,因为你总会在生活中偶尔碰到这样的人。


《一句顶一万句》

我喜欢杨百顺,也许我就是杨百顺。通篇故事没有什么起伏,我更喜欢上部《出延津记》,出场人物繁杂,每个人都是非常非常小的角色,所有人都这么活着,这么过着。杨百顺也想这么活着这么过着,可是偏偏事情总是找上他,找上他的事情呢不大不小,却总是能要了他的饭碗,而他无非就只是图个过活罢了。最终他丢了吴巧玲,出了延津,改名罗长礼,虽然未能做成叫丧的活计,总算过活了下来。


《了不起的盖茨比》

也许是因为有个电影口碑还不错,小李子还因着这个再次被奥斯卡提名了,所以原本就抱着比较高的期望来读的,在读书前我并未看过电影,想着等把书读完了,在看看电影究竟拍得如何。

这是本小书,应该算个大中篇吧,故事并不引人入胜,不过对当时美国社会的现状和生活描绘得应该还是蛮到位的。读完之后,我是没有感受到有多强烈的冲击,只能说是一部还算不错的作品吧,也许是翻译的问题(我读的是多看上李继宏的版本),当然也许原文也就这样吧。

如何在 OSX 10.11 EI Capitan 中给移动硬盘分区并支持 NTFS

趁着这几天京东白条免息,买了一个新的移动硬盘,想着给自己服役快5年的老移动硬盘做个备胎来着,上午的单下午送到公司,拿过来那就先分一下区呗,想着布局应该是这样的,两个分区为 OSX Extended (Journaled) 格式,一个用来备份一些重要数据,一个用来做 Time Machine 备份用,剩下的那个分区就留作共享的 NTFS 分区吧,偶尔还是会用到这个 Windows 的,家人同事偶尔用用的话,这个 NTFS 还是比较有必要的,否则空有个硬盘完全无法用上也是件蛮悲剧的事情呢。

好,名字我都起好了,就分三个盘:

  1. Data-OSX
  2. Backup-OSX
  3. Data-NTFS

感觉棒棒哒,那么开始吧。打开 Disk Utility 开始吧,然后碰到了各种奇怪的问题,这儿我就不多说了,后面一步步来吧,碰到问题的地方,我都会一一吐槽的。

第一步,格式化整个硬盘

QQ20151110-2

从左侧列表中,选择 External 下的 HGST TOUR S Media (这个自己要加小心啊,别选错了,一定要选外接的自己要格式化的硬盘),选中硬盘了之后,点击工具栏上面的那个 Erase 按钮(中文应该是擦除吧),这时会弹出一个提示框,然后我们就可以输入各种参数了。

  • Name,名字随便起吧;
  • Format,看上我们可以选择的有 OS X Extended (Journaled) 系列格式、MS-DOS (FAT) 和 ExFAT 三个格式可以选择,然而实际经验告诉我们如果你想分区的话,实际上最终只有 OS X Extended (Journaled) 格式可以选择,相信我吧,不会错的;
  • Scheme,看上我们又有得选择噢,有 GUID Partition Map、Master Boot Record(俗称 MBR)和 Apple Partition Map 这三个选项,Apple Partition Map 我压根就没有试过,我想 MS 肯定是不会支持这个鬼的(其实我希望我错了),然后我尝试了一个我听得比较耳熟的 MBR,实际证明我又错了,最终的结论就是如果我想让这块硬盘能分区,我能选择的就是 GUI Partition Map。

综上,请按照上图所示的选项进行设定,也就是 Format 必须是OS X Extended (Journaled),Scheme 必须是 GUID Partition Map,然后点击 Erase,稍微等一会儿,整个硬盘格式化就完成了。

第二步,新建分区

Mac OSX 下面显示硬盘是这样的,一个物理硬盘下多个逻辑分区,那么我们分区是针对物理硬盘来进行操作的,所以还是先选中 External 下的 HGST TOUR S Media 物理硬盘,然后点击工具栏上的 Partition(分区)按钮,这时候我们会看到一个这样的弹出面板:

QQ20151110-3

这个弹出面板描述的内容就是,目前这个物理硬盘上就有一个名为 Laputa-HGST 的逻辑分区,分区格式是 OS X Extended (Journaled),容量大小是 999.861 GB。

这个时候,我们点击左侧蓝色圆饼下面的加号按钮,新建一个分区,用鼠标点击圆饼中你想要的那个分区,那个小白点是可以用来拖动调整磁盘大小的(当然通常我们数学都还蛮好的,还是键盘输入更妙一些):

QQ20151110-4

然后给分区命名为 Data-OSX,设定文件格式为 OS X Extended (Journaled),设定其大小为 300 GB。

依次再创建一个名为 Backup-OSX 的分区,文件格式为 OS X Extended (Journaled),设定其大小为 350 GB:

QQ20151110-6

现在一个饼实际上已经分成三块了,那么剩下的那个分区就是我们要的那个用来做 NTFS 格式的分区了,用鼠标选中之后,设置分区名为 Data-NTFS,调整分区格式为 MS-DOS (FAT):

QQ20151110-7

这下三个分区都设定好了之后,点击 Apply(应用)按钮,等一会儿就 OK 了,由于 Mac OS µX 自身并不支持 NTFS 文件的写入(但是支持读),所以也没有将磁盘格式化为 NTFS 的功能。Mac OS X 上有一个付费的 NTFS for Mac OS X 软件很好用,装上之后,读写 NTFS 硬盘文件就全无障碍了,他们家还有 ExtFS for Mac OSX (用在 Mac OS X 上访问 Linux 系统下常用的文件格式 Ext 系列格式的磁盘),还有一个 HFS+ for Windows (用在 Windows 系统下访问 OS X Journaled 系列格式的磁盘)。

那么接下来格式化 NTFS 盘的这一步,我们就得自己把硬盘插到 Windows 电脑上再格式化一下就好了。


这次格式化硬盘碰到的问题就是 Disk Utility 这货跟之前版本的 OS X 中的不一样了,然后碰到了一些隐性的设定,来回折腾了好些工夫,而网络上能找到的都是早些时候版本的 OS X 下的移动硬盘格式化的教程,既然自己遭罪了,我想可能也会有人碰到跟我一样的问题,那么就纪录一下吧。

 

Unity3D 中如何实现 C# 与 JavaScript 之间的互相访问

在项目研发的过程中,总会依赖众多的插件以及 Unity3D 自带的一些工具脚本等等,而这些插件和工具脚本的作者呢,有些人就偏爱于使用 JavaScript 来实现一些功能。如果在项目中恰好碰到了,而自己项目中使用的脚本语言与使用到的第三方的脚本并不是同一个语言,这个时候不同类型的脚本之间的互访问操作就很有必要了。

我们项目中有一个对 UI 整体进行模糊显示效果的需求,也就是当我们在 UI 界面上再次弹出二次提示确认框时,我们希望不出现多个界面重叠,导致整个画面很乱,视觉焦点不知道在哪儿的问题,我们希望在二次确认框弹出时,能将其后面的整个 UI 面板进行模糊显示,如此一来这个效果就会好不少,我们可以对比一下。

使用了 UI 模糊效果 未使用 UI 模糊效果

 

 

 

 

显然我们还是很有必要做一个模糊效果的,对吧。那么我们就找到了 Unity3D 自带的 Blur 脚本。这货在 Unity3D 的 Standard Assets 包中,我们需要通过 Unity3D Editor 的菜单 Assets -> Import Package -> Image Effects (Pro Only) 导入相应的资源包。然后我们就能在项目工程的 Assets/Standard Assets/Image Effects (Pro Only)/ 下找到 Blur.js 这个文件了,其实同一目录下还有一个 BlurEffect.cs 的文件,不过 Unity3D 官方目前推荐的是 Blur.js 这货,BlurEffect.cs 的这个已经是一个废弃的脚本了,官方文档上的描述如下:

This is now deprecated. Use Blur (Optimized) instead.

所以我们应该果断修改为使用 Blur.js 这货,看看这货的名字就晓得了,Blur (Optimized),名字里头都自带优化了,选它肯定没错了。好,那么我们就开始编码吧,因为这个模糊是需要动态控制其开关的,那么我们通常的做法呢就是动态地获取一下 UI 所使用的 Camera 上是否挂载有这个 Blur 组件,如果没有挂在载呢,就通过 AddComponent 方法将这个组件挂载上去,如果有的话,那么就直接将获得的组件设置的 enable 设置为 true 就好了,关闭的时候就将这个组件的 enable 设置为 false 就行了。

可是泥煤的这个 Blur 组件是个 JavaScript 代码写的呢,我们在 MonoDevelop 中写代码的时候,会发现自动提示不好使了,代码高亮中只要有 Blur 的地方都是个红色的错误代码颜色块在那儿,让人看着有点发怵啊,尼玛到底能不能用啊?我的第一反应就是这货估计不能直接在 C# 中使用,可是为啥这 JavaScript 代码中直接就能继承自 MonoBehavior 呢?反正有点懵,然后就 Google 了一下,也有人碰到这个问题,然后有个哥们给了一个解决方法就是把 Unity3D 提供的这个 JavaScript 语法的组件翻译成了一个 C# 版本的,擦。好吧,那我们就拿来主义了,直接拿进来,貌似可以耶,然后就屁颠屁颠地测试了一下,发现在 Editor 上是好使的,但是在 iOS 设备上不好使了,这是什么鬼?当然因为自己也是拿来主义,并没有仔细去查看人家的代码,想必代码是没啥问题的,真正的问题我会在后面单独描述。

既然怀疑可能是别人翻译的代码有问题(其实这个翻译非常简单,能出错也算是见鬼了,可是谁让程序运行出问题了呢,我只能怀疑人生了),那我就继续 Google 吧,然后发现实际上这个 C# 和 JavaScript 两者之间的交互是完全木有问题的,毕竟都是通过 Unity3D 的脚本引擎来编译的,既然支持两种语言编程,那么必然两者之间的通信是没有问题的,因为实际上我们通过脚本操作的所有对象本质上都是一样的东西才是啊,只是提供了两个不一样的方法而已。最终呢,我找到了这篇文章: Unity3D: JavaScript->C# or C#->JavaScript access ,文中明确地指出这两个脚本语言之间的交互是完全没有问题的但是前提是提供出来给 C# 或者 JavaScript 语言访问的脚本,必须放在 Standard Assets 目录或者 Plugins 目录下,因为这两个目录下的脚本是最先编译的模块,编译完了之后不论是啥语言写的脚本,实际上对于 Unity3D 来说,这就是一个组件对象,你用什么方式都是可以正常访问和操作的。

也就是说,我们可以对那些代码自动完成的提示不生效,代码高亮时红色警告的部分开启忽略模式了,这只是因为 MonoDevelop 自身对脚本语言支持的原因导致的,MonoDevelop 只是对 C# 语言有一个很好的支持,而对于 JavaScript 实际上它什么也做不了,我们就径直把代码写好,交给 Unity3D 去编译就好了,只要不报错,运行一下,发现效果正确。

QQ20151029-0@2x

那我们就再放到手机上试试呗,谁知道在 iOS 上依然木有效果,这就奇怪了,那就一步步来呗,既然我们都用的是 Unity3D 官方提供的脚本和资源,而且官方声明了这个是支持所有平台的,那么应该不至于出现这种奇怪的问题的啊。那么首先排除是是否通过 AddComponent 动态挂载 Blur 组件导致的,试了一下貌似还真是这么回事,如果我们通过在场景或者 Prefab 中将 Blur 组件挂载到 Camera 上,那么运行的时候一切正常,如果采用 AddComponent 的方法的话,除了需要在代码里头通过 Shader.Find 方法找到我们需要的 Shader 资源还需要将其赋值给 Blur 组件的 blurShader 变量。

不过即便是我们按照上面的这个代码来做,测试表现只在 Editor 环境下有效,在 iOS 平台上依然没有效果,最终我们只能看 Log 了,不看不知道,一看就晓得了,错误日志如下:

– Completed reload, in 0.131 seconds
Missing shader in Camera (Blur)
UnityEngine.Debug:Internal_Log(Int32, String, Object)
UnityEngine.Debug:Log(Object)
PostEffectsBase:CheckShaderAndCreateMaterial(Shader, Material)
Blur:CheckResources()
PostEffectsBase:Start()

从这个日志中输出的信息来看,就是缺失了 Blur 脚本中使用到的名为 「Hidden/FastBlur」 的 Shader 了,那么为啥会缺失呢?为啥在 Editor 中就不会出现这个问题呢?这么一问就猜到可能是因为打包发布到 iOS 设备的过程中,这个 Shader 没有被一同打包发布到 iOS 设备的安装包里头。既然有了疑问,那就去找证据吧。我第一时间查看了 Shader.Find 方法的文档,文档中有这么一句:

When building a player, a shader will only be included if it is assigned to a material that is used in any scene or if the shader is placed in a “Resources” folder.

这已经非常明确地解释了问题出现的原因了,也就是说如果我们这个 Shader 不是放在一个名为 Resources 的目录(子目录也行)下,那么只有在场景中引用到了这个 Shader,它才会被打包到最终的执行游戏包中。这下问题就明了了,就是因为我们采用了完全动态的方法来添加这个 Blur 组件,而这个 Blur 组件依赖的这个 Shader 呢因为其处于 Standard Assets/Image Effects (Pro Only) 目录下又没有被其它场景中的任何对象引用和使用,所以 Unity3D Editor 在打包发布的时候呢,就没有计算到需要把这个 Shader 资源给包含进来,最终导致了这个问题。所以,我们知道了其实之前提到的改写 JavaScript 为 C# 的那位童鞋其实并没有做错,我们这个问题是因为其它原因导致的。

那么解决这个问题就是简单的在我们可能需要使用到 Blur 组件的某个对象上事先挂载一个 Blur 组件,但是将其 enable 设置为 false,这样一来呢,可以确保名为「Hidden/FastBlur」 的Shader会被打包进来,同时呢又可以满足我们动态开关 UI 界面模糊的功能了。

总结一下:

  1. Unity3D 中的 C# 和 JavaScript 脚本之间是可以互相访问并交互的,但是要求这些被访问和操作的 C# 和 JavaScript 组件必须放在名为 Standard Assets 或者 Plugins 目录下,这样保证被访问和操作的组件是第一时间被编译的,那么在这两个目录之外的其它脚本就可以随意使用它们了,同时呢,我们不要被 MonoDevelop 的一些表现给唬住;
  2. Blur 组件不生效的原因在于 Shader 资源没有被打包到执行程序中,而并不是其它别的原因,所以打蛇要打七寸就是这个道理。