好炫酷哇!
1.什么是骨骼动画
当前有两种模型动画的方式:顶点动画和骨骼动画。顶点动画中,每帧动画其实就是模型特定姿态的一个“快照”。通过在帧之间插值的方法,引擎可以得到平滑的动画效果。在骨骼动画中,模型具有互相连接的“骨骼”组成的骨架结构,通过改变骨骼的朝向和位置来为模型生成动画。
骨骼动画比顶点动画要求更高的处理器性能,但同时它也具有更多的优点,骨骼动画可以更容易、更快捷地创建。不同的骨骼动画可以被结合到一起——比如,模型可以转动头部、射击并且同时也在走路。一些引擎可以实时操纵单个骨骼,这样就可以和环境更加准确地进行交互——模型可以俯身并向某个方向观察或射击,或者从地上的某个地方捡起一个东西。多数引擎支持顶点动画,但不是所有的引擎都支持骨骼动画。
2.关于帧动画和骨骼动画的加载与调用
对比一下示例代码:
// *********************帧动画加载与调用************************ // 动作的加载 initAction:function(){ // 站立动作 var sa = cc.Animation.create(); for (var si = 1; si < 4; si++){ var frameName1 = "res/Hero" + si + ".png"; sa.addSpriteFrameWithFile(frameName1); } sa.setDelayPerUnit(5.8 / 14); sa.setRestoreOriginalFrame(true); this._actionStand = cc.RepeatForever.create(cc.Animate.create(sa)); // 跑动动作 var animation = cc.Animation.create(); for (var i = 1; i < 12; i++){ var frameName = "res/HeroRun" + i + ".png"; animation.addSpriteFrameWithFile(frameName); } animation.setDelayPerUnit(2.8 / 14); animation.setRestoreOriginalFrame(true); this._actionRunning = cc.RepeatForever.create(cc.Animate.create(animation)); // 普通攻击 var anAttack = cc.Animation.create(); for (var attackIndex = 1; attackIndex < 6; attackIndex ++){ var attackFrame = "res/HeroAttack" + attackIndex + ".png"; anAttack.addSpriteFrameWithFile(attackFrame); } anAttack.setDelayPerUnit(1.8 / 14); // anAttack.setRestoreOriginalFrame(false); this._actionAttack = cc.Animate.create(anAttack); // 跳跃攻击 ... // 突刺攻击 ... // 其它动作,如果有 ~ } // 动作的调用 this._sprite.runAction(this._actionStand); // 站立 this._sprite.runAction(this._actionRunning); // 跑动 // ... // *********************<a href='http://cn.cocos2d-x.org/article/index?type=cocos2d-x&url=/doc/cocos-docs-master/manual/framework/native/v3/spine/zh.md' target='_blank' title=骨骼动画>骨骼动画</a>加载与调用************************ // 加载骨骼资源 var s_Robot_png = "res/armature/Robot.png"; var s_Robot_plist = "res/armature/Robot.plist"; var s_Robot_json = "res/armature/Robot.json"; cc.ArmatureDataManager.getInstance().addArmatureFileInfo( s_Robot_png,s_Robot_plist,s_Robot_json); this._armature = cc.Armature.create("Robot"); // 使用方法 this._armature.getAnimation().play("stand"); // 站立 this._armature.getAnimation().play("run"); // 跑动 // ...
3.动作组织
游戏中,对于英雄和怪物来说,有一些通用的方法或者代码结构。
var ActionSprite = cc.Node.extend({ // 初始化方法 init:function(obj){...},// 攻击 acceptAttack:function(obj){...},// 是否翻转,图片“左右”走动 isFlip:function(){...},// 设置精灵 setSprite:function(image,pos){...},// 开始跑动 附带方向,方向是一个小于 360 的角度 runWithDegrees:function(degrees){...},// 跑动,改变方向 moveWithDegrees:function(degrees){...},// 停止跑动 idle:function(){...},// 每帧更新 update:function(dt){...},// 简单 ai 实现 ai:function(){...},// 屏幕检测,人物不能走出屏幕之外 并且只能在下方 checkLocation:function(){...},// 站立 hStand:function(){...},// 跑动 hRunning:function(){...},// ... });
若为帧动画,具体实现如下:
hAttack:function(at){ var aa = null; if (at == AT.ATTACK){ aa = this._actionAttack; this._attackRangt = 150; }else if (at == AT.ATTACK_A){ aa = this._actionAttackJump; // 当前位置跳跃 var jump = cc.JumpTo.create( 0.6,cc.pSub(this.getPosition(),cc.p(this._flipX ? 200: -200)),120,1); this.runAction(jump); this._attackRangt = 300; }else if (at == AT.ATTACK_B){ aa = this._actionAttackT; // 当前位置移动 var move = cc.MoveTo.create(0.3,cc.p( this._flipX ? 200:-200,0))); this.runAction(move); this._attackRangt = 300; } if (aa){ this._sprite.stopAllActions(); var action = cc.Sequence.create( aa,cc.CallFunc.create(this.callBackEndAttack,this)); this._sprite.runAction(action); this._state = AC.STATE_HERO_ATTACK; this.postAttack(); } },attack:function(at){ this.hAttack(at); },callBackEndAttack:function(){ if (this._isRun){ this.hRunning(); }else{ this.hStand(); } }
若为骨骼动画,具体实现如下:
var Robot = ActionSprite.extend({ _armture:null,init:function(){ var bRet = false; if (this._super()){ cc.ArmatureDataManager.getInstance().addArmatureFileInfo( s_Robot_png,s_Robot_json); this._armature = cc.Armature.create("NewProject"); this.setSprite(this._armature,cc.p(500,300)); this.setZLocatoin(-90); this.hStand(); this.runWithDegrees(180); this.setRoleType(AC.ROLE_ROBOT); this._imageflipX = true; bRet = true; this._speed = 150; } return bRet; },setSprite:function(armature,pos){ this._sprite = armature; this.addChild(this._sprite); this.setPosition(pos); },hAttack:function(at){ this._attackRangt = 150; this._sprite.stopAllActions(); this._sprite.getAnimation().play("attack"); this._sprite.getAnimation().setMovementEventCallFunc(this.callBackEndAttack,this); this._state = AC.STATE_HERO_ATTACK; this.postAttack(); },hStand:function(){ this._sprite.getAnimation().play("stand"); this._state = AC.STATE_HERO_STAND; },hRunning:function(){ this._sprite.getAnimation().play("run"); this._state = AC.STATE_HERO_RUNNING; },attack:function(button){ this.hAttack(button); },callBackEndAttack:function(armature,movementType,movementID){ if (movementType == CC_MovementEventType_LOOP_COMPLETE) { if (this._isRun){ this.hRunning(); }else{ this.hStand(); } } },_timestamp: (new Date()).valueOf(),_attackIndex: 0,_moveIndex: 0,ai:function(){ var newTs = (new Date()).valueOf(); var value = newTs - this._timestamp; if (this._moveIndex < value / 3000){ this._moveIndex += 1; var r = Math.random() * 360; this.moveWithDegrees(r); } if (this._attackIndex < value / 6000){ this._attackIndex += 1; this.attack(); } } });
4.读官方示例代码
var sp = sp || {}; 开一个命名空间 var ANIMATION_TYPE = { 宏定义小人的动作类型,为状态机做准备。 ANIMATION_START: 0, ANIMATION_END: 1, ANIMATION_COMPLETE: 2,ANIMATION_EVENT: 3 }; SpineTestScene = TestScene.extend({ 里面封装了prototype函数。将对象实例化。这是一个场景类 runThisTest:function () { var layer = new SpineTest(); this.addChild(layer); 运行layer层 director.runScene(this); 调用 导演,运行此场景类 } }); touchcount = 0; var SpineTest = BaseTestLayer.extend({ 骨骼动画层 _spineboy:null,_debugMode: 0,_flipped: false,ctor:function () { 构造函数 this._super(cc.color(0,0,255),cc.color(98,99,117,255)); 背景颜色 cc.eventManager.addListener({ 添加响应事件 event: cc.EventListener.TOUCH_ALL_AT_ONCE,关于cc.eventlistener,一会总结吧 onTouchesBegan: function(touches,event){ var target = event.getCurrentTarget(); target._debugMode ++; target._debugMode = target._debugMode % 3; if (target._debugMode == 0) { target._spineboy.setDebugBones(false); target._spineboy.setDebugSolots(false); return; } if (target._debugMode == 1) { target._spineboy.setDebugBones(true); target._spineboy.setDebugSolots(false); return; } if (target._debugMode == 2) { target._spineboy.setDebugBones(false); target._spineboy.setDebugSolots(true); } } },this); var size = director.getWinSize(); ///////////////////////////// // Make Spine's Animated skeleton Node // You need 'json + atlas + image' resource files to make it. // No JS binding for spine-c in this version. So,only file loading is supported. var SpineBoyAnimation = sp.SkeletonAnimation.extend({ ctor: function() { this._super('res/skeletons/spineboy.json','res/skeletons/spineboy.atlas'); cc.log("Extended SkeletonAnimation"); } }); var spineBoy = new SpineBoyAnimation(); spineBoy.setPosition(cc.p(size.width / 2,size.height / 2 - 150)); spineBoy.setAnimation(0,'walk',true); spineBoy.setMix('walk','jump',0.2); spineBoy.setMix('jump',0.4); spineBoy.setAnimationListener(this,this.animationStateEvent); spineBoy.setScale(0.5); this.addChild(spineBoy,4); this._spineboy = spineBoy; },onBackCallback:function (sender) { },onRestartCallback:function (sender) { },onNextCallback:function (sender) { touchcount++; this._spineboy.setAnimation(0,['walk','run','shoot'][touchcount % 4],true); },subtitle:function () { return "Spine test"; },title:function () { return "Spine test"; },animationStateEvent: function(obj,trackIndex,type,event,loopCount) { var entry = this._spineboy.getCurrent(); var animationName = (entry && entry.animation) ? entry.animation.name : 0; switch(type) { case ANIMATION_TYPE.ANIMATION_START: cc.log(trackIndex + " start: " + animationName); break; case ANIMATION_TYPE.ANIMATION_END: cc.log(trackIndex + " end:" + animationName); break; case ANIMATION_TYPE.ANIMATION_EVENT: cc.log(trackIndex + " event: " + animationName); break; case ANIMATION_TYPE.ANIMATION_COMPLETE: cc.log(trackIndex + " complete: " + animationName + "," + loopCount); if(this._flipped){ this._flipped = false; this._spineboy.setScaleX(0.5); }else{ this._flipped = true; this._spineboy.setScaleX(-0.5); } break; default : break; } },// automation numberOfPendingTests:function() { return 1; },getTestNumber:function() { return 0; } });