1 /**************************************************************************** 2 Copyright (c) 2011-2012 cocos2d-x.org 3 Copyright (c) 2013-2014 Chukong Technologies Inc. 4 5 http://www.cocos2d-x.org 6 7 Permission is hereby granted, free of charge, to any person obtaining a copy 8 of this software and associated documentation files (the "Software"), to deal 9 in the Software without restriction, including without limitation the rights 10 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 copies of the Software, and to permit persons to whom the Software is 12 furnished to do so, subject to the following conditions: 13 14 The above copyright notice and this permission notice shall be included in 15 all copies or substantial portions of the Software. 16 17 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 THE SOFTWARE. 24 ****************************************************************************/ 25 26 /** 27 * movement event type enum 28 * @constant 29 * @type {Object} 30 */ 31 ccs.MovementEventType = { 32 start: 0, 33 complete: 1, 34 loopComplete: 2 35 }; 36 37 /** 38 * The animation event class, it has the callback, target and arguments. 39 * @deprecated since v3.0. 40 * @class 41 * @extends ccs.Class 42 */ 43 ccs.AnimationEvent = ccs.Class.extend(/** @lends ccs.AnimationEvent# */{ 44 _arguments: null, 45 _callFunc: null, 46 _selectorTarget: null, 47 48 /** 49 * Constructor of ccs.AnimationEvent 50 * @param {function} callFunc 51 * @param {object} target 52 * @param {object} [data] 53 */ 54 ctor: function (callFunc,target, data) { 55 this._data = data; 56 this._callFunc = callFunc; 57 this._selectorTarget = target; 58 }, 59 call: function () { 60 if (this._callFunc) 61 this._callFunc.apply(this._selectorTarget, this._arguments); 62 }, 63 setArguments: function (args) { 64 this._arguments = args; 65 } 66 }); 67 68 /** 69 * The movement event class for Armature. 70 * @constructor 71 * 72 * @property {ccs.Armature} armature - The armature reference of movement event. 73 * @property {Number} movementType - The type of movement. 74 * @property {String} movementID - The ID of movement. 75 */ 76 ccs.MovementEvent = function () { 77 this.armature = null; 78 this.movementType = ccs.MovementEventType.start; 79 this.movementID = ""; 80 }; 81 82 /** 83 * The frame event class for Armature. 84 * @constructor 85 * 86 * @property {ccs.Bone} bone - The bone reference of frame event. 87 * @property {String} frameEventName - The name of frame event. 88 * @property {Number} originFrameIndex - The index of origin frame. 89 * @property {Number} currentFrameIndex - The index of current frame. 90 */ 91 ccs.FrameEvent = function () { 92 this.bone = null; 93 this.frameEventName = ""; 94 this.originFrameIndex = 0; 95 this.currentFrameIndex = 0; 96 }; 97 98 /** 99 * The Animation class for Armature, it plays armature animation, and controls speed scale and manages animation frame. 100 * @class 101 * @extends ccs.ProcessBase 102 * 103 * @property {ccs.AnimationData} animationData - Animation data 104 * @property {Object} userObject - User custom object 105 * @property {Boolean} ignoreFrameEvent - Indicate whether the frame event is ignored 106 * @property {Number} speedScale - Animation play speed scale 107 * @property {Number} animationScale - Animation play speed scale 108 */ 109 ccs.ArmatureAnimation = ccs.ProcessBase.extend(/** @lends ccs.ArmatureAnimation# */{ 110 _animationData: null, 111 _movementData: null, 112 _armature: null, 113 _movementID: "", 114 _toIndex: 0, 115 _tweenList: null, 116 _speedScale: 1, 117 _ignoreFrameEvent: false, 118 _frameEventQueue: null, 119 _movementEventQueue: null, 120 _movementList: null, 121 _onMovementList: false, 122 _movementListLoop: false, 123 _movementIndex: 0, 124 _movementListDurationTo: -1, 125 126 _movementEventCallFunc: null, 127 _frameEventCallFunc: null, 128 _movementEventTarget: null, 129 _frameEventTarget:null, 130 _movementEventListener: null, 131 _frameEventListener: null, 132 133 /** 134 * The Construction of ccs.ArmatureAnimation 135 */ 136 ctor: function () { 137 ccs.ProcessBase.prototype.ctor.call(this); 138 139 this._tweenList = []; 140 this._movementList = []; 141 this._frameEventQueue = []; 142 this._movementEventQueue = []; 143 }, 144 145 /** 146 * Initializes with an armature object 147 * @param {ccs.Armature} armature 148 * @return {Boolean} 149 */ 150 init: function (armature) { 151 this._armature = armature; 152 this._tweenList.length = 0; 153 return true; 154 }, 155 156 /** 157 * Pauses armature animation. 158 */ 159 pause: function () { 160 var locTweenList = this._tweenList; 161 for (var i = 0; i < locTweenList.length; i++) 162 locTweenList[i].pause(); 163 ccs.ProcessBase.prototype.pause.call(this); 164 }, 165 166 /** 167 * Resumes armature animation. 168 */ 169 resume: function () { 170 var locTweenList = this._tweenList; 171 for (var i = 0; i < locTweenList.length; i++) 172 locTweenList[i].resume(); 173 ccs.ProcessBase.prototype.resume.call(this); 174 }, 175 176 /** 177 * Stops armature animation. 178 */ 179 stop: function () { 180 var locTweenList = this._tweenList; 181 for (var i = 0; i < locTweenList.length; i++) 182 locTweenList[i].stop(); 183 locTweenList.length = 0; 184 ccs.ProcessBase.prototype.stop.call(this); 185 }, 186 187 /** 188 * Sets animation play speed scale. 189 * @deprecated since v3.0, please use setSpeedScale instead. 190 * @param {Number} animationScale 191 */ 192 setAnimationScale: function (animationScale) { 193 this.setSpeedScale(animationScale); 194 }, 195 196 /** 197 * Returns animation play speed scale. 198 * @deprecated since v3.0, please use getSpeedScale instead. 199 * @returns {Number} 200 */ 201 getAnimationScale: function () { 202 return this.getSpeedScale(); 203 }, 204 205 /** 206 * Sets animation play speed scale. 207 * @param {Number} speedScale 208 */ 209 setSpeedScale: function (speedScale) { 210 if (speedScale == this._speedScale) 211 return; 212 this._speedScale = speedScale; 213 this._processScale = !this._movementData ? this._speedScale : this._speedScale * this._movementData.scale; 214 var dict = this._armature.getBoneDic(); 215 for (var key in dict) { 216 var bone = dict[key]; 217 bone.getTween().setProcessScale(this._processScale); 218 if (bone.getChildArmature()) 219 bone.getChildArmature().getAnimation().setSpeedScale(this._processScale); 220 } 221 }, 222 223 /** 224 * Returns animation play speed scale. 225 * @returns {Number} 226 */ 227 getSpeedScale: function () { 228 return this._speedScale; 229 }, 230 231 /** 232 * play animation by animation name. 233 * @param {String} animationName The animation name you want to play 234 * @param {Number} [durationTo=-1] 235 * he frames between two animation changing-over.It's meaning is changing to this animation need how many frames 236 * -1 : use the value from CCMovementData get from flash design panel 237 * @param {Number} [loop=-1] 238 * Whether the animation is loop. 239 * loop < 0 : use the value from CCMovementData get from flash design panel 240 * loop = 0 : this animation is not loop 241 * loop > 0 : this animation is loop 242 * @example 243 * // example 244 * armature.getAnimation().play("run",-1,1);//loop play 245 * armature.getAnimation().play("run",-1,0);//not loop play 246 */ 247 play: function (animationName, durationTo, loop) { 248 cc.assert(this._animationData, "this.animationData can not be null"); 249 250 this._movementData = this._animationData.getMovement(animationName); 251 cc.assert(this._movementData, "this._movementData can not be null"); 252 253 durationTo = (durationTo === undefined) ? -1 : durationTo; 254 loop = (loop === undefined) ? -1 : loop; 255 256 //! Get key frame count 257 this._rawDuration = this._movementData.duration; 258 this._movementID = animationName; 259 this._processScale = this._speedScale * this._movementData.scale; 260 261 //! Further processing parameters 262 durationTo = (durationTo == -1) ? this._movementData.durationTo : durationTo; 263 var durationTween = this._movementData.durationTween == 0 ? this._rawDuration : this._movementData.durationTween; 264 265 var tweenEasing = this._movementData.tweenEasing; 266 //loop = (!loop || loop < 0) ? this._movementData.loop : loop; 267 loop = (loop < 0) ? this._movementData.loop : loop; 268 this._onMovementList = false; 269 270 ccs.ProcessBase.prototype.play.call(this, durationTo, durationTween, loop, tweenEasing); 271 272 if (this._rawDuration == 0) 273 this._loopType = ccs.ANIMATION_TYPE_SINGLE_FRAME; 274 else { 275 this._loopType = loop ? ccs.ANIMATION_TYPE_TO_LOOP_FRONT : ccs.ANIMATION_TYPE_NO_LOOP; 276 this._durationTween = durationTween; 277 } 278 279 var movementBoneData; 280 this._tweenList = []; 281 282 var map = this._armature.getBoneDic(); 283 for(var element in map) { 284 var bone = map[element]; 285 movementBoneData = this._movementData.movBoneDataDic[bone.getName()]; 286 287 var tween = bone.getTween(); 288 if(movementBoneData && movementBoneData.frameList.length > 0) { 289 this._tweenList.push(tween); 290 movementBoneData.duration = this._movementData.duration; 291 tween.play(movementBoneData, durationTo, durationTween, loop, tweenEasing); 292 tween.setProcessScale(this._processScale); 293 294 if (bone.getChildArmature()) 295 bone.getChildArmature().getAnimation().setSpeedScale(this._processScale); 296 } else { 297 if(!bone.isIgnoreMovementBoneData()){ 298 //! this bone is not include in this movement, so hide it 299 bone.getDisplayManager().changeDisplayWithIndex(-1, false); 300 tween.stop(); 301 } 302 } 303 } 304 this._armature.update(0); 305 }, 306 307 /** 308 * Plays animation with index, the other param is the same to play. 309 * @param {Number} animationIndex 310 * @param {Number} durationTo 311 * @param {Number} durationTween 312 * @param {Number} loop 313 * @param {Number} [tweenEasing] 314 * @deprecated since v3.0, please use playWithIndex instead. 315 */ 316 playByIndex: function (animationIndex, durationTo, durationTween, loop, tweenEasing) { 317 cc.log("playByIndex is deprecated. Use playWithIndex instead."); 318 this.playWithIndex(animationIndex, durationTo, loop); 319 }, 320 321 /** 322 * Plays animation with index, the other param is the same to play. 323 * @param {Number|Array} animationIndex 324 * @param {Number} durationTo 325 * @param {Number} loop 326 */ 327 playWithIndex: function (animationIndex, durationTo, loop) { 328 var movName = this._animationData.movementNames; 329 cc.assert((animationIndex > -1) && (animationIndex < movName.length)); 330 331 var animationName = movName[animationIndex]; 332 this.play(animationName, durationTo, loop); 333 }, 334 335 /** 336 * Plays animation with names 337 * @param {Array} movementNames 338 * @param {Number} durationTo 339 * @param {Boolean} loop 340 */ 341 playWithNames: function (movementNames, durationTo, loop) { 342 durationTo = (durationTo === undefined) ? -1 : durationTo; 343 loop = (loop === undefined) ? true : loop; 344 345 this._movementListLoop = loop; 346 this._movementListDurationTo = durationTo; 347 this._onMovementList = true; 348 this._movementIndex = 0; 349 if(movementNames instanceof Array) 350 this._movementList = movementNames; 351 else 352 this._movementList.length = 0; 353 this.updateMovementList(); 354 }, 355 356 /** 357 * Plays animation by indexes 358 * @param {Array} movementIndexes 359 * @param {Number} durationTo 360 * @param {Boolean} loop 361 */ 362 playWithIndexes: function (movementIndexes, durationTo, loop) { 363 durationTo = (durationTo === undefined) ? -1 : durationTo; 364 loop = (loop === undefined) ? true : loop; 365 366 this._movementList.length = 0; 367 this._movementListLoop = loop; 368 this._movementListDurationTo = durationTo; 369 this._onMovementList = true; 370 this._movementIndex = 0; 371 372 var movName = this._animationData.movementNames; 373 374 for (var i = 0; i < movementIndexes.length; i++) { 375 var name = movName[movementIndexes[i]]; 376 this._movementList.push(name); 377 } 378 379 this.updateMovementList(); 380 }, 381 382 /** 383 * <p> 384 * Goes to specified frame and plays current movement. <br/> 385 * You need first switch to the movement you want to play, then call this function. <br/> 386 * <br/> 387 * example : playByIndex(0); <br/> 388 * gotoAndPlay(0); <br/> 389 * playByIndex(1); <br/> 390 * gotoAndPlay(0); <br/> 391 * gotoAndPlay(15); <br/> 392 * </p> 393 * @param {Number} frameIndex 394 */ 395 gotoAndPlay: function (frameIndex) { 396 if (!this._movementData || frameIndex < 0 || frameIndex >= this._movementData.duration) { 397 cc.log("Please ensure you have played a movement, and the frameIndex is in the range."); 398 return; 399 } 400 401 var ignoreFrameEvent = this._ignoreFrameEvent; 402 this._ignoreFrameEvent = true; 403 this._isPlaying = true; 404 this._isComplete = this._isPause = false; 405 406 ccs.ProcessBase.prototype.gotoFrame.call(this, frameIndex); 407 this._currentPercent = this._curFrameIndex / (this._movementData.duration - 1); 408 this._currentFrame = this._nextFrameIndex * this._currentPercent; 409 410 var locTweenList = this._tweenList; 411 for (var i = 0; i < locTweenList.length; i++) 412 locTweenList[i].gotoAndPlay(frameIndex); 413 this._armature.update(0); 414 this._ignoreFrameEvent = ignoreFrameEvent; 415 }, 416 417 /** 418 * Goes to specified frame and pauses current movement. 419 * @param {Number} frameIndex 420 */ 421 gotoAndPause: function (frameIndex) { 422 this.gotoAndPlay(frameIndex); 423 this.pause(); 424 }, 425 426 /** 427 * Returns the length of armature's movements 428 * @return {Number} 429 */ 430 getMovementCount: function () { 431 return this._animationData.getMovementCount(); 432 }, 433 434 /** 435 * Updates the state of ccs.Tween list, calls frame event's callback and calls movement event's callback. 436 * @param {Number} dt 437 */ 438 update: function (dt) { 439 ccs.ProcessBase.prototype.update.call(this, dt); 440 441 var locTweenList = this._tweenList; 442 for (var i = 0; i < locTweenList.length; i++) 443 locTweenList[i].update(dt); 444 445 var frameEvents = this._frameEventQueue, event; 446 while (frameEvents.length > 0) { 447 event = frameEvents.shift(); 448 this._ignoreFrameEvent = true; 449 if(this._frameEventCallFunc) 450 this._frameEventCallFunc.call(this._frameEventTarget, event.bone, event.frameEventName, event.originFrameIndex, event.currentFrameIndex); 451 if(this._frameEventListener) 452 this._frameEventListener(event.bone, event.frameEventName, event.originFrameIndex, event.currentFrameIndex); 453 this._ignoreFrameEvent = false; 454 } 455 456 var movementEvents = this._movementEventQueue; 457 while (movementEvents.length > 0) { 458 event = movementEvents.shift(); 459 if(this._movementEventCallFunc) 460 this._movementEventCallFunc.call(this._movementEventTarget, event.armature, event.movementType, event.movementID); 461 if (this._movementEventListener) 462 this._movementEventListener(event.armature, event.movementType, event.movementID); 463 } 464 }, 465 466 /** 467 * Updates will call this handler, you can handle your logic here 468 */ 469 updateHandler: function () { //TODO set it to protected in v3.1 470 var locCurrentPercent = this._currentPercent; 471 if (locCurrentPercent >= 1) { 472 switch (this._loopType) { 473 case ccs.ANIMATION_TYPE_NO_LOOP: 474 this._loopType = ccs.ANIMATION_TYPE_MAX; 475 this._currentFrame = (locCurrentPercent - 1) * this._nextFrameIndex; 476 locCurrentPercent = this._currentFrame / this._durationTween; 477 if (locCurrentPercent < 1.0) { 478 this._nextFrameIndex = this._durationTween; 479 this.movementEvent(this._armature, ccs.MovementEventType.start, this._movementID); 480 break; 481 } 482 break; 483 case ccs.ANIMATION_TYPE_MAX: 484 case ccs.ANIMATION_TYPE_SINGLE_FRAME: 485 locCurrentPercent = 1; 486 this._isComplete = true; 487 this._isPlaying = false; 488 489 this.movementEvent(this._armature, ccs.MovementEventType.complete, this._movementID); 490 491 this.updateMovementList(); 492 break; 493 case ccs.ANIMATION_TYPE_TO_LOOP_FRONT: 494 this._loopType = ccs.ANIMATION_TYPE_LOOP_FRONT; 495 locCurrentPercent = ccs.fmodf(locCurrentPercent, 1); 496 this._currentFrame = this._nextFrameIndex == 0 ? 0 : ccs.fmodf(this._currentFrame, this._nextFrameIndex); 497 this._nextFrameIndex = this._durationTween > 0 ? this._durationTween : 1; 498 this.movementEvent(this, ccs.MovementEventType.start, this._movementID); 499 break; 500 default: 501 //locCurrentPercent = ccs.fmodf(locCurrentPercent, 1); 502 this._currentFrame = ccs.fmodf(this._currentFrame, this._nextFrameIndex); 503 this._toIndex = 0; 504 this.movementEvent(this._armature, ccs.MovementEventType.loopComplete, this._movementID); 505 break; 506 } 507 this._currentPercent = locCurrentPercent; 508 } 509 }, 510 511 /** 512 * Returns the Id of current movement 513 * @returns {String} 514 */ 515 getCurrentMovementID: function () { 516 if (this._isComplete) 517 return ""; 518 return this._movementID; 519 }, 520 521 /** 522 * Sets movement event callback to animation. 523 * @param {function} callFunc 524 * @param {Object} target 525 */ 526 setMovementEventCallFunc: function (callFunc, target) { 527 if(arguments.length == 1){ 528 this._frameEventListener = target; 529 }else if(arguments.length == 2){ 530 this._movementEventTarget = target; 531 this._movementEventCallFunc = callFunc; 532 } 533 }, 534 535 /** 536 * Sets frame event callback to animation. 537 * @param {function} callFunc 538 * @param {Object} target 539 */ 540 setFrameEventCallFunc: function (callFunc, target) { 541 if(arguments.length == 1){ 542 this._frameEventListener = target; 543 }else if(arguments.length == 2){ 544 this._frameEventTarget = target; 545 this._frameEventCallFunc = callFunc; 546 } 547 }, 548 549 /** 550 * Sets user object to animation. 551 * @param {Object} userObject 552 */ 553 setUserObject: function (userObject) { 554 this._userObject = userObject; 555 }, 556 557 /** 558 * Emits a frame event 559 * @param {ccs.Bone} bone 560 * @param {String} frameEventName 561 * @param {Number} originFrameIndex 562 * @param {Number} currentFrameIndex 563 */ 564 frameEvent: function (bone, frameEventName, originFrameIndex, currentFrameIndex) { 565 if ((this._frameEventTarget && this._frameEventCallFunc) || this._frameEventListener) { 566 var frameEvent = new ccs.FrameEvent(); 567 frameEvent.bone = bone; 568 frameEvent.frameEventName = frameEventName; 569 frameEvent.originFrameIndex = originFrameIndex; 570 frameEvent.currentFrameIndex = currentFrameIndex; 571 this._frameEventQueue.push(frameEvent); 572 } 573 }, 574 575 /** 576 * Emits a movement event 577 * @param {ccs.Armature} armature 578 * @param {Number} movementType 579 * @param {String} movementID 580 */ 581 movementEvent: function (armature, movementType, movementID) { 582 if ((this._movementEventTarget && this._movementEventCallFunc) || this._movementEventListener) { 583 var event = new ccs.MovementEvent(); 584 event.armature = armature; 585 event.movementType = movementType; 586 event.movementID = movementID; 587 this._movementEventQueue.push(event); 588 } 589 }, 590 591 /** 592 * Updates movement list. 593 */ 594 updateMovementList: function () { 595 if (this._onMovementList) { 596 var movementObj, locMovementList = this._movementList; 597 if (this._movementListLoop) { 598 movementObj = locMovementList[this._movementIndex]; 599 this.play(movementObj, movementObj.durationTo, 0); 600 this._movementIndex++; 601 if (this._movementIndex >= locMovementList.length) 602 this._movementIndex = 0; 603 } else { 604 if (this._movementIndex < locMovementList.length) { 605 movementObj = locMovementList[this._movementIndex]; 606 this.play(movementObj, movementObj.durationTo, 0); 607 this._movementIndex++; 608 } else 609 this._onMovementList = false; 610 } 611 this._onMovementList = true; 612 } 613 }, 614 615 /** 616 * Sets animation data to animation. 617 * @param {ccs.AnimationData} data 618 */ 619 setAnimationData: function (data) { 620 if(this._animationData != data) 621 this._animationData = data; 622 }, 623 624 /** 625 * Returns animation data of animation. 626 * @return {ccs.AnimationData} 627 */ 628 getAnimationData: function () { 629 return this._animationData; 630 }, 631 632 /** 633 * Returns the user object of animation. 634 * @return {Object} 635 */ 636 getUserObject: function () { 637 return this._userObject; 638 }, 639 640 /** 641 * Determines if the frame event is ignored 642 * @returns {boolean} 643 */ 644 isIgnoreFrameEvent: function () { 645 return this._ignoreFrameEvent; 646 } 647 }); 648 649 var _p = ccs.ArmatureAnimation.prototype; 650 651 // Extended properties 652 /** @expose */ 653 _p.speedScale; 654 cc.defineGetterSetter(_p, "speedScale", _p.getSpeedScale, _p.setSpeedScale); 655 /** @expose */ 656 _p.animationScale; 657 cc.defineGetterSetter(_p, "animationScale", _p.getAnimationScale, _p.setAnimationScale); 658 659 _p = null; 660 661 /** 662 * Allocates and initializes a ArmatureAnimation. 663 * @return {ccs.ArmatureAnimation} 664 * @example 665 * // example 666 * var animation = ccs.ArmatureAnimation.create(); 667 */ 668 ccs.ArmatureAnimation.create = function (armature) { //TODO it will be deprecated in v3.1 669 var animation = new ccs.ArmatureAnimation(); 670 if (animation && animation.init(armature)) 671 return animation; 672 return null; 673 };