AEJoy —— 表达式之使用标记触发动画【JS】

732 阅读4分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

效果图

2.gif

想法

为了角色动画的目的,想象一个预定义的关键帧动作库,只要我们在想要动画开始的地方放一个图层标记,我们就可以随时触发这些动作。那很酷,对吧? 因此,我们的目标是找到一种使用标记来触发这样的动画片段的好方法。 理想情况下,我们希望能够只放置一个图层标记,使用所需动作的名称作为标记注释,并让它触发所需的动画。例如,如果我们放置带有 “跳跃” 注释的图层标记,这将触发一个之前定义的名为 “跳跃” 的动作,并导致角色跳跃。

设计

有许多方法可以实现这一点,但最好的解决方案要求表达式能够检索层标记的注释字段。幸运的是,这种能力现存于 AE CS3 中。我们还需要使用通过注释字段访问标记的功能。例如,如果你有一个标记 "begin" ,你可以像这样得到这个标记的时间:

marker.key("begin").time

出于演示的目的,我们将着眼于动画化一些简单的面部表情特征,包括角色的眼睛、眼睑和眉毛。我们将在预合成中为每个身体部分定义不同的动画 “动作”(action)。每个动作都将在这些预合成打上关键帧,并带有相应的每个动画的开始的标记。我们将在主 “人脸”(face) 合成中使用这些预合成。我们将为每个预合成启动时间重映射,并应用一个时间重映射表达式,将每个层标记的时间转换为预合成中定义的相应动作的时间。

让我们总结一下。我们的动作将在预合成中打上关键帧。每个动作的开始都将用带有适当注释的标签进行标记。在我们的主合成中,每个预合成都将使用一个调整时间的表达式进行时间映射,以便主合成中应用的标记与预合成中相应的动作标记同步。

一旦我们像这样把东西连接起来,我们就可以简单地通过在主合成中放置图层标记来动画化我们的角色。

为了简单和一致性,我们将在每个预合成中创建一个名为 “action” 的层,预合成的命名标记将驻留在其中。我们还将设置一些东西,以便每个预合成在主合成中以层的形式保留它的名字(也就是说,它没有被重命名)。这两个设计决定将允许我们创建一个表达式,该表达式将适用于任何预合成而无需修改。复制粘贴即可。

还有一件事要处理。因为每个预合成都将连续定义多个动作,所以我们需要让表达式足够智能,以便当我们触发一个动作时,该动作的回放在进入下一个动作之前停止。

让我们看一下设置好后的几个屏幕截图。

image.png

你在上图中看到的是角色五官部分的合成,在这个例子中是眉毛。请注意,眉毛实际上由三个独立的部分组成: 左眉毛、右眉毛和皱眉(额头上的皱纹)。你会注意到顶层是一个 null ,我们将其命名为 “action” ,在这里我们放置了层标记来命名每个定义的动作。这些动作是通过对眉毛的位置和旋转属性,以及应用于皱眉的 Bezier Warp 效果的切线位置打上关键帧来创建的。 时刻 0 的关键帧定义了静止或 “标准”(norm) 位置。其他动作都有描述性的名称。例如 “norm>raise” 将眉毛从正常状态移到升高状态,而 “raise>norm” 则恰恰相反

需要注意的是,在这个合成中,动作之间的时间关系在很大程度上是不相关的。动作的时间将由放置在主合成中的标记来确定,让我们来看看主合成。

image.png

主合成包括五官部分的预合成。在每个五官部分的预合成层中,我们在希望动作发生的地方放置了标记(名称与预合成中定义的动作相匹配)。只要在适当的预合成层上放置一个标记,并给它一个与动作匹配的名称,我们就可以随时触发动作。

对于主合成中的每个五官部分预合成,时间重映射被打开,并且表达式被应用到时间重映射属性上。让我们来看看这个表达。

表达式

action = comp(name).layer("action"); ///< 预合成中的 Null 层

n = 0;
if (marker.numKeys > 0) { ///< 寻找当前时刻本层(往前)最近的 marker,并保存其索引 
    n = marker.nearestKey(time).index; 
    if (marker.key(n).time > time) {
        n--;
    }
}

if (n == 0) {
    0
} else {
    m = marker.key(n); ///< (往前)最近的 marker
    myComment = m.comment; ///< marker 的注释
    t = time - m.time; ///< 自该 marker 开始已经过了多久
    try {
        actMarker = action.marker.key(myComment); ///< 在预合成的 Null 层的 marker 中寻找(注释)匹配项
        
        /// @note 确保 t 不会超越到下一个 action 或者预合成的结尾
        if (action.marker.numKeys > actMarker.index) {
            ///< 俩标记之间的时间间隔
            tMax = action.marker.key(actMarker.index + 1).time - actMarker.time;
        } else {
            ///< 结束点和标记之间的时间间隔
            tMax = action.outPoint - actMarker.time;
        }
        t = Math.min(t, tMax);
        actMarker.time + t; ///< 设置重映射的时间
    } catch (err) {
        0
    }
}