最近在做的项目里很经常的用到动画系统。其中遇到了一个比较特别的需求:在一个动画播放的过程中,让一个对象去跟随外部的对象。由此引发了一些对Unity动画系统的一些有趣的思考。

初步尝试

我们在制作一个2D的动作游戏,其中需要实现玩家角色的摔投技。摔投技需要让玩家摔投的怪物跟随着一个特定的路径(大致是玩家手的移动轨迹)运动,最后被砸到玩家身后的地上。

首先,这个轨迹肯定需要可视化编辑,正好玩家的动作部分的播放也是用的Unity的Mechanim动画系统,所以就考虑把轨迹部分编入动画系统实现。

这个轨迹是相对于玩家而言的,因此我在玩家下新建一个指示跟踪位置的节点(称为跟踪节点),并且在玩家的动画里操作该节点的位置。接下来想要让怪物跟随这个指示节点。这可以通过写一个小的跟随脚本,在脚本里公开一个follow对象,并在follow对象不为空的时候重设当前节点的位置实现。

| Player (Animator 1)
 |- Follow Node

| Monster (Animator 2)
 - Component FollowObject
    - GameObject follow

接下来,只需要在动画里动态的改变follow对象,让它先设置为玩家的跟踪节点,在无需跟踪时设置为空,我们想要的跟踪效果就实现了。

……

真的是这样么?

外部对象

在设置完毕以后,直接在编辑器里进入Play模式,我们发现怪物的确如我们所设想的,在跟随这个跟踪节点所移动。但是如果我们退出该场景再重新打开,你会发现跟踪的效果消失了:follow对象设置的那个关键帧所设置的内容变成了None。

这件事为什么会发生,其实并不难理解。在一个animation clip里,我们操作的对象通常都涉及Animator所在的本节点,或者它的各个子节点的属性。如果我们要涉及到本节点外的其他节点,就要考虑这个clip中如何定位到这一个节点。通过相对路径?还是通过场景里的绝对路径?无论是哪种,要保证这个animation能对于一个场景树里的各个对象都能方便的使用,好像都不太合理。

于是,Unity所做的就是不提供这样的定位。它只在你第一次设置这个关键帧的时候把它的值设置一个临时的节点引用,在保存clip的时候,并不保存它的信息。

把Prefab当做单例使用

那么,要怎么实现我们想做的功能呢?我们只好提供一个能让animation clip定位到的节点。如果让这个节点成为当前节点的子节点,显然是不太合理的,因为这个节点在逻辑上是跟随玩家移动的。我们实际上需要一个“全局”的节点,能让一个animation clip不论在什么时候都能定位到它。这时候就想到了Prefab。

我们创建一个空节点,并且把它拖到资源文件夹里,让它成为一个Prefab。再在动画的关键帧里把跟随脚本的follow对象设置为这个prefab节点本身,并保存场景重新打开。

Success!这次关键帧设置的对象被成功保留了。

最后,我们只需要在必要的时刻更新这个prefab节点的位置就行了。Prefab节点的脚本不会运行,因此这个需要我们主动的在跟踪节点里进行。只需要简单的写一个脚本,然后让prefab节点的位置设置成当前位置即可。

这引出了一个Prefab节点的有趣用途:它不仅仅是一个节点的“蓝图”,也可以把它当做一个独立的节点来看待。由于它在Unity中是全局的资源(Asset),在任何地方都可以访问到,所以可以在动画系统、状态触发等情况下,使用这一技巧来进行数据、状态的间接传递。

总结

  1. Prefab可以当做GameObject来使用,只是不在场景树里显示
  2. 实例化(Instantiate)一个Prefab的时候,可以理解为只是对这个GameObject做了一个复制
  3. 可以把Prefab当做全局单例节点使用,来间接的进行属性的传递(它自身的属性也可在运行时改变)
  4. 在动画里要和外界的节点做交互,可以用到上述技巧。