跳转至

运动语法:从一幅画走到下一幅画的六种办法

一部影片先给出一样好东西,再给出另一样毫不相干的好东西,那么不管每一样的动画做得多好,看起来都只是幻灯片。下面这六种机制,都是让下一幅画从当前这幅画里面出来的办法。它们是用来组合的语法,绝不是预设:挑故事本来就在要求的那一种,再针对这部影片的题材和媒介把它做出来。

默认做法不变。 温和的过渡动作(晕开、翻转、推近)仍然是标准接缝(workflows/launch-video.md 的「运动(Motion)」一节)。下面每一种机制都是一项带条件的判断。如果它的「何时用」并不明确成立,就用温和的过渡动作。

六种机制都遵守三件事:

  • 动作开始之前,目的地已经就位。 下一个场景已经画好,垫在动作的下面或后面,所以两个场景之间永远不会出现空帧。
  • 有东西被带过去:一个形状、一个位置、一个运动方向。设计出来的接缝和盖在拼接处上的转场,区别就在这里。
  • 一部影片用一到两种机制,每种只用在要紧的接缝上。 30 秒里六种全上,那是一卷动作演示片,不是故事。

1. 前景即转场

场景里本来就有的某样东西(一个词、一个记号、一个形状)不断变大或扫过,直到盖满画面;等它退开,下一个场景已经在那里了。

  • 何时用:盖住画面的那样东西是故事的一部分(品牌的墨、产品的标志、主张本身),而两个场景之间没有别的东西可以带过去。
  • 何时不用:遮盖物会是一个随手拿来、只为藏住拼接处的形状;或者它会出现在每一个接缝上——到第三次,它看起来就是一个划变预设。
  • 我们的实例:发布片里的文字帧(launch3.ts,launchCut.ts 里的 typeFrame)。墨从一个点在画面上晕开,词语在墨晕里一笔一笔把自己写到纸面上,然后墨收拢成一个点,落在下一幅画上。墨晕把它展开时覆盖的那一帧内容、和它收拢时落到的那一帧内容,都当作数据接收(type(lines, len, before, after)),所以下一个场景总是等在它底下。

2. 常驻角色

一个物体在整部影片里保持自己的身份,场景在它周围变换。

  • 何时用:故事本来就有这样一个物体(一个光标、产品的圆点、一个角色、一条正在画的线),而且它在它走过的每一个场景里都有出现的理由。
  • 何时不用:它得为了这个目的专门发明出来,或者被拖着穿过一些它什么也不做的场景:一个只在旁边看着的吉祥物就是装饰。
  • 我们的实例:发布模板里的墨滴(launchTemplate.ts)。每按一次 Generate,就有一滴墨落进对话串,在那里晕开成回答卡片;到结尾,它最后一次从 Generate 上升起,落下成为片尾卡标志上的那个点,片尾卡就从它展开。motif.ts 用规则管住它:接缝两侧的屏幕位置和大小一致,误差在 2 px 以内,否则 checkRelay 抛错。

  • 通用做法:engine/src/canvas-core/morph.ts。handoff({ from, to, t0, t1 }) 把一条轮廓从源形状带到目标形状:它一边移动,一边在运动中略微拉长,并在行程的后半段变成目标形状,精确地落在目标上。coverShape 把一个形状放大到整页那么大。物体的颜色和边缘在接缝两侧保持一致,并用 checkShapes(2 px)守住接缝。morphDemo.ts:一个句号变成一张卡片,卡片又变成整页。

3. 从选中到展开

一组东西里的某一项被挑出来,不断变大,直到它就是下一个场景。

  • 何时用:故事从多走向一:一整面选项墙,然后是正在用的那一个;一个列表,然后是被打开的那一项。观众看得出下一个场景是从哪里来的。
  • 何时不用:那一组东西摆出来只是为了被离开(搭这一组,是因为故事需要展示广度,而不是把它当跳板);或者那一项的小版本和全画面版本对不上:要把完整场景的第一帧构图得和它所长出来的那张卡片一模一样。
  • 我们的实例:画风墙(launchGallery.ts,用在 launch2.ts 里)。摄像机猛推进卡片又退出来;最后一次推进不再退出,卡片填满画面,变成场景。锦鲤从一张静帧出发也是这么做:koiWorld.ts 把它的第一帧构图得和锦鲤样张一模一样,于是卡片无缝地交接给那个游动的世界。

4. 对位拆解

一个整体保持可辨认,同时它的各个部件移动、归成若干组,每个部件从原来的位置到落点都能追踪。

  • 何时用:要点在于产品对观众刚刚看到的东西做了整理、提取或重组:拥挤的一周变成一份计划,一份文档变成一个个字段。
  • 何时不用:部件跟不住的时候。同时移动的超过大约七个,或者部件的路径彼此交叉,观众就丢了地图;把它们错开,或者成组移动而不是逐项移动。
  • 我们的实例:productUI.ts(发布模板里的一条 kind: "ui" 请求)。界面由数据画出两个状态。按一下产品的操作,每一项都按 id 匹配,从它在前一状态里的位置弹到后一状态里的位置:行移到新的槽位,数值滚动到新的数字,新项进场,旧项离场。

5. 连续画布

所有场景都放在一张比画面大的布局里,摄像机在它们之间移动,而不是切换。

  • 何时用:这些地方在空间上真的有关系(一页里的几个区块、一栋楼里的几个房间、一条路上的几个步骤),而它们之间的路线正好告诉观众是什么关系。
  • 何时不用:这些地方互不相关(这时移动会凭空编出一种并不存在的关系);或者两站之间的路程又长又空:每一秒都必须有变化(craft-bar.md,不要冷场)。所有平面共用一台摄像机(camera.md)。
  • 我们的实例:webTour.ts。一张长页面上的三个区块,一个看得见的光标,以及一台先展示整页、再在每一次点击和每一个字段上推近的摄像机。koiWorld.ts 是手绘版:一个比画面大得多的池塘,景物放在世界里,所以随着鱼游动,景物也在变化。

6. 爆炸分层

一个物体沿着纵深分开成它的各个层,停住让每一层都能被看清,然后再合拢回这个物体。

  • 何时用:里面是什么正是要点:一样东西是怎么造出来的,一个堆栈由什么组成。这些层必须是题材里真实的部件,按真实的顺序排列。
  • 何时不用:题材本身没有真正的层,层是为了效果编出来的;或者各部分分开的时间不够长,来不及看清。如果它再也不合拢,那它就是一张图解,而这也许正是这部影片想要的;要决定是哪一种。
  • 我们的实例,只有一个方向:paperCraft.ts,玩具剧场。场景从后往前、一片布景接一片布景地搭起来,布景片之间留着真实的间隙:每一片在移动时都被托在这一摞的上方,影子又宽又远,落下时影子收拢。这是合拢的那一半。我们还没有哪部影片是从完整状态开始再分开的;在有一部做到之前,把分开的那一半当作未经验证。

三个现成的接缝

对于一次普通的换场、但仍想带一点手作笔触的情况,engine/src/canvas-core/transitions.ts 提供三个调用。每个都接收两个场景的绘制函数和一个进度,适用任意画面尺寸,进来的场景已经在底下画完整。transitionsDemo.ts 把每一种各演示一次。

  • brushWipe:一支蘸满颜料的笔刷横拖过去;带 ink 时,新画面前方有一条湿墨带(不带时是一道干净的刷毛边缘)。用于手绘调子(drawn register),方向顺着故事本来就在走的方向。不用于简洁调子(clean register),一部影片里也不超过一两次。
  • iris:从一个点圆形张开,或者收拢到一个点。从下一个场景讲的那样东西上张开,在这一个场景讲的那样东西上收拢;不以任何东西为中心,它就只是一个形状。墨用 shape: "blot",简洁调子用普通圆形。
  • flashCut:在一阵短促的闪光下硬切,所以硬切的条件同样适用(一个强拍,出现在能量要求它的作品里)。绝不只有一帧,默认绝不全白,绝不快速重复:把一部影片的闪光声明给 checkFlashes,任意两次相隔不到半秒它就抛错。

带着运动的切

两个都在运动的镜头之间的硬切,可以是最顺滑的接缝,也可以是最粗糙的。区别在于算术,engine/src/canvas-core/seams.ts 负责算。

  • 两边都静止,或两边同速。 从运动的镜头切到静止的镜头(或者反过来)看起来是一下顿挫。运动方向转弯、速度减半或加倍的切也一样。
  • 加速出去,在最快的那一帧之后切,到达并停稳。 出去的镜头在大约 0.2 到 0.4 秒内加速;进来的镜头以同样的速度、朝同样的屏幕方向开场,在 0.8 到 1.4 秒内停稳。出场是短的那一边。matchedMove({ dir, exitDist, exitF, entryF }) 给出两半,分别加到各个场景的摄像机取景上;进场距离由出场速度算出,所以两边总是一致。切点那一帧归进来的镜头:出去的镜头留在屏幕上的最后一帧是切点的前一帧,而那一帧就是它最快的一帧。
  • 穿过切点,在纵深方向上。 matchedZoom({ push, exitF, entryF }) 对一台飞进一个镜头、又在下一个镜头里飞出来的摄像机做同样的事。缩放是一个比率,所以两边都在对数空间里运行,跨过切点时每帧以相同的速率增长。用默认的推进量,进来的镜头开场时大约是它尺寸的三分之二,所以它的场景必须画得比画面宽出那么多;会让它在 0.4 以下开场的缩放会被拒绝。
  • 速度有上限。 超过大约每秒两个半画面宽(speedCeiling(W, fps)),模糊就不再读作运动,而是显出一道带阶梯状重影的拖痕。需要比这更快的移动,就该缩短行程、把其余部分藏到切点后面,或者在拍点上直接硬切。快速接缝用 --blur auto 渲染。
  • 所有东西都对上,并且只出现一次。 跨过切点的任何东西都保持它的位置、大小、颜色和边缘;在切点那一帧突变的属性会跳一下。静止姿势只展示一次:如果一个镜头停稳在某个姿势上、下一个镜头又以这个姿势开场,那么第一个镜头的最后一帧是这个动作的最后一张中间帧,绝不是姿势本身,否则影片会停住、定格、再走。
  • 绝不要让同一样东西的两个副本互相叠化。 到中点时两者都只出现一半,眼睛看到的是重影。移动一个物体;在它行程的中途改变它是什么。

checkSeam(velocity before, velocity after, { W, fps }) 返回一次切哪里不对,或者什么都不返回:一边静止、转向超过 30 度、速度骤降或骤升。checkSeamPlan 读取影片的接缝清单,标出被第三次使用的设计接缝;普通切和匹配切不受此限,影片唯一的招牌动作可以用三次(开头、中段、结尾)。两个场景都有空间朝同一方向移动时用这一套。没有空间时,让两边都停稳,在拍点上切。

发一手牌

任由自己挑,每部影片都会伸手去拿同样那三个动作。node tools/deck.mjs deal --kind story --energy high --seconds 27 从 references/motion-deck.json 发出一手牌:一个开场、一个收尾,以及一组接缝、运镜、揭示和表演提示,每张都带着它的数值、它的引擎调用,以及何时不该用它。发牌会向适合这部影片的牌加权,并且可以用打印出来的种子复现;--avoid 让下一部影片避开上一部用过的牌。

一张牌是一个提示,绝不是命令。留下它,针对这部影片的题材和媒介改编它,或者丢掉它,并在 brief 里写明是哪一种。本页开头的默认做法依然成立:「何时不用」成立的牌就丢掉,由温和的过渡动作顶上。

标注与标签交接

engine/src/canvas-core/callout.ts 给画面里的一样东西命名:一个点落在它上面,一条引线被画出来,末端写上一个标签。摄像机移动时,点跟着那样东西走(用 cam.toScreen 钉住,见 camera.md);标签保持不动,好让人读。它是叠在画面上的文字,所以那一套判断完全适用(workflows/launch-video.md 的「文字(Type)」一节):一块安静的区域,一个说出所指之物的标签,同一时间屏幕上只有一两个。画面繁杂的地方,给标签加一个 backing,或者干脆不用标注。

标签也可以充当跨过接缝的常驻角色。用 handoff 时,点和线收回,标签移动到下一个场景开始的位置,在那个场景的第一帧上落成它的标题;下一个场景从那里用 drawLabel 以同样的姿态把它画出来。checkHandoff 把两边约束在 2 px 以内,接缝一跳就抛错。calloutDemo.ts 把两种用法都演示了。

怎么选

先问这个接缝要告诉观众什么,再拿能说出这句话的那种机制:

接缝在说 机制
「现在是这个」,除了品牌之外没有共享任何东西 前景即转场
「还是那样东西,往后走了一步」 常驻角色
「那么多里面,就是这一个」 从选中到展开
「还是那些东西,排好了顺序」 对位拆解
「在那个旁边,就在这儿」 连续画布
「这就是里面的东西」 爆炸分层

如果这些都不是这个接缝要说的,那它就是一次普通的换场:用温和的过渡动作,或者在条件成立时用一次匹配的硬切。在整部影片完整渲染之前,把每一个设计接缝都当作一对前后静帧、再当作它们之间的一条帧序列来审一遍。