200字范文,内容丰富有趣,生活中的好帮手!
200字范文 > 跟着我左手学好前端 右手画条龙~

跟着我左手学好前端 右手画条龙~

时间:2022-12-04 21:36:25

相关推荐

跟着我左手学好前端 右手画条龙~

点击上方“程序员黑叔”,选择“置顶或者星标”

期待你的关注!

人的运动——走,跑,跳,是由骨骼带动躯干和四肢完成的。「骨骼动画」,顾名思义,就是模拟骨骼运动的机制而制作的动画。

什么是骨骼动画

人的运动——走,跑,跳,是由骨骼带动躯干和四肢完成的。「骨骼动画」,顾名思义,就是模拟骨骼运动的机制而制作的动画。比如下面这条奔跑的小龙。

用到的素材,额,其实是他大卸八块后的样子。

骨骼动画主要被用游戏场景中,做 Logo 、彩蛋也不错(比如 年双 11 的喵喵舞就是天猫的同学基于骨骼动画原理实现的)。其实,在 CSS transform 或 Canvas 的帮助下,Web 前端播放骨骼动画,可谓举手之劳矣。

组装骨骼

骨骼当然不是随便排列的,它们需要以树状结构组织起来。

光有树状的结构还是远远不够的,每片骨骼还需要描述自身的位移是多少(x,y),旋转的角度是多少(θ)。注意,骨骼的位移和角度是相对「父骨骼」(的坐标系)而言的。

举个例子,左臂的位移是(78,-40),角度是 -30°,表示左臂在躯干的基础上,沿 X 轴平移 78,沿 Y 轴平移 -40,然后旋转 -30°,才是左臂目前的位置。同样,左前臂的位移是(-45,100),角度是 55°,表示左前臂在左臂的基础上,沿 X 轴平移 -45,沿 Y 轴平移 100,然后旋转 55°,才是左前臂的位置。依此类推。

躯干-左臂-左前臂 骨骼系统

位移和角度统称「变换参数」。用相对于父骨骼(而非全局)的变换参数描述子骨骼本身,其好处在于。当右臂(父骨骼)运动起来(变换参数变化起来)的时候,右前臂(子骨骼)自身即使不动(变换参数不变),它实际上还能够随着右臂运动。

先不论如何使骨骼动起来,我们先研究下,如何把组装好的静态骨骼渲染/展示出来。这里分两种情况讨论:

DOM元素:

很容易想到,用固定尺寸且绝对定位的 div,配合 CSS transform 属性实现。比如:

<div class=“躯干” style=“transform:躯干的位移和角度; position: absolute; …”><div class=“左臂” style=“transform:左臂的位移和角度; position: absolute; …”><div class=“左前臂” style=“..”><div class=“左爪” style=“..”></div></div></div><!—其余部分—></div>

你看,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 方法会变换当前画笔坐标系,于是很容易想到这么做。

context.transform(躯干位置)context.save(); // 1context.transform(左臂位置)context.draw(左臂)context.transform(在左臂左前臂位置)context.draw(左前臂)context.transform(左爪位置)context.draw(左爪)context.restore() // 回到了 1 状态// 继续绘制其他部分

这样做也存在问题。实际上,这种方法是按照深度优先的原则,遍历了以最外层骨骼为根的骨骼树,绘制了树上的每个节点(骨骼)。这限制了我们绘制 Canvas 对象的顺序。

Canvas 2D 绘图没有 3D 绘图的「深度缓存」机制,所以在 2D 绘图中,对某一个像素而言,后绘制的内容一定会覆盖(至少影响)之前绘制的内容。但是,每片骨骼的 Z 值是定义好的,它们的前后关系可不能随便乱来,要使绘制的效果和定义的相同,就先得对骨骼进行深度排序,先画后面的,再画前面的。

深度缓存机制,可以这样理解:我「画」了,但由于这个像素之前已经画过,而且 z 值比我大,所以我硬是没「画」上去。

context.setTransform(躯干的绝对位置)context.draw(躯干)context.setTransform(左臂的绝对位置)context.draw(左臂)// 按照深度顺序进行绘制

这时,我们需要的位移和角度,便不再是相对于父骨骼的了,而是相对于最外层骨骼的。遇到的问题和使用 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 个单位时长之时),都需要根据左右最近的关键帧,内插出一个变换参数值,然后根据这个参数值,按照前一节所述的方法渲染或更新动画的状态。

这样,我们就能看到下面的情形了:

你们懂的,读者三部曲

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。