author:山鬼 有点烂,因此先看掘金的吧javascript
5000字,带你了解动画与交互的基本实现 不少内容写的比较粗略,因此还望你们不要太过吐槽,后续我会给完善的。css
当图形被绘制在屏幕上的时候,不管是2D仍是3D,都会有其本身的空间,也会有其本身的转换数据。java
通常w的默认值为1,较为基本的旋转,平移,缩放多采用的是4维矩阵,当咱们须要一些复杂的操做时,还能够经过矩阵得到复合矩阵。git
不管是css仍是canvas等图形的转换操做,采用的操做都是相同的。github
平移web
旋转xcanvas
旋转yapi
旋转zpromise
放缩ide
也许在看3D的图形转换的时候,会感受好复杂,可是当咱们去看2D的时候,砍去了一个维度,公式也就固定了。
进一步简化
这个时候,咱们会获得一段较为常见的旋转代码
/** 向量定义 var Vector2={ x:0, y:0 } **/
function rotate(site,angle=0){
var _angle=angle/180*Math.PI;//将弧度转换为角度
//进行计算
var x1=site.x*Math.cos(_angle)-site.y*Math.sin(_angle);
var y1=site.x*Math.sin(_angle)+site.y*Math.cos(_angle);
//返回新的向量
return {
x:x1,
y:y1
}
};
复制代码
局限性: 矩阵的数据转换由于数据格式化,因此并不适用于如非线性动画的转换。
一周是360度,也是2π弧度。弧度是这样定义的,一个角对应的弧长与半径的比值就是弧度。半径为1的圆周长是2π,因此360度=2π弧度,之后的类推就好了。几个重要的角度还有:30度=π/6弧度,60度=π/3弧度,90度=π/2弧度,180度=π弧度等。
在空间之中,能够被划分为空间坐标与对象坐标 用CSS来表示的话,空间坐标有些相似position:absolute
以整个视图的原点为基准。而对象坐标的说法更贴切的应该是相对坐标,相似position:relative
为了方便对于坐标进行计算以及数据转换,空间中的任何点信息均可以使用向量来做为信息载体。
Example: (1,1)能够表示为空间中x=1,y=1的坐标点,也能够表示为从(0,0)到(1,1)的距离。 从新定义一个Vector2的类
function Vector2(x=0,y=0){
if(!(this instanceof Vector2)){
return new Vector2(x,y);
}
this.x=x;
this.y=y;
}
Vector2.prototype = {
copy: function() {//返回新的向量
return new Vector2(this.x, this.y); },
length: function() {//当前向量的长度
return Math.sqrt(this.x * this.x + this.y * this.y); },
normalize: function() {//单位向量
var inv = 1 / this.length();
return new Vector2(this.x * inv, this.y * inv); },
negate: function() {//反向向量
return new Vector2(-this.x, -this.y); },
add: function(v) {//向量和
return new Vector2(this.x + v.x, this.y + v.y); },
subtract: function(v) {//向量差
return new Vector2(this.x - v.x, this.y - v.y); },
multiply: function(f) {//向量积
return new Vector2(this.x * f, this.y * f); },
divide: function(f) { //向量方向化
var invf = 1 / f;
return new Vector2(this.x * invf, this.y * invf); },
dot: function(v) {//点积
return this.x * v.x + this.y * v.y; },
move:function(v){
this.x=v.x;
this.y=v.y;
return this;
},
prependicular:function() {//法向量
return new Vector(this.y, -this.x);
},
rotate:function(angle=0){
var _angle=angle/180*Math.PI;
this.x1=this.x*Math.cos(_angle)-this.y*Math.sin(_angle);
this.y1=this.x*Math.sin(_angle)+this.y*Math.cos(_angle);
},
};
复制代码
向量的运用:速度(v),力(f),方向(d),颜色(rgb)等...
当咱们把信息使用向量存储值后,就会发现不少功能都是清晰明了,好比属性的插值运算
角度的计算,在计算机动画实现中,有定角表达 欧拉角表达 轴角表达这三种说法,不过这些都不须要去了解,由于在插值计算的过程当中,这些技术并不合适,若是想深刻了解缘由的,能够去了解一下什么是万向节死锁(gimbal lock )。
欧拉角是表达旋转最简单的一种方式,表达了物体绕坐标系的轴的旋转角度,2D平面内提供了大量的旋转api ,css里的transform:rotate(90deg)
,canvas里的ctx.rotate(angle)
,对于3D方面,css也是提供了在各个轴向上的Rotate,canvas则更可能是在webgl中使用的矩阵变换。
对于欧拉角的定义,有人归纳了一下几点。
X-Y-Z== rotateX()-rotateY()-rotateZ()
在欧拉角中,咱们能够发现,在轴转向的时候,会有一个顺序,若是当角度不恰当,会致使轴旋转的过程当中,有两个轴会发生重合,致使维度下降。
固然,咱们也可使用代码来对万向节死锁进行复现。
Point.Rotate(new Vector3(0, 0, 10));
Point.Rotate(new Vector3(0, 90, 0));
Point.Rotate(new Vector3(20, 0, 0));
复制代码
只须要固定住某一个轴的转角为90°,不管怎么去调整其余的轴,都会发现,他们只会在平面上运动。
咱们所要了解的是 四元数,这个词的概念在游戏开发中很常见。那么选择四元数来处理自由度旋转的优点在哪里呢。 优点
弱点
在了解四元数以前,咱们要了解一个知识点复数,若是已有基础,能够跳过。
定义: 任意一个复数 z ∈ C 均可以表示为 z = a +bi的形式,其中 a, b ∈ R 并且 .咱们将 a 称之为这个复数的实部(Real Part),b称之为这个复数的虚部(Imaginary Part). 若是将复数使用坐标系来表示。
四元数是一个恐怖的东西,由于当把他放在图形中去理解,你会发现比矩阵的还要难理解不少,在正常的坐标系中,每一个轴都会是一个直线,而在四元数中,多出一个轴向,并且这个轴会垂直于任何一个轴,相对于复数的二维空间,四元数则是三维的复数形式,是一种高阶复数,感受像就是四维空间。
四元数的数学表达仍是比较好理解的,Q是一个四元数,w是一个实部,x,y,z则是虚部,且
。
当四元数应用到旋转中的时候,咱们一般能够这么表示一个,w是实数,v是向量,每一次的旋转都会须要两个四元数来配合,四元数的的范围在[-1,1]之间。
接下来咱们试着实现一个四元数
/* 四元数 */
class Quaternion{
constructor(x=0,y=0,z=0,w=0){
this.x=x;
this.y=y;
this.z=z;
this.w=w;
}
fromAxisVector(axisVector,angle){// 由 旋转轴向量,旋转角 获得
var t = sin(0.5*angle);
this.w = cos(0.5*angle);
this.x = axisVector.x * t;
this.y = axisVector.y * t;
this.z = axisVector.z * t;
}
add(q){
this.w += q.w;
this.x += q.x;
this.y += q.y;
this.z += q.z;
}
subtract(q){
this.w -= q.w;
this.x -= q.x;
this.y -= q.y;
this.z -= q.z;
}
multiply(q){
var {x,y,z,w}=q;
this.w = w*q.w - x*q.x - y*q.y - z*q.z;
this.x = w*q.x + x*q.w + y*q.z - z*q.y;
this.y = w*q.y + y*q.w + z*q.x - x*q.z;
this.z = w*q.z + z*q.w + x*q.y - y*q.x;
}
normalize(){
var {x,y,z,w}=this;
var magnitude = Math.sqrt(x*x + y*y + z*z + w*w);
if (magnitude != 0)
{
x /= magnitude;
y /= magnitude;
z /= magnitude;
w /= magnitude;
}
}
convertToMatrix4(){//转换为矩阵
// 四元数与矩阵的转换
// [ 1-2y2-2z2 , 2xy-2wz , 2xz+2wy ]
// [ 2xy+2wz , 1-2x2-2z2 , 2yz-2wx ]
// [ 2xz-2wy , 2yz+2wx , 1-2x2-2y2 ]
var {x,y,z,w}=this;
var xx = x*x; var xy = x*y;
var xz = x*z; var xw = x*w;
var yy = y*y; var yz = y*z;
var yw = y*w; var zz = z*z; var zw = z*w;
return Matrix4( 1-2*(yy+zz), 2*(xy-zw), 2*(xz+yw), 0,
2*(xy+zw), 1-2*(xx+zz), 2*(yz-xw), 0,
2*(xz-yw), 2*(yz+xw), 1-2*(xx+yy), 0,
0, 0, 0, 1 );
}
}
复制代码
插值运动是指经过一些离散的数据进行数据的拟合,从而推断出新的未知数据点,使用简单函数来模拟复杂函数,从而提高数据的精度。
插值计算在运动之中,最多见的就是属性插值,如颜色渐变,宽高过分,缓动动画等,主要是经过计算机自行去计算,实现自动补帧。Flash中的补间动画采用的就是插值补间补帧。
假设给定n个离散数据,定义了其坐标为 在区间
上有函数g(x), 能够知足
,那么g(x)则能够被称为是f(x)在的
上插值函数,这也就是使用简单函数来模拟复杂函数。
属性 | 插值类型 | 效果 |
---|---|---|
color/alpha | 线性 | (颜色/透明度)渐变过分 |
加速度 | 线性 | 匀变速 |
欧拉角 | 线性 | 旋转 |
速度 | 非线性 | 变加速 |
线性插是一种很常见的插值方法,在动画计算中很常见,能够用来实现自动补帧,其基本的实现也较为简单。
线性插值通常是采用两点数据进行计算,最多见的就是直线插值,tween.js的Linear就是线性插值的一个实例。
/* * t: current time(当前时间); * b: beginning value(初始值); * c: change in value(变化量); * d: duration(持续时间)。 */
Linear: function(t, b, c, d) {
return c * t / d + b;
}
复制代码
多项式插值是线性插值的一个延伸,在线性插值的原公式上,支持了高阶多项式计算。
Quad: {
easeIn: function(t, b, c, d) {
return c * (t /= d) * t + b;
},
easeOut: function(t, b, c, d) {
return -c *(t /= d)*(t-2) + b;
},
easeInOut: function(t, b, c, d) {
if ((t /= d / 2) < 1) return c / 2 * t * t + b;
return -c / 2 * ((--t) * (t-2) - 1) + b;
}
}
复制代码
这是Tween.js
中的二次方插值,同时,还包含了三次方插值,甚至五次方插值。
三角插值这里指的就是三角函数COS TAN SIN,以x轴与y轴造成关系.如:
有了以前的基础知识与插值的基础,就有了足够的而基础去进行动画的尝试。
因而咱们能够从一个点开始构建
class Point{
constructor(x,y){
this.pos=new Vector2(x,y);
}
draw(){
//图形绘制
}
updata(){
//逻辑处理,数据更新
}
}
复制代码
这里的点已经具备了Vector2
的方法,从而使得这个点在二维空间中具备了必定的能力,包括平移,旋转。
以前有说,几乎全部的属性均可以使用向量做为载体,因而这里,可使用Vector2
给Point
赋予不少的属性,即可以获得
class Point{
constructor(x,y){
this.pos=new Vector2(x,y);
this.f=new Vector(0,0);
this.m=10;
this.a=this.f.length()/this.m;
}
}
复制代码
很简单的一个 公式,就给
Point
赋予了接受外界力的能力,以及运动的能力。
这几个公式是力与运动学之中最经常使用也是最关键的几个公式,也是运动学中很关键的一步,那么如何正确的去计算一个物体的运动状态呢。
Point
进行updata
这样,就能够将基本运动的动画利用物理公式从而实现,如匀加速,变加速,圆周运动等。
状态机在游戏开发中是一个很常见的词汇,那么状态机的存在是为了什么,在哪些地方有运用呢,
首先以Point
为基础,添加一个状态量
const PEDDING='PEDDING';//静止状态
const MOVING ='MOVING';//运动状态
const SHOW ='SHOW';//显示
const OUT ='OUT';//屏幕以外
//状态判断
if(Point.status=='PEDDING'){
cb();
}
复制代码
这么看起来是否是有些熟悉,对比发现,promise
其实也是一个状态机,不断判断当前的执行状态,来肯定什么时候进行下一个事件的执行,对比着promise
的链式调用,也就能够轻易的去明白一些动画库中的链式调用原理。
在视图中进行动画的物体,总会有一部分会消失在视图以外,为了下降了内存占有,也许能够直接使obj=nul
,可是当咱们仍须要其后续的出现,再去使用申请一个新的对象?显然有不少不合理的地方,因而便有了资源管理器。
var p1=new Point(0,0);
var p2=new Point(1,1)
var resource=[p1,p2];
//状态判断
resource.forEach(p=>{
if(p.status=='SHOW'){
p.updata();
p.draw();
}
if(p.status=='OUT'){
//对p进行移除或者重置设置
}
})
复制代码
这样的优点是能够下降大量的计算以及渲染工做,若是打算完全移除某个物体,则可使用Array.splice
用户交互也是很经常使用的一个状态机,以canvas为例,用户的事件监听是针对canvas总体的,若是咱们想实现一个拖拽的功能。
状态分析:
状态机的存在是以鼠标事件为本体。
实现了物体基本的运动与交互,那么接下来须要实现的就是物体与物体的交互,如今在咱们所了解到的碰撞检测方法。
这两个也是最为简单计算,也是最适合作粗计算阶段的碰撞检测,能够将一些没必要要进行进行精密计算的物体图形排除在外,减小计算量
以物体中心为基础,生成最小的包围矩形
rectB.x > rectA.x - rectB.width &&
rectB.x < rectA.x + rectA.width + rectB.width &&
rectB.y > rectA.y - rectB.height &&
rectB.y < rectA.y + rectA.height + rectB.height
复制代码
以物体为基础,生成最小的包围球形
Math.sqrt(Math.pow(circleA.x - circleB.x, 2) + Math.pow(circleA.y - circleB.y, 2)) < circleA.radius + circleB.radius
复制代码
分离轴也许听起来晕,甚至看网上的一些讲解也很晕,那么能够考虑在这个时候打开网易云音乐,点一首你最爱的歌,而后开始阅读。
分离轴,顾名思义是将轴分离开,那么在咱们所了解的领域中,最长出现的就是x轴与y轴,这也是坐标系的基础,那么轴的特色是什么,垂直,这也是分离轴的依据所在。
分离轴的实现有些像模拟灯光投影,当光线穿过两个空间中的物体,为了防止影子变形,设置一个垂直光线的挡板,想像一下,若是光线能够从两个物体中穿出,那么两个物体之间就不存在接触,那么投射的影子也就不会出现重叠,当足够多的光线进行穿透,若是出现垂直光线的挡板没有出现阴影重叠,那么咱们就能够认定这两个物体没有发生碰撞。
碰撞的检测,是只须要一组轴的检测未重合,那么能够断定为分离,若是全部轴的检测都重合,则物体发生碰撞
因而这里咱们就有了两个轴,光轴与投影轴。
因而咱们有了第一缕阳光
var Light=new Vector2(0,0);
复制代码
让阳光来穿过物体
var Point1 =new Point(0,0);
var Point2 =new Point(0,1);
var Light =Point1.pos.subtract(Point2.pos);//光线向量
var Panel =Light.prependicular(); //获取投影轴的向量
var axis =Panel.normalize(); //轴的单位向量,为投影点作准备
复制代码
求出咱们的投影点,这里所须要的公式
Light.dot(axis);
复制代码
获得了投影点后,一个物体在一个轴面上的投影点的最大值与最小值的差值,就是阴影面的范围。
像素检测的方法就是将每一个物体当前的像素位置都存储起来,再比较物体之间的像素是否有重复,可是计算量庞大。
栅格化的意思就是将屏幕划分为数个小块,对不一样区域内的物体进行单独处理,对于处于分界线上的物体,则能够进行屡次判断。最经常使用的栅格法就是四叉树
后续还有更精彩的如IK/FK动画,2.5D的效果实现等... 若是发现有哪些错误,欢迎指出。