月度归档:2014年12月

Unity3D 中 useGravity 和 isKinematic 以及 applyRootMotion 的备忘

在最近几天的开发中,碰到一个比较有意思(恶心)的问题,需要达到的效果如下:

  1. 有一个 NPC,在受到攻击时,会播放一个从站立姿势到击飞浮空的动画;
  2. 在 NPC 切换击飞浮空动画的同时,NPC 需要同时向斜上方飞行,飞行到指定高度待机。

通常我们有几个方案可以选择:

  1. 通过 Unity3D 自带的物理引擎模拟,给 NPC 一个斜上方的速度或者作用力,这个通过 Rigidbody.velocity 设置一个速度向量或者通过 Rigidbody.AddFore/Rigidbody.AddRelativeForce 进行设置就 OK 了;
  2. 自己根据需要的速度自行计算每一帧 NPC 的位移,然后通过直接设置 NPC 对象 Transform.position 属性来达成 NPC 斜向上飞的效果。

这两个方案从理论上来说肯定都是 OK 的,其实方案一最终在 Unity3D 内部的 PhysicX 引擎中的实现也是通过对 Transform 的 position 进行设置来完成的,只是额外还会进行碰撞监测和阻力计算等等。

实际开发的过程中,我分别使用了这两种方案来进行测试,对比之后发现:

  1. 方案一模拟效果确实更真实,而且省去了很多的计算过程,但是简单的设置速度和作用力可能无法达到策划设计的受击效果,因为我们制作的是格斗游戏,对于受击对象最终浮空的位置有相对严格的要求,需要为各种连续攻击留足设计空间,这个就不展开说了。总之,如果完全交给物理引擎来演算模拟的话,会出现一些不可控的结果,而那是我们不想要的,所以需要通过一些其他的手段来进行反算,使得我们设置了某些参数之后,物理引擎模拟最终的结果就是我们想要的(最终我就是这么做的);
  2. 方案二简单,而且结果是完全可控的,NPC 飞行的轨迹完全是可以预计的,但是由于直接控制 Transform 的 position 是完全无视在整个游戏场景中的物理碰撞的,在做 position 设置之前每次都需要自行检测 NPC 是否会与场景中可碰撞对象之间的关系,这个性能的开销如何控制以及如何做到有效的检测对于目前的我来说是一个较难的课题,所以最终我放弃了。

那么按照方案一来执行的过程中,又碰到了什么有意思的问题呢?

NPC 在受击之后,通过 Animator 设置了一个 Trigger,触发 NPC 从站立到击飞浮空的动画切换,同时给 NPC 对象的 Rigidbody 一个 Velocity,代码如下:

[code lang=”csharp”]
mAnimator.SetTrigger ("KickFloat");
rigidbody.velocity = velocity; // NPC 斜向上方飞行的初速度
[/code]

可是最终执行的效果是 NPC 只会往上垂直飞行,在水平方向上根本就没有速度,这是为啥呢?就在我快要把头发拽下来的刹那,我决定先去泡一杯劣质的越南『中原 G6』速溶咖啡,等我把咖啡泡好了回到座位上,突然意识到其实在很早之前碰到过另一个问题,当时就是因为 Animator 的 applyRootMotion 变量给惹的祸,卧槽啊~

于是,我很精灵的在刚刚的代码基础上加上了一个前缀处理,代码如下:

[code lang=”csharp”]
mAnimator.applyRootMotion = false;
mAnimator.SetTrigger ("KickFloat");
rigidbody.velocity = velocity; // NPC 斜向上方飞行的初速度
[/code]

尼玛这下清净了吧,哥哥我半个下午的时间就是在不断的怀疑人生和自我啊,不过看在最终还是搞定的份上,我也就不再说啥了。然后 NPC 在做从站立过渡到空中浮空待机的动作的同时,终于按照我设定的速度的方向移动了,如释重负啊。

说了这么老些题外话(什么?这些竟然都是题外话?尼玛这已经很丰富了喂,可以做单独一篇文章了喂,那你到底要说的是啥啊?),我的目的只有一个,我就是想说明一下我为啥要针对这三个变量来说道说道,因为它们真的蛮有意思的(恶心了我这个 Unity3D 傻逼)。为了让自己以后不再傻逼,所以还是记录一下,做个备忘吧。

Rigidbody.isKinematic,这个属性用于控制物理引擎效果是否会在生效,如果这个属性为 True,那么所有通过物理引擎给物体施加外力和设置速度等等就不会再有任何效果了。例如如果想做一个让 NPC 受击浮空之后,在空中停留的效果,就可以通过设置这个变量来实现,只要该变量为 true,物理的重力效果就不会生效,NPC 并不会自动往下做自由落体运动,而会在空中保持之前的动画状态,位置也不会发生改变。

Rigidbody.useGravity,这个属性就是用来控制物理引擎模拟时是否加入重力影响,例如我们在做击飞效果的时候,通常会通过给受击对象一个击飞后方向的作用力或者运动速度,如果需要考虑重力因素的话,可能会更真实一些,但是有的某些情况下,我们可能不想因为重力的作用对受击对象的飞行轨迹产生影响,那么就可以通过将该变量设置为 false 就可以关闭重力在屋里引擎模拟过程中对物体整体运动速度和轨迹的影响了。

Animator.applyRootMotion,这个属性是用来控制物体在播放骨骼动画的时候是否应用骨骼根节点的运动参数,也就是说在动画播放的过程中是否使用动画中物体的位移。在实际制作的过程中,总会有某些动画自身带位移,而某些动画是不带位移的,对于那些动画中自带位移的,那么就可以通过设置这个变量为 true 就可以让对象在播放动画的过程中按照动画中的位移进行移动,这样动画的效果也会更逼真一些而不是通过程序来控制对象的位移(程序大部分情况下都是简单的做匀速或者加速运动,无法做到完全与动画中动作移动幅度完全吻合,可能会出现一些失真的情况)。但是如果一旦设置了这个变量为 true,那么请一定注意,这个会对物理引擎在模拟对象的运动轨迹时产生直接的影响,例如在某个动画 A 中,对象只向 Y 轴方向进行了移动,在 X 和 Z 轴是静止的,那么我们在播放 A 动画的时候,如果使用 Rigidbody 设置速度或者施加外力,还是不会让物体在 X 和 Z 轴上发生位移的。这是因为,在整个动画播放的过程中(例如 0.5 秒),Animator 会根据动画中物体的位移信息对物体的速度进行赋值,这样达到使用骨骼根节点的位移的效果,也就是说,我在播放动画过程中的任意时间给物体设置了 X 或者 Z 轴方向上的运动速度,后续的动画播放帧中,速度又会被 Animator 强制赋值为跟动画文件中的位移信息一致。

综上,咱们来说说碰到哪些情况,我们需要检查哪些变量?

  1. 如果出现物体无论设置运动速度还是施加作用力,愣是没有任何效果,赶紧检查一下 isKinematic 变量是否为 true,如果是的话,那么肯定是不会有效果的,当然也不会自由落体了;
  2. 如果出现物体往某个一直运动,根本停不下来,那很有可能是 useGravity 这个变量为 false,如果真的不想要这个重力效果,那么设置为 false 刚好可以从此摆脱地心引力了;
  3. 如果出现物体无论设置运动速度还是施加作用力,而且也没有设置 isKinematic 变量为 true,那么先别怀疑人生,赶紧确认该物体的 Animator 组件是否设置了 applyRootMotion 变量,否则头发揪没了也无济于事啊。

关于 Mecanim 动画系统中,如何让物体应用 Generic 动画中的位移,可以 移步这里 查看。

你想做什么样的老板

这两天各种媒体上都是关于陌陌 IPO 的新闻,以及陌陌 CEO 唐岩前雇主网易在陌陌 IPO 前夕发布一些关于唐岩个人的负面新闻,大家众说纷纭,被大家戏称为又一次『撕逼』,甚至有人臆想为陌陌上市之后会对易信造成更大的市场阻力等等。

作为一个局外人,其实我永远都只是一个不明真相,多少又有点愤慨的群众。莫说易信实际的竞争力远不如陌陌来得猛烈,就说陌陌平台通过游戏给陌陌带来的变现能力也远不是如今的易信可以望其项背的。作为一个观众,其实我们只看到了舞台的一角,也可能幕布还没有打开,我们就已经对着幕布在不断地 YY,想象着幕布打开之后会是啥景象,也许幕布还没有打开,观众们凭着自己丰富的想象力以及各种阴谋论,足以让自己达到高潮,然后匆匆散场,而实际的演出都木有开始,也许这番乱象才是真相。

回到文题,实际上对于这个热点新闻,我完全木有任何想法也木有观点,跟我半毛钱关系都木有,去他大爷的。不过关于前东家在自己前雇员上市的前夕做出如此举动,自己心里头有点莫名的想说点啥的冲动。

什么样的老板会这么干呢?这么干的目的是啥呢?干了这个事情之后,能给自己带来什么呢?

问出这三个问题之后,其实也很难回答,因为我自己不会这么干,其实很难去分析别人这么干的个中缘由,也因为我们不知道当事人之间的各种利益纠葛和陈年旧事,所以我们回到自己想做什么样的老板这个话题上吧。其实谈自己想做什么样的老板,也意味着自己想跟着什么样的老板。

在我看来,老板需要具备以下一些比较优良的品质,方可让自己的雇员感觉到比较的舒服。

  • 能带领大家赚到钱,赚到钱之后愿意与大家分享;
  • 自己犯的错误要自己承担,不能把自己的屎盆子扣到别人头上;
  • 栽培员工是自己的职责,不能因此站在道德的高地;
  • 能坦然面对团队中能力突出者,并及时做出肯定和认可,不吝啬赞美之词;
  • 疑人不用,用人不疑,适当放权,相信别人会比自己做得好;
  • 面对团队成员离开,保持平静的态度,宽容地对待这些与自己想法不再相同的前伙伴;
  • 珍惜所有一起做事情的人们,有缘才聚到一起,别弄得像掉进钱眼里头去了一般。

说得不够抽象也不够简洁,也没有谈到类似于『克己,以身作则,思路开阔』等等优秀品质,这倒不是因为我认为这些特质不够重要,这些也非常重要。其实我们可以回头看看自己的老板,他们身上都有着很多不错的品质,通常都很能拼,能打硬仗,业务能力即便不算超一流肯定也是行业里头顶尖的家伙。可是真的只需要这些就可以算是一个好老板吗?

我一个人能写别人 10 个人都写不来的好程序,我一个人 1 年能卖出别人 10 年都卖不出的业绩,我一个人能每天工作 18 个小时,我可以一个月都不陪老婆逛街,天天就说工作。这些是不是很牛逼?是,确实很牛逼,很多人都做不到这些牛逼的事情,这些人也确实有很大部分就是现在的某些老板,他们都很优秀。

不过作为一个员工,其实每个人对于老板也是有要求的,员工都希望老板能给自己涨工资,希望老板能比较友善,希望老板给自己发展机会和空间。可能是因为自己之前碰到过一些不太如意的事情,也跟很多朋友聊到过类似的事情,心中有些郁积之气,如此这般道出来之后,心中也稍微畅快一些。

写下这些字,目的是提醒自己,如果有一天自己选择老板,或者自己也成为了老板,要记住除了自身要牛逼之外,还要记住这些自己曾经作为员工的时候,期待自己的老板会有的一些优良品质,不要自己最终成为了自己最初讨厌的人。那样的话,幸甚!

Unity3D 编辑器通过拖拽获取文件路径

Unity3D 项目开发过程中咱们难免会碰到一些需要设置 Prefab 路径的时候,例如某个攻击动作的特效,虽然我们最终都是通过填表来完成,如果完全手动填表那实在让人崩溃啊,碰到有的特效命名不小心填错了,那就更加无聊了,所以作为程序猿的我们肯定是要通过更加友好的方式来获取各种目录下各种文件的路径了。

我们早已经习惯了在各种地方通过拖拽来进行文件路径设置了,那么显然拖拽是非常简单又人性化的设定啊。假设我们现在有一个技能设置的编辑器,如下图:

技能编辑器

特效路径就是我们想设置的,而且可能会在平时做一些修改,每次都自己手动输入显然不是我们想要的,那么怎么让这个 TextField 支持将 Project 视图下的文件拖拽到这个文本输入框中,并且让它自动获取我拖拽的文件的路径呢?

『技能编辑器』这整个 Window 其实就是通过各种小控件给拼起来的,Unity3D 中默认所有控件都是支持拖拽事件的,所以我们不需要做任何设置,直接针对我们的需求开始就好了。

我们想要的是,特效路径这个 Label 后边跟着的这个可输入的 TextField 文本输入框可以接受拖拽文件的方式来进行路径的输入。那么我们需要做的就是在这个『技能编辑器』的 Window 中监听鼠标拖拽的事情,同时判断鼠标拖拽时鼠标的位置是否处于这个可输入的 TextField 文本输入框之中就 OK 了,上代码。
[code lang=”csharp”]
EditorGUILayout.BeginHorizontal ();
EditorGUILayout.LabelField (" 特效路径", GUILayout.Width (80));
// 获得特效路径文本输入框的位置和大小参数,用于后续判断鼠标在文本输入框内使用
Rect sfxPathRect = EditorGUILayout.GetControlRect(GUILayout.Width (250));
// 用刚刚获取的文本输入框的位置和大小参数,创建一个文本输入框,用于输入特效路径
string sfxPathText = EditorGUI.TextField (sfxPathRect, meleeAttackSection.sfxPath);
// 判断当前鼠标正拖拽某对象或者在拖拽的过程中松开了鼠标按键
// 同时还需要判断拖拽时鼠标所在位置处于文本输入框内
if ((Event.current.type == EventType.DragUpdated
|| Event.current.type == EventType.DragExited)
&& sfxPathRect.Contains (Event.current.mousePosition)) {
// 判断是否拖拽了文件
if (DragAndDrop.paths != null && DragAndDrop.paths.Length > 0) {
string sfxPath = DragAndDrop.paths [0];
// 拖拽的过程中,松开鼠标之后,拖拽操作结束,此时就可以使用获得的 sfxPath 变量了
if (!string.IsNullOrEmpty (sfxPath) && Event.current.type == EventType.DragExited) {
DragAndDrop.AcceptDrag ();
// 好了,这下想用这个 sfxPath 变量干嘛就干嘛吧
}
}
}
EditorGUILayout.EndHorizontal ();
[/code]

VPS 再次搬家

这次搬家搬到了 Linode 上,从最早的免费 1 年 AWS EC2,搬到后来的 42 区,再到 DigitalOcean,然后到 Linode,折腾的次数也不算少了,所幸每次搬家耀华同学都非常快的就完成了所有的工作。我只需要把域名解析改一下就 OK 了,其他的一切照旧。

此次注册 Linode 帐号的时候还遭遇了一次必须上传身份证扫描件和信用卡扫描件的事情,有点小意外,不过还好,前后两个小时左右,帐号开通,一切正常。Tokyo 机房的速度简直有点小残暴啊,直接使用 VPN 访问国内所有站点竟然不卡,简直就是禽兽啊。

此次逃离 DigitalOcean 的主要原因呢,就是在大陆地区经常无法 Ping 通主机,也就无法顺畅地访问博客站点了。之前差不多每次重启实例就 OK 了,这次多次重启均无效,貌似 IP 被封了,懒得发 Ticket 申请换 IP,而且对于 DigitalOcean 旧金山机房的速度也不是特别满意,索性直接切换到了 Linode 上,目前使用情况良好,希望能保持。