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 } else { 298 if(!bone.isIgnoreMovementBoneData()){ 299 //! this bone is not include in this movement, so hide it 300 bone.getDisplayManager().changeDisplayWithIndex(-1, false); 301 tween.stop(); 302 } 303 } 304 } 305 this._armature.update(0); 306 }, 307 308 /** 309 * Plays animation with index, the other param is the same to play. 310 * @param {Number} animationIndex 311 * @param {Number} durationTo 312 * @param {Number} durationTween 313 * @param {Number} loop 314 * @param {Number} [tweenEasing] 315 * @deprecated since v3.0, please use playWithIndex instead. 316 */ 317 playByIndex: function (animationIndex, durationTo, durationTween, loop, tweenEasing) { 318 cc.log("playByIndex is deprecated. Use playWithIndex instead."); 319 this.playWithIndex(animationIndex, durationTo, loop); 320 }, 321 322 /** 323 * Plays animation with index, the other param is the same to play. 324 * @param {Number|Array} animationIndex 325 * @param {Number} durationTo 326 * @param {Number} loop 327 */ 328 playWithIndex: function (animationIndex, durationTo, loop) { 329 var movName = this._animationData.movementNames; 330 cc.assert((animationIndex > -1) && (animationIndex < movName.length)); 331 332 var animationName = movName[animationIndex]; 333 this.play(animationName, durationTo, loop); 334 }, 335 336 /** 337 * Plays animation with names 338 * @param {Array} movementNames 339 * @param {Number} durationTo 340 * @param {Boolean} loop 341 */ 342 playWithNames: function (movementNames, durationTo, loop) { 343 durationTo = (durationTo === undefined) ? -1 : durationTo; 344 loop = (loop === undefined) ? true : loop; 345 346 this._movementListLoop = loop; 347 this._movementListDurationTo = durationTo; 348 this._onMovementList = true; 349 this._movementIndex = 0; 350 if(movementNames instanceof Array) 351 this._movementList = movementNames; 352 else 353 this._movementList.length = 0; 354 this.updateMovementList(); 355 }, 356 357 /** 358 * Plays animation by indexes 359 * @param {Array} movementIndexes 360 * @param {Number} durationTo 361 * @param {Boolean} loop 362 */ 363 playWithIndexes: function (movementIndexes, durationTo, loop) { 364 durationTo = (durationTo === undefined) ? -1 : durationTo; 365 loop = (loop === undefined) ? true : loop; 366 367 this._movementList.length = 0; 368 this._movementListLoop = loop; 369 this._movementListDurationTo = durationTo; 370 this._onMovementList = true; 371 this._movementIndex = 0; 372 373 var movName = this._animationData.movementNames; 374 375 for (var i = 0; i < movementIndexes.length; i++) { 376 var name = movName[movementIndexes[i]]; 377 this._movementList.push(name); 378 } 379 380 this.updateMovementList(); 381 }, 382 383 /** 384 * <p> 385 * Goes to specified frame and plays current movement. <br/> 386 * You need first switch to the movement you want to play, then call this function. <br/> 387 * <br/> 388 * example : playByIndex(0); <br/> 389 * gotoAndPlay(0); <br/> 390 * playByIndex(1); <br/> 391 * gotoAndPlay(0); <br/> 392 * gotoAndPlay(15); <br/> 393 * </p> 394 * @param {Number} frameIndex 395 */ 396 gotoAndPlay: function (frameIndex) { 397 if (!this._movementData || frameIndex < 0 || frameIndex >= this._movementData.duration) { 398 cc.log("Please ensure you have played a movement, and the frameIndex is in the range."); 399 return; 400 } 401 402 var ignoreFrameEvent = this._ignoreFrameEvent; 403 this._ignoreFrameEvent = true; 404 this._isPlaying = true; 405 this._isComplete = this._isPause = false; 406 407 ccs.ProcessBase.prototype.gotoFrame.call(this, frameIndex); 408 this._currentPercent = this._curFrameIndex / (this._movementData.duration - 1); 409 this._currentFrame = this._nextFrameIndex * this._currentPercent; 410 411 var locTweenList = this._tweenList; 412 for (var i = 0; i < locTweenList.length; i++) 413 locTweenList[i].gotoAndPlay(frameIndex); 414 this._armature.update(0); 415 this._ignoreFrameEvent = ignoreFrameEvent; 416 }, 417 418 /** 419 * Goes to specified frame and pauses current movement. 420 * @param {Number} frameIndex 421 */ 422 gotoAndPause: function (frameIndex) { 423 this.gotoAndPlay(frameIndex); 424 this.pause(); 425 }, 426 427 /** 428 * Returns the length of armature's movements 429 * @return {Number} 430 */ 431 getMovementCount: function () { 432 return this._animationData.getMovementCount(); 433 }, 434 435 /** 436 * Updates the state of ccs.Tween list, calls frame event's callback and calls movement event's callback. 437 * @param {Number} dt 438 */ 439 update: function (dt) { 440 ccs.ProcessBase.prototype.update.call(this, dt); 441 442 var locTweenList = this._tweenList; 443 for (var i = 0; i < locTweenList.length; i++) 444 locTweenList[i].update(dt); 445 446 var frameEvents = this._frameEventQueue, event; 447 while (frameEvents.length > 0) { 448 event = frameEvents.shift(); 449 this._ignoreFrameEvent = true; 450 if(this._frameEventCallFunc) 451 this._frameEventCallFunc.call(this._frameEventTarget, event.bone, event.frameEventName, event.originFrameIndex, event.currentFrameIndex); 452 if(this._frameEventListener) 453 this._frameEventListener(event.bone, event.frameEventName, event.originFrameIndex, event.currentFrameIndex); 454 this._ignoreFrameEvent = false; 455 } 456 457 var movementEvents = this._movementEventQueue; 458 while (movementEvents.length > 0) { 459 event = movementEvents.shift(); 460 if(this._movementEventCallFunc) 461 this._movementEventCallFunc.call(this._movementEventTarget, event.armature, event.movementType, event.movementID); 462 if (this._movementEventListener) 463 this._movementEventListener(event.armature, event.movementType, event.movementID); 464 } 465 }, 466 467 /** 468 * Updates will call this handler, you can handle your logic here 469 */ 470 updateHandler: function () { //TODO set it to protected in v3.1 471 var locCurrentPercent = this._currentPercent; 472 if (locCurrentPercent >= 1) { 473 switch (this._loopType) { 474 case ccs.ANIMATION_TYPE_NO_LOOP: 475 this._loopType = ccs.ANIMATION_TYPE_MAX; 476 this._currentFrame = (locCurrentPercent - 1) * this._nextFrameIndex; 477 locCurrentPercent = this._currentFrame / this._durationTween; 478 if (locCurrentPercent < 1.0) { 479 this._nextFrameIndex = this._durationTween; 480 this.movementEvent(this._armature, ccs.MovementEventType.start, this._movementID); 481 break; 482 } 483 break; 484 case ccs.ANIMATION_TYPE_MAX: 485 case ccs.ANIMATION_TYPE_SINGLE_FRAME: 486 locCurrentPercent = 1; 487 this._isComplete = true; 488 this._isPlaying = false; 489 490 this.movementEvent(this._armature, ccs.MovementEventType.complete, this._movementID); 491 492 this.updateMovementList(); 493 break; 494 case ccs.ANIMATION_TYPE_TO_LOOP_FRONT: 495 this._loopType = ccs.ANIMATION_TYPE_LOOP_FRONT; 496 locCurrentPercent = ccs.fmodf(locCurrentPercent, 1); 497 this._currentFrame = this._nextFrameIndex === 0 ? 0 : ccs.fmodf(this._currentFrame, this._nextFrameIndex); 498 this._nextFrameIndex = this._durationTween > 0 ? this._durationTween : 1; 499 this.movementEvent(this, ccs.MovementEventType.start, this._movementID); 500 break; 501 default: 502 //locCurrentPercent = ccs.fmodf(locCurrentPercent, 1); 503 this._currentFrame = ccs.fmodf(this._currentFrame, this._nextFrameIndex); 504 this._toIndex = 0; 505 this.movementEvent(this._armature, ccs.MovementEventType.loopComplete, this._movementID); 506 break; 507 } 508 this._currentPercent = locCurrentPercent; 509 } 510 }, 511 512 /** 513 * Returns the Id of current movement 514 * @returns {String} 515 */ 516 getCurrentMovementID: function () { 517 if (this._isComplete) 518 return ""; 519 return this._movementID; 520 }, 521 522 /** 523 * Sets movement event callback to animation. 524 * @param {function} callFunc 525 * @param {Object} target 526 */ 527 setMovementEventCallFunc: function (callFunc, target) { 528 if(arguments.length === 1){ 529 this._movementEventListener = callFunc; 530 }else if(arguments.length === 2){ 531 this._movementEventTarget = target; 532 this._movementEventCallFunc = callFunc; 533 } 534 }, 535 536 /** 537 * Sets frame event callback to animation. 538 * @param {function} callFunc 539 * @param {Object} target 540 */ 541 setFrameEventCallFunc: function (callFunc, target) { 542 if(arguments.length === 1){ 543 this._frameEventListener = callFunc; 544 }else if(arguments.length === 2){ 545 this._frameEventTarget = target; 546 this._frameEventCallFunc = callFunc; 547 } 548 }, 549 550 /** 551 * Sets user object to animation. 552 * @param {Object} userObject 553 */ 554 setUserObject: function (userObject) { 555 this._userObject = userObject; 556 }, 557 558 /** 559 * Emits a frame event 560 * @param {ccs.Bone} bone 561 * @param {String} frameEventName 562 * @param {Number} originFrameIndex 563 * @param {Number} currentFrameIndex 564 */ 565 frameEvent: function (bone, frameEventName, originFrameIndex, currentFrameIndex) { 566 if ((this._frameEventTarget && this._frameEventCallFunc) || this._frameEventListener) { 567 var frameEvent = new ccs.FrameEvent(); 568 frameEvent.bone = bone; 569 frameEvent.frameEventName = frameEventName; 570 frameEvent.originFrameIndex = originFrameIndex; 571 frameEvent.currentFrameIndex = currentFrameIndex; 572 this._frameEventQueue.push(frameEvent); 573 } 574 }, 575 576 /** 577 * Emits a movement event 578 * @param {ccs.Armature} armature 579 * @param {Number} movementType 580 * @param {String} movementID 581 */ 582 movementEvent: function (armature, movementType, movementID) { 583 if ((this._movementEventTarget && this._movementEventCallFunc) || this._movementEventListener) { 584 var event = new ccs.MovementEvent(); 585 event.armature = armature; 586 event.movementType = movementType; 587 event.movementID = movementID; 588 this._movementEventQueue.push(event); 589 } 590 }, 591 592 /** 593 * Updates movement list. 594 */ 595 updateMovementList: function () { 596 if (this._onMovementList) { 597 var movementObj, locMovementList = this._movementList; 598 if (this._movementListLoop) { 599 movementObj = locMovementList[this._movementIndex]; 600 this.play(movementObj, movementObj.durationTo, 0); 601 this._movementIndex++; 602 if (this._movementIndex >= locMovementList.length) 603 this._movementIndex = 0; 604 } else { 605 if (this._movementIndex < locMovementList.length) { 606 movementObj = locMovementList[this._movementIndex]; 607 this.play(movementObj, movementObj.durationTo, 0); 608 this._movementIndex++; 609 } else 610 this._onMovementList = false; 611 } 612 this._onMovementList = true; 613 } 614 }, 615 616 /** 617 * Sets animation data to animation. 618 * @param {ccs.AnimationData} data 619 */ 620 setAnimationData: function (data) { 621 if(this._animationData !== data) 622 this._animationData = data; 623 }, 624 625 /** 626 * Returns animation data of animation. 627 * @return {ccs.AnimationData} 628 */ 629 getAnimationData: function () { 630 return this._animationData; 631 }, 632 633 /** 634 * Returns the user object of animation. 635 * @return {Object} 636 */ 637 getUserObject: function () { 638 return this._userObject; 639 }, 640 641 /** 642 * Determines if the frame event is ignored 643 * @returns {boolean} 644 */ 645 isIgnoreFrameEvent: function () { 646 return this._ignoreFrameEvent; 647 } 648 }); 649 650 var _p = ccs.ArmatureAnimation.prototype; 651 652 // Extended properties 653 /** @expose */ 654 _p.speedScale; 655 cc.defineGetterSetter(_p, "speedScale", _p.getSpeedScale, _p.setSpeedScale); 656 /** @expose */ 657 _p.animationScale; 658 cc.defineGetterSetter(_p, "animationScale", _p.getAnimationScale, _p.setAnimationScale); 659 660 _p = null; 661 662 /** 663 * Allocates and initializes a ArmatureAnimation. 664 * @return {ccs.ArmatureAnimation} 665 * @deprecated since v3.1, please use new construction instead 666 */ 667 ccs.ArmatureAnimation.create = function (armature) { 668 return new ccs.ArmatureAnimation(armature); 669 }; 670