骨骼动画定义
传统的动画,一般是对一个物体对象进行位移、旋转、缩放、变形,然后把关键帧的信息记录下来,在播放的时候按照关键帧时间对物体对象进行位移、旋转、缩放、变形,并在关键帧与关键帧之间做插值运算。
骨骼动画的特点是,需要做动画的物体对象本身不记录位移、旋转、缩放、变形信息,而是通过了第三方的“骨骼”物体记录动画信息,然后物体对象本身只记录受到骨骼物体影响的权重。在播放的时候,通过骨骼物体的关键帧和物体对象记录的权重,让动画重现
骨骼动画的好处
1、骨骼动画是影响到顶点级别的动画,而且可以多根骨骼根据权重影响同一个顶点,不论是2D或者3D使用,都可以让动画做得更丰富。
2、做到了对象和动画分离,我们只需要记录了物体对于骨骼的蒙皮权重,就可以单独的去制作骨骼的动画,在保证蒙皮信息和骨骼信息一致的情况下,还可以多个物体之间共享动画。
3、相对于2D的逐帧动画大大的节省资源容量。
4、3D角色动画离不开骨骼动画。
骨骼动画文件
spine骨骼动画导出时一般会包含三个文件,png,atlas,json
png是材质,atlas是创建整个初始骨骼物体对png的解析,即每一个蒙皮碎片在整个纹理图集上的位置,下边重点介绍一下 json文件内容:
keys:skeleton,描述骨骼的尺寸,fps,
keys:bones,描述所有骨骼的初始信息(位置,旋转,父节点,)
keys:slots,插槽,描述的是bone对应的的蒙皮名字
keys:skins,蒙皮,描述骨骼对应的蒙皮的一些状态(旋转,大小,位置,名字)
keys:animations,动画,描述的是动画的信息,每一段动画,都有若干帧,都对应这所有骨骼的变化,而这些变化就是(translate,rotate,shear),外界可通过这些骨骼的变化再依据权值来算出蒙皮的状态,最后呈现出来一帧帧动画
creator中使用骨骼
**Skeleton:**骨骼的渲染脚本
animation:外界可以通过它来修改要播放的动画名字
update函数:
_updateCache函数:
updateToFrame(idx);_frameCache成员变量:(AnimationCache):
spine.AnimationState.apply(),
时间轴.prototype.apply()
修改骨骼的状态
修改蒙皮的状态
**AnimationCache:**缓存一个动画里的所有帧
缓存模式有三种:SHARED_CACHE(共享缓存),PRIVATE_CACHE(私有缓存),REALTIME(实时计算)
**SkeletonCache:**骨骼的缓存
通过spine.SkeletonData,可以创建spine.Skeleton,spine.SkeletonClipping(), spine.AnimationState,其实缓存的就是这三个数据
**spine.SkeletonJson和spine.SkeletonBinary:**主要是来解析骨骼动画数据的,骨骼动画文件在导出时一般有两种格式,二进制(skel)或者json,最后会生成第二级骨骼动画数(spine.SkeletonData),注意,此处是生成spine.SkeletonData所有成员的数据,具体可以查看这两个类的readSkeletonData的函数实现
**第一级骨骼动画数据SkeletonData:**这个是继承自cc.Asset,他是我们加载骨骼动画源文件所产生的一个解析文件,主要是解析atlas和skel(二进制,也可以导出为json)这两个源文件
**第二级骨骼动画数据是spine.SkeletonData:**解析第一级骨骼动画数据,生成新的data,主要包含骨骼(bones),插槽(slots),蒙皮(skins),事件(events),约束,animations(动画数据),fps这些数据
**spine.Skeleton:**自身也是一个骨骼,基础数据(位置,缩放,时间),管理所有的骨骼(Bone),约束条件(ikConstraints,transformConstraints,pathConstraints),插槽(slots),蒙皮(skin)等以及他们之间的关系
**spine.PathConstraint:**路径约束
**spine.PathConstraintData:**路径约束的data
**spine.IkConstraint:**反向动力学约束
**spine.IkConstraintData:**反向动力学约束的data
**spine.ConstraintData:**约束的基类data
**spine.Bone:**单个骨骼
**spine.BoneData:**单个骨骼的数据
**spine.Animation:**游戏中的动画,持有动画名,动画的时间轴,动画的时间
**spine.AnimationState:**动画的状态,每一个骨骼动画都有一个动画状态
它的apply函数直接
**spine.AnimationStateData:**动画的状态数据
**spine.TrackEntry:**持有当前要播放的动画数据animation,当前动画是否loop,当前动画的总时间
时间轴的类型有15种,修改骨骼bone的状态
**spine.CurveTimeline:**时间轴基类
**spine.RotateTimeline:**旋转
**spine.TranslateTimeline:**平移
**spine.ScaleTimeline:**缩放
**spine.ShearTimeline:**曲线
**spine.ColorTimeline:**颜色
spine.TwoColorTimeline:
spine.AttachmentTimeline:
spine.DeformTimeline:
**spine.EventTimeline:**事件
spine.DrawOrderTimeline:
spine.IkConstraintTimeline:
spine.TransformConstraintTimeline:
spine.PathConstraintMixTimeline:
spine.PathConstraintSpacingTimeline:
spine.PathConstraintPositionTimeline:
一个骨骼动画通常会包含两个文件(纹理信息png和动画数据skel)
当把一个骨骼动画导入内存的时候,
一个动画文件包含若干组动画,每一组动画都有一个名字来标记,这一组动画则由一个帧数组frames[N]组成,默认动画播放的第一帧为frames[0],当游戏设置完动画名字以后,就开始更新动画,按照一帧一帧的去frames中取动画数据来播放
creator中sprite
一个sprite渲染组件,会包含两个特殊的成员变量,一个spriteFrame,一个是material
spriteFrame:指的是精灵帧,它主要管理的是uv坐标,顶点位置,矩形区域
//ccspriteFrame.js
//通过纹理的名字来加载纹理信息
_loadTexture: function () {
if (this._textureFilename) {
let texture = textureUtil.loadImage(this._textureFilename);
this._refreshTexture(texture);
}
}
//texture-util.js
//加载纹理信息
loadImage (url, cb, target) {
cc.assertID(url, 3103);
var tex = cc.loader.getRes(url);
if (tex) {
if (tex.loaded) {
cb && cb.call(target, null, tex);
return tex;
}
else
{
tex.once("load", function(){
cb && cb.call(target, null, tex);
}, target);
return tex;
}
}
else {
//../assets/CCTexture2D
tex = new Texture2D();
tex.url = url;
cc.loader.load({url: url, texture: tex}, function (err, texture) {
if (err) {
return cb && cb.call(target, err || new Error('Unknown error'));
}
texture.handleLoadedTexture();
cb && cb.call(target, null, texture);
});
return tex;
}
}
//loader.js
//cc.loader.load调用的时候会进到下面这个函数中
function loadImage (item) {
var loadByDeserializedAsset = (item._owner instanceof cc.Asset);
if (loadByDeserializedAsset) {
// already has cc.Asset
return null;
}
var image = item.content;
if (cc.sys.platform !== cc.sys.FB_PLAYABLE_ADS && !(image instanceof Image)) {
return new Error('Image Loader: Input item doesn\'t contain Image content');
}
// load cc.Texture2D
var tex = item.texture || new Texture2D();
tex._uuid = item.uuid;
tex.url = item.url;
tex._setRawAsset(item.rawUrl, false);
tex._nativeAsset = image;
return tex;
}
//
material:指的是材质,它会和shader关联,将shader中的一些属性暴露出来,比如我们可以通过材质给shader中的属性赋值,比如texture
spriteFrame内部持有有一个CC.Texture2D,这个成员变量主要是解析纹理信息的
CC.Texture2D内部持有一个renderer.Texture2D,这个成员变量主要是和GPU打交道的
//renderer.Texture2D.js
constructor(device, options) {
super(device);
let gl = this._device._gl;
//目标缓冲
this._target = gl.TEXTURE_2D;
//首先会在内部创建glID这个是纹理在GPU中的标识
this._glID = gl.createTexture();
// always alloc texture in GPU when we create it.
//创建时总是在gpu中分配纹理
options.images = options.images || [null];
//此处主要是更新数据到GPU纹理缓存
this.update(options);
}
在shader中经常会使用texImage2D(texture,v.uv0),这里的纹理texture从哪里加载来的呢
//当外界加载一张纹理成功以后,返回的数据,我们可以把它赋给sprite.spriteframe,这个时候就会调用下面的函数
//ccsprite.js
_applySpriteFrame (oldFrame) {
let oldTexture = oldFrame && oldFrame.getTexture();
if (oldTexture && !oldTexture.loaded) {
oldFrame.off('load', this._applySpriteSize, this);
}
if (this._spriteFrame && !this._spriteFrame.isValid) {
cc.log('sprite frame is not valid', this.node._name)
this._spriteFrame = null;
}
let spriteFrame = this._spriteFrame;
if (spriteFrame) {
this._updateMaterial();
let newTexture = spriteFrame.getTexture();
if (newTexture && newTexture.loaded) {
this._applySpriteSize();
}
else {
this.disableRender();
spriteFrame.once('load', this._applySpriteSize, this);
}
}
else {
this.disableRender();
}
if (CC_EDITOR) {
// Set atlas
this._applyAtlas(spriteFrame);
}
}
//ccsprite.js
_updateMaterial () {
let texture = this._spriteFrame && this._spriteFrame.getTexture();
// make sure material is belong to self.
let material = this.getMaterial(0);
if (material) {
if (material.getDefine('USE_TEXTURE') !== undefined) {
material.define('USE_TEXTURE', true);
}
//设置纹理信息,将来可以在shader中使用
material.setProperty('texture', texture);
}
BlendFunc.prototype._updateMaterial.call(this);
},