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 * @param {ccs.Armature} [armature] The armature 104 * 105 * @property {ccs.AnimationData} animationData - Animation data 106 * @property {Object} userObject - User custom object 107 * @property {Boolean} ignoreFrameEvent - Indicate whether the frame event is ignored 108 * @property {Number} speedScale - Animation play speed scale 109 * @property {Number} animationScale - Animation play speed scale 110 */ 111 ccs.ArmatureAnimation = ccs.ProcessBase.extend(/** @lends ccs.ArmatureAnimation# */{ 112 _animationData: null, 113 _movementData: null, 114 _armature: null, 115 _movementID: "", 116 _toIndex: 0, 117 _tweenList: null, 118 _speedScale: 1, 119 _ignoreFrameEvent: false, 120 _frameEventQueue: null, 121 _movementEventQueue: null, 122 _movementList: null, 123 _onMovementList: false, 124 _movementListLoop: false, 125 _movementIndex: 0, 126 _movementListDurationTo: -1, 127 128 _movementEventCallFunc: null, 129 _frameEventCallFunc: null, 130 _movementEventTarget: null, 131 _frameEventTarget:null, 132 _movementEventListener: null, 133 _frameEventListener: null, 134 135 ctor: function (armature) { 136 ccs.ProcessBase.prototype.ctor.call(this); 137 138 this._tweenList = []; 139 this._movementList = []; 140 this._frameEventQueue = []; 141 this._movementEventQueue = []; 142 this._armature = null; 143 144 armature && ccs.ArmatureAnimation.prototype.init.call(this, armature); 145 }, 146 147 /** 148 * Initializes with an armature object 149 * @param {ccs.Armature} armature 150 * @return {Boolean} 151 */ 152 init: function (armature) { 153 this._armature = armature; 154 this._tweenList.length = 0; 155 return true; 156 }, 157 158 /** 159 * Pauses armature animation. 160 */ 161 pause: function () { 162 var locTweenList = this._tweenList; 163 for (var i = 0; i < locTweenList.length; i++) 164 locTweenList[i].pause(); 165 ccs.ProcessBase.prototype.pause.call(this); 166 }, 167 168 /** 169 * Resumes armature animation. 170 */ 171 resume: function () { 172 var locTweenList = this._tweenList; 173 for (var i = 0; i < locTweenList.length; i++) 174 locTweenList[i].resume(); 175 ccs.ProcessBase.prototype.resume.call(this); 176 }, 177 178 /** 179 * Stops armature animation. 180 */ 181 stop: function () { 182 var locTweenList = this._tweenList; 183 for (var i = 0; i < locTweenList.length; i++) 184 locTweenList[i].stop(); 185 locTweenList.length = 0; 186 ccs.ProcessBase.prototype.stop.call(this); 187 }, 188 189 /** 190 * Sets animation play speed scale. 191 * @deprecated since v3.0, please use setSpeedScale instead. 192 * @param {Number} animationScale 193 */ 194 setAnimationScale: function (animationScale) { 195 this.setSpeedScale(animationScale); 196 }, 197 198 /** 199 * Returns animation play speed scale. 200 * @deprecated since v3.0, please use getSpeedScale instead. 201 * @returns {Number} 202 */ 203 getAnimationScale: function () { 204 return this.getSpeedScale(); 205 }, 206 207 /** 208 * Sets animation play speed scale. 209 * @param {Number} speedScale 210 */ 211 setSpeedScale: function (speedScale) { 212 if (speedScale === this._speedScale) 213 return; 214 this._speedScale = speedScale; 215 this._processScale = !this._movementData ? this._speedScale : this._speedScale * this._movementData.scale; 216 var dict = this._armature.getBoneDic(); 217 for (var key in dict) { 218 var bone = dict[key]; 219 bone.getTween().setProcessScale(this._processScale); 220 if (bone.getChildArmature()) 221 bone.getChildArmature().getAnimation().setSpeedScale(this._processScale); 222 } 223 }, 224 225 /** 226 * Returns animation play speed scale. 227 * @returns {Number} 228 */ 229 getSpeedScale: function () { 230 return this._speedScale; 231 }, 232 233 /** 234 * play animation by animation name. 235 * @param {String} animationName The animation name you want to play 236 * @param {Number} [durationTo=-1] 237 * the frames between two animation changing-over.It's meaning is changing to this animation need how many frames 238 * -1 : use the value from CCMovementData get from flash design panel 239 * @param {Number} [loop=-1] 240 * Whether the animation is loop. 241 * loop < 0 : use the value from CCMovementData get from flash design panel 242 * loop = 0 : this animation is not loop 243 * loop > 0 : this animation is loop 244 * @example 245 * // example 246 * armature.getAnimation().play("run",-1,1);//loop play 247 * armature.getAnimation().play("run",-1,0);//not loop play 248 */ 249 play: function (animationName, durationTo, loop) { 250 cc.assert(this._animationData, "this.animationData can not be null"); 251 252 this._movementData = this._animationData.getMovement(animationName); 253 cc.assert(this._movementData, "this._movementData can not be null"); 254 255 durationTo = (durationTo === undefined) ? -1 : durationTo; 256 loop = (loop === undefined) ? -1 : loop; 257 258 //! Get key frame count 259 this._rawDuration = this._movementData.duration; 260 this._movementID = animationName; 261 this._processScale = this._speedScale * this._movementData.scale; 262 263 //! Further processing parameters 264 durationTo = (durationTo === -1) ? this._movementData.durationTo : durationTo; 265 var durationTween = this._movementData.durationTween === 0 ? this._rawDuration : this._movementData.durationTween; 266 267 var tweenEasing = this._movementData.tweenEasing; 268 //loop = (!loop || loop < 0) ? this._movementData.loop : loop; 269 loop = (loop < 0) ? this._movementData.loop : loop; 270 this._onMovementList = false; 271 272 ccs.ProcessBase.prototype.play.call(this, durationTo, durationTween, loop, tweenEasing); 273 274 if (this._rawDuration === 0) 275 this._loopType = ccs.ANIMATION_TYPE_SINGLE_FRAME; 276 else { 277 this._loopType = loop ? ccs.ANIMATION_TYPE_TO_LOOP_FRONT : ccs.ANIMATION_TYPE_NO_LOOP; 278 this._durationTween = durationTween; 279 } 280 281 this._tweenList.length = 0; 282 283 var movementBoneData, map = this._armature.getBoneDic(); 284 for(var element in map) { 285 var bone = map[element]; 286 movementBoneData = this._movementData.movBoneDataDic[bone.getName()]; 287 288 var tween = bone.getTween(); 289 if(movementBoneData && movementBoneData.frameList.length > 0) { 290 this._tweenList.push(tween); 291 movementBoneData.duration = this._movementData.duration; 292 tween.play(movementBoneData, durationTo, durationTween, loop, tweenEasing); 293 tween.setProcessScale(this._processScale); 294 295 if (bone.getChildArmature()) { 296 bone.getChildArmature().getAnimation().setSpeedScale(this._processScale); 297 if (!bone.getChildArmature().getAnimation().isPlaying()) 298 bone.getChildArmature().getAnimation().playWithIndex(0); 299 } 300 } else { 301 if(!bone.isIgnoreMovementBoneData()){ 302 //! this bone is not include in this movement, so hide it 303 bone.getDisplayManager().changeDisplayWithIndex(-1, false); 304 tween.stop(); 305 } 306 } 307 } 308 this._armature.update(0); 309 }, 310 311 /** 312 * Plays animation with index, the other param is the same to play. 313 * @param {Number} animationIndex 314 * @param {Number} durationTo 315 * @param {Number} durationTween 316 * @param {Number} loop 317 * @param {Number} [tweenEasing] 318 * @deprecated since v3.0, please use playWithIndex instead. 319 */ 320 playByIndex: function (animationIndex, durationTo, durationTween, loop, tweenEasing) { 321 cc.log("playByIndex is deprecated. Use playWithIndex instead."); 322 this.playWithIndex(animationIndex, durationTo, loop); 323 }, 324 325 /** 326 * Plays animation with index, the other param is the same to play. 327 * @param {Number|Array} animationIndex 328 * @param {Number} durationTo 329 * @param {Number} loop 330 */ 331 playWithIndex: function (animationIndex, durationTo, loop) { 332 var movName = this._animationData.movementNames; 333 cc.assert((animationIndex > -1) && (animationIndex < movName.length)); 334 335 var animationName = movName[animationIndex]; 336 this.play(animationName, durationTo, loop); 337 }, 338 339 /** 340 * Plays animation with names 341 * @param {Array} movementNames 342 * @param {Number} durationTo 343 * @param {Boolean} loop 344 */ 345 playWithNames: function (movementNames, durationTo, loop) { 346 durationTo = (durationTo === undefined) ? -1 : durationTo; 347 loop = (loop === undefined) ? true : loop; 348 349 this._movementListLoop = loop; 350 this._movementListDurationTo = durationTo; 351 this._onMovementList = true; 352 this._movementIndex = 0; 353 if(movementNames instanceof Array) 354 this._movementList = movementNames; 355 else 356 this._movementList.length = 0; 357 this.updateMovementList(); 358 }, 359 360 /** 361 * Plays animation by indexes 362 * @param {Array} movementIndexes 363 * @param {Number} durationTo 364 * @param {Boolean} loop 365 */ 366 playWithIndexes: function (movementIndexes, durationTo, loop) { 367 durationTo = (durationTo === undefined) ? -1 : durationTo; 368 loop = (loop === undefined) ? true : loop; 369 370 this._movementList.length = 0; 371 this._movementListLoop = loop; 372 this._movementListDurationTo = durationTo; 373 this._onMovementList = true; 374 this._movementIndex = 0; 375 376 var movName = this._animationData.movementNames; 377 378 for (var i = 0; i < movementIndexes.length; i++) { 379 var name = movName[movementIndexes[i]]; 380 this._movementList.push(name); 381 } 382 383 this.updateMovementList(); 384 }, 385 386 /** 387 * <p> 388 * Goes to specified frame and plays current movement. <br/> 389 * You need first switch to the movement you want to play, then call this function. <br/> 390 * <br/> 391 * example : playByIndex(0); <br/> 392 * gotoAndPlay(0); <br/> 393 * playByIndex(1); <br/> 394 * gotoAndPlay(0); <br/> 395 * gotoAndPlay(15); <br/> 396 * </p> 397 * @param {Number} frameIndex 398 */ 399 gotoAndPlay: function (frameIndex) { 400 if (!this._movementData || frameIndex < 0 || frameIndex >= this._movementData.duration) { 401 cc.log("Please ensure you have played a movement, and the frameIndex is in the range."); 402 return; 403 } 404 405 var ignoreFrameEvent = this._ignoreFrameEvent; 406 this._ignoreFrameEvent = true; 407 this._isPlaying = true; 408 this._isComplete = this._isPause = false; 409 410 ccs.ProcessBase.prototype.gotoFrame.call(this, frameIndex); 411 this._currentPercent = this._curFrameIndex / (this._movementData.duration - 1); 412 this._currentFrame = this._nextFrameIndex * this._currentPercent; 413 414 var locTweenList = this._tweenList; 415 for (var i = 0; i < locTweenList.length; i++) 416 locTweenList[i].gotoAndPlay(frameIndex); 417 this._armature.update(0); 418 this._ignoreFrameEvent = ignoreFrameEvent; 419 }, 420 421 /** 422 * Goes to specified frame and pauses current movement. 423 * @param {Number} frameIndex 424 */ 425 gotoAndPause: function (frameIndex) { 426 this.gotoAndPlay(frameIndex); 427 this.pause(); 428 }, 429 430 /** 431 * Returns the length of armature's movements 432 * @return {Number} 433 */ 434 getMovementCount: function () { 435 return this._animationData.getMovementCount(); 436 }, 437 438 /** 439 * Updates the state of ccs.Tween list, calls frame event's callback and calls movement event's callback. 440 * @param {Number} dt 441 */ 442 update: function (dt) { 443 ccs.ProcessBase.prototype.update.call(this, dt); 444 445 var locTweenList = this._tweenList; 446 for (var i = 0; i < locTweenList.length; i++) 447 locTweenList[i].update(dt); 448 449 var frameEvents = this._frameEventQueue, event; 450 while (frameEvents.length > 0) { 451 event = frameEvents.shift(); 452 this._ignoreFrameEvent = true; 453 if(this._frameEventCallFunc) 454 this._frameEventCallFunc.call(this._frameEventTarget, event.bone, event.frameEventName, event.originFrameIndex, event.currentFrameIndex); 455 if(this._frameEventListener) 456 this._frameEventListener(event.bone, event.frameEventName, event.originFrameIndex, event.currentFrameIndex); 457 this._ignoreFrameEvent = false; 458 } 459 460 var movementEvents = this._movementEventQueue; 461 while (movementEvents.length > 0) { 462 event = movementEvents.shift(); 463 if(this._movementEventCallFunc) 464 this._movementEventCallFunc.call(this._movementEventTarget, event.armature, event.movementType, event.movementID); 465 if (this._movementEventListener) 466 this._movementEventListener(event.armature, event.movementType, event.movementID); 467 } 468 }, 469 470 /** 471 * Updates will call this handler, you can handle your logic here 472 */ 473 updateHandler: function () { //TODO set it to protected in v3.1 474 var locCurrentPercent = this._currentPercent; 475 if (locCurrentPercent >= 1) { 476 switch (this._loopType) { 477 case ccs.ANIMATION_TYPE_NO_LOOP: 478 this._loopType = ccs.ANIMATION_TYPE_MAX; 479 this._currentFrame = (locCurrentPercent - 1) * this._nextFrameIndex; 480 locCurrentPercent = this._currentFrame / this._durationTween; 481 if (locCurrentPercent < 1.0) { 482 this._nextFrameIndex = this._durationTween; 483 this.movementEvent(this._armature, ccs.MovementEventType.start, this._movementID); 484 break; 485 } 486 break; 487 case ccs.ANIMATION_TYPE_MAX: 488 case ccs.ANIMATION_TYPE_SINGLE_FRAME: 489 locCurrentPercent = 1; 490 this._isComplete = true; 491 this._isPlaying = false; 492 493 this.movementEvent(this._armature, ccs.MovementEventType.complete, this._movementID); 494 495 this.updateMovementList(); 496 break; 497 case ccs.ANIMATION_TYPE_TO_LOOP_FRONT: 498 this._loopType = ccs.ANIMATION_TYPE_LOOP_FRONT; 499 locCurrentPercent = ccs.fmodf(locCurrentPercent, 1); 500 this._currentFrame = this._nextFrameIndex === 0 ? 0 : ccs.fmodf(this._currentFrame, this._nextFrameIndex); 501 this._nextFrameIndex = this._durationTween > 0 ? this._durationTween : 1; 502 this.movementEvent(this, ccs.MovementEventType.start, this._movementID); 503 break; 504 default: 505 //locCurrentPercent = ccs.fmodf(locCurrentPercent, 1); 506 this._currentFrame = ccs.fmodf(this._currentFrame, this._nextFrameIndex); 507 this._toIndex = 0; 508 this.movementEvent(this._armature, ccs.MovementEventType.loopComplete, this._movementID); 509 break; 510 } 511 this._currentPercent = locCurrentPercent; 512 } 513 }, 514 515 /** 516 * Returns the Id of current movement 517 * @returns {String} 518 */ 519 getCurrentMovementID: function () { 520 if (this._isComplete) 521 return ""; 522 return this._movementID; 523 }, 524 525 /** 526 * Sets movement event callback to animation. 527 * @param {function} callFunc 528 * @param {Object} target 529 */ 530 setMovementEventCallFunc: function (callFunc, target) { 531 if(arguments.length === 1){ 532 this._movementEventListener = callFunc; 533 }else if(arguments.length === 2){ 534 this._movementEventTarget = target; 535 this._movementEventCallFunc = callFunc; 536 } 537 }, 538 539 /** 540 * Sets frame event callback to animation. 541 * @param {function} callFunc 542 * @param {Object} target 543 */ 544 setFrameEventCallFunc: function (callFunc, target) { 545 if(arguments.length === 1){ 546 this._frameEventListener = callFunc; 547 }else if(arguments.length === 2){ 548 this._frameEventTarget = target; 549 this._frameEventCallFunc = callFunc; 550 } 551 }, 552 553 /** 554 * Sets user object to animation. 555 * @param {Object} userObject 556 */ 557 setUserObject: function (userObject) { 558 this._userObject = userObject; 559 }, 560 561 /** 562 * Emits a frame event 563 * @param {ccs.Bone} bone 564 * @param {String} frameEventName 565 * @param {Number} originFrameIndex 566 * @param {Number} currentFrameIndex 567 */ 568 frameEvent: function (bone, frameEventName, originFrameIndex, currentFrameIndex) { 569 if ((this._frameEventTarget && this._frameEventCallFunc) || this._frameEventListener) { 570 var frameEvent = new ccs.FrameEvent(); 571 frameEvent.bone = bone; 572 frameEvent.frameEventName = frameEventName; 573 frameEvent.originFrameIndex = originFrameIndex; 574 frameEvent.currentFrameIndex = currentFrameIndex; 575 this._frameEventQueue.push(frameEvent); 576 } 577 }, 578 579 /** 580 * Emits a movement event 581 * @param {ccs.Armature} armature 582 * @param {Number} movementType 583 * @param {String} movementID 584 */ 585 movementEvent: function (armature, movementType, movementID) { 586 if ((this._movementEventTarget && this._movementEventCallFunc) || this._movementEventListener) { 587 var event = new ccs.MovementEvent(); 588 event.armature = armature; 589 event.movementType = movementType; 590 event.movementID = movementID; 591 this._movementEventQueue.push(event); 592 } 593 }, 594 595 /** 596 * Updates movement list. 597 */ 598 updateMovementList: function () { 599 if (this._onMovementList) { 600 var movementObj, locMovementList = this._movementList; 601 if (this._movementListLoop) { 602 movementObj = locMovementList[this._movementIndex]; 603 this.play(movementObj, movementObj.durationTo, 0); 604 this._movementIndex++; 605 if (this._movementIndex >= locMovementList.length) 606 this._movementIndex = 0; 607 } else { 608 if (this._movementIndex < locMovementList.length) { 609 movementObj = locMovementList[this._movementIndex]; 610 this.play(movementObj, movementObj.durationTo, 0); 611 this._movementIndex++; 612 } else 613 this._onMovementList = false; 614 } 615 this._onMovementList = true; 616 } 617 }, 618 619 /** 620 * Sets animation data to animation. 621 * @param {ccs.AnimationData} data 622 */ 623 setAnimationData: function (data) { 624 if(this._animationData !== data) 625 this._animationData = data; 626 }, 627 628 /** 629 * Returns animation data of animation. 630 * @return {ccs.AnimationData} 631 */ 632 getAnimationData: function () { 633 return this._animationData; 634 }, 635 636 /** 637 * Returns the user object of animation. 638 * @return {Object} 639 */ 640 getUserObject: function () { 641 return this._userObject; 642 }, 643 644 /** 645 * Determines if the frame event is ignored 646 * @returns {boolean} 647 */ 648 isIgnoreFrameEvent: function () { 649 return this._ignoreFrameEvent; 650 } 651 }); 652 653 var _p = ccs.ArmatureAnimation.prototype; 654 655 // Extended properties 656 /** @expose */ 657 _p.speedScale; 658 cc.defineGetterSetter(_p, "speedScale", _p.getSpeedScale, _p.setSpeedScale); 659 /** @expose */ 660 _p.animationScale; 661 cc.defineGetterSetter(_p, "animationScale", _p.getAnimationScale, _p.setAnimationScale); 662 663 _p = null; 664 665 /** 666 * Allocates and initializes a ArmatureAnimation. 667 * @return {ccs.ArmatureAnimation} 668 * @deprecated since v3.1, please use new construction instead 669 */ 670 ccs.ArmatureAnimation.create = function (armature) { 671 return new ccs.ArmatureAnimation(armature); 672 }; 673