效果图
一层链接一层,就像用一根松紧带连接起来一样
想法
那些使用 After Effects 足够长时间并记得 Motion Math 的人可能还记得一个非常酷的脚本 spring.mm 。这个脚本使用弹簧运动方程在两个层之间附加一个 “弹簧” 。当表达式出现时,大多数的 Motion Math 脚本都很容易转换为表达式,但是 spring.mm 神秘地消失了。
事实证明,在一些任务中,Motion Math 比表达式更擅长。这是因为在 Motion Math 的操作方式上有一个关键的区别。Motion Math 的优势在于可以一次性完成所有帧的计算。这意味着 Motion Math 能够保存从一帧到下一帧的信息,这对于这样的模拟非常重要。正如您现在可能已经发现的那样,表达式没有这样的方式将信息从一个帧传递到下一个帧。
所以我们在这里做的是考虑到表达式的限制,尽可能想出一些方法来复制 Motion Math 的 spring 脚本的功能,。
设计
经验丰富的表达式编写人员会开发许多技术,以避免表达式中缺乏持久数据所遇到的问题。当没有其他选项可用时,还有一种最后的方法,即使用蛮力方法,即设置表达式,以便在每一帧上重新创建在前一帧上发生的所有事情。这意味着,例如,在第 100 帧时,表达式必须运行 100 次计算(之前的每一帧运行一次,加上当前帧运行一次)。显然,如果计算很复杂,而且计算很长,这真的会让事情陷入困境。
然而,对于较短的序列,这种技术或许是可行的,也是我们将在这里使用的。所以这个想法是相当简单的 —— 我们将创建一个循环,表达式从第 0 帧开始,并为每一帧更新一次 spring 计算,直到它到达当前帧。我们将假设建立运动的图层被称为 “leader” ,我们将应用表达式到另一个图层的位置属性上。
这种逐帧技术还有一个不那么极端的版本,如果你正在做的事情只涉及到发生在前一帧的最近发生的某个事件,那么就可以使用它。这方面的一个例子便是当音频级别超过某个阈值时便会触发动画。在这种情况下,您将从当前帧向后循环,直到找到那个事件。
表达式代码
restLength = 20; ///< 从 leader 层余出的距离(像素)
damp = .95; ///< 定义了链接的弹性衰减因子
leader = thisComp.layer("leader");
fDur = thisComp.frameDuration;
currFrame = Math.round(time / fDur); ///< 当前帧数
p2 = position.valueAtTime(0); ///< 时刻 0 时本层的位置
v2 = 0;
for (f = 0; f <= currFrame; f++) { ///< 从第 0 帧开始,循环到当前帧数
/// @note 以下就是从 Motion Math 脚本借鉴的弹簧计算
t = f * fDur;
p1 = leader.transform.position.valueAtTime(t); ///< leader 层对应帧所处时刻的位置
delta = p2 - p1;
nDelta = normalize(delta); ///< 确定 leader 与本层位置的方向
a = 2 * nDelta * (length(delta) - restLength) * fDur;
v2 = (v2 - a) * damp;
p2 += v2; ///< 更新本层的位置
}
p2
注意,如果这两个层始终占据相同的位置,这个表达式将生成一个除零错误。当你第一次设置合成时,你可能会碰到这个问题,解决方法也很简单,只需要稍微移动其中一个图层并重新启用表达式。