骨骼动画定义 传统的动画,一般是对一个物体对象进行位移、旋转、缩放、变形,然后把关键帧的信息记录下来,在播放的时候按照关键帧时间对物体对象进行位移、旋转、缩放、变形,并在关键帧与关键帧之间做插值运算。 骨骼动画的特点是,需要做动画的物体对象本身不记录位移、旋转、缩放、变形信息,而是通过了第三方的“骨骼”物体记录动画信息,然后物体对象本身只记录受到骨骼物体影响的权重。在播放的时候,通过骨骼物体的关键帧和物体对象记录的权重,让动画重现 骨骼动画的好处 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),外界可通过这些骨骼的变化再依据权值来算出蒙皮的状态,最后呈现出来一帧帧动画
**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中取动画数据来播放
一个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); },