好炫酷哇!
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.pSub(this.getPosition(), 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_plist,
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,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', 'walk', 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', 'jump','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; } });