人的运动 -- 走, 跑, 跳, 是由骨骼带动躯干和四肢完成的.「骨骼动画」, 顾名思义, 就是模拟骨骼运动的机制而制作的动画. 比如下面这条奔跑的小龙.
用到的素材, 额, 其实是他大卸八块后的样子.
骨骼动画主要被用游戏场景中, 做 Logo , 彩蛋也不错(比如 2014 年双 11 的喵喵舞就是天猫的同学基于骨骼动画原理实现的). 其实, 在 CSS transform 或 Canvas 的帮助下, web 前端播放骨骼动画, 可谓举手之劳矣.
组装骨骼
骨骼当然不是随便排列的, 它们需要以树状结构组织起来.
光有树状的结构还是远远不够的, 每片骨骼还需要描述 自身的位移是多少 (x,y), 旋转的角度是多少(θ) . 注意, 骨骼的位移和角度是相对「父骨骼」(的坐标系) 而言的 .
举个例子, 左臂的位移是(78,-40), 角度是 -30°, 表示左臂在躯干的基础上, 沿 X 轴平移 78, 沿 Y 轴平移 -40, 然后旋转 -30°, 才是左臂目前的位置. 同样, 左前臂的位移是(-45,100), 角度是 55°, 表示左前臂在左臂的基础上, 沿 X 轴平移 -45, 沿 Y 轴平移 100, 然后旋转 55°, 才是左前臂的位置. 依此类推.
躯干 - 左臂 - 左前臂 骨骼系统
位移和角度统称「变换参数」. 用相对于父骨骼 (而非全局) 的变换参数描述子骨骼本身, 其好处在于. 当右臂 (父骨骼) 运动起来 (变换参数变化起来) 的时候, 右前臂 (子骨骼) 自身即使不动(变换参数不变), 它实际上还能够 随着 右臂运动. 如果身边没有一个能够指导你的技术大佬, 可以到 Web 前端学习裙, 里面有我整理的最新的学习路线, 学习教程以及一些学习手册和 PDF 文档, 学习过程中有什么问题可以随时在里面问, 我都会帮忙解答, 这样能够提升你的学习效率, 这是我的 Web 前端裙. 前面是 --282549184---
先不论如何使骨骼动起来, 我们先研究下, 如何把组装好的静态骨骼渲染 / 展示出来. 这里分两种情况讨论:
DOM 元素:
很容易想到, 用固定尺寸且绝对定位的 div, 配合 CSS transform 属性实现. 比如:
你看, DOM 元素被 CSS transform 作用时, 所有子元素随之被 transform 了, 和骨骼动画完美契合!
想法固然不错, 但是 , 请考虑一下这样一种情况:
骨骼 A:div A (z=1)
骨骼 B:div B (z=4:B 显示在 D 前面)
骨骼 C:div C (z=2:C 显示在 A 前面)
骨骼 D:div D (z=3)
我们知道, 在 position:absolute 时, DOM 元素会创建自己的 Stack Context. 也就是说, 只要 div A 的 z-index 值小于了 div C, 那么作为 A 的子元素 B, 即使 z-index 大过天, 也不可能覆盖 C 及其子元素.
所以, 我们只能将所有骨骼平铺下来:
骨骼 A (z=1)
骨骼 B (z=4)
骨骼 C (z=2)
骨骼 D (z=3)
这带来了新的问题: 我们所具有的 B 的变换参数, 是相对其父骨骼 A 的, 而在渲染时, 我们需要的是 B 直接相对于最外层骨骼的位移和角度.
Canvas:
对使用 Canvas 的情况, 我们知道 context.transform 方法会变换当前画笔坐标系, 于是很容易想到这么做.
这样做也存在问题. 实际上, 这种方法是按照深度优先的原则, 遍历了以最外层骨骼为根的骨骼树, 绘制了树上的每个节点(骨骼). 这限制了我们绘制 Canvas 对象的顺序.
Canvas 2D 绘图没有 3D 绘图的「深度缓存」机制, 所以在 2D 绘图中, 对某一个像素而言, 后绘制的内容一定会覆盖 (至少影响) 之前绘制的内容. 但是, 每片骨骼的 Z 值是定义好的, 它们的前后关系可不能随便乱来, 要使绘制的效果和定义的相同, 就先得对骨骼进行深度排序, 先画后面的, 再画前面的.
深度缓存机制, 可以这样理解: 我「画」了, 但由于这个像素之前已经画过, 而且 z 值比我大, 所以我硬是没「画」上去.
这时, 我们需要的位移和角度, 便不再是相对于父骨骼的了, 而是相对于最外层骨骼的. 遇到的问题和使用 DOM 时一模一样.
从相对到绝对
仍然以躯干 - 左臂 - 左前臂系统为例. 我们知道, 左臂 (相对躯干) 的变换参数是 (78,-40,-30°), 左前臂(相对左臂) 的变换参数是(-45,100,55°). 那么, 左前臂相对躯干的变换参数如何求算呢?
躯干也是有变换参数的, 它的「父骨骼」是 Root -- 一个「隐形」,「固定」的最外层骨骼, 所以实际上我们需要求算相对 Root 的变换参数. 不过原理是完全一致的. 也许你会猜: 左前臂相对躯干的变换参数是(78-45=33,-40+100=60,-30°+55°=25°). 这是错误的.
实际上, 位移和旋转会相互影响, 不能直接加和. 这里. 我们需要使用「变换矩阵」来求算(还记得 transform: matrix(...) 的形式吗). 对于不了解图形学基础的同学, 这也许有点难, 不过没关系, 我们只需要知道, 通过一个 3x3 的矩阵, 有办法算出左前臂相对躯干的变换参数.
算出来的结果是 (89,69,25°).
这样, 我们就能在不依赖左臂的情况下, 把左前臂的位置确定下来了. 所有的骨骼都按照这种方式处理, 万事大吉了!
动起来
如何让骨骼动起来
骨骼拼装好的对象, 当然是要动起来的. 而骨骼动画最重要的特征, 就是「父骨骼」的运动带动了所有「后代骨骼」(手臂挥舞的时候, 巴掌当然要跟着动咯), 所以骨骼动画才比较精致.
一个完整的骨骼动画的动作 , 可以分解到每片骨骼上. 而每一片骨骼的动作, 则由关键帧所定义. 举个例子, 一个跑步的动作, 分解到各个骨骼上, 无非包括:
躯干: 上下起伏.
腿部: 抬起和落下.
手臂: 前后摆动.
手前臂: 肘部的弯曲变化.
如果还想做细腻一些, 自然还有头部的摆动, 毛发颤动, 眼珠转动等等...... 而每一片骨骼的变化, 只需要三五个关键帧. 比如, 示例骨骼动画 (小龙跑步) 的关键帧定义如下图所示. 每个关键帧包含的信息包括: 关键帧在动画周期中的位置, 以及骨骼的变换参数.
关键帧
接下来, 还有什么好说的呢? 动画最基本的原理, 就是随着时间更新对象的视觉状态. 那么, 对骨骼动画而言, 就是在每一帧的时候, 根据当前帧在一个动画周期内的位置, 线性内插出每一片骨骼的位移和角度, 然后算出全局的位移和角度, 再画出来.
内插, 就是根据离散的值求取中间值的过程. 最简单是线性内插, 中间值仅与左右两个离散值, 和中间值的位置有关. 比如, 当中间值位置为 0.5, 左右两侧的值为 1 和 2, 那么内插值就是 1.5, 即 f(1, 2, 0.5) = 1.5; 同理, f(1, 2, 0.7) = 1.7,f(1, 2, 0.3) = 1.3.
如上图所示, 一个动画周期的长度是 6 个单位(这里是 DragonBones 定义的长度单位, 默认是 1/24 秒, 实际播放时可以随意改变), 那么在任意时刻(比如播放到 1 个单位时长之时), 都需要根据左右最近的关键帧, 内插出一个变换参数值, 然后根据这个参数值, 按照前一节所述的方法渲染或更新动画的状态.
这样, 我们就能看到下面的情形了:
来源: http://www.jianshu.com/p/557aeaf9f382