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 * The tween class for Armature. 28 * @class 29 * @extends ccs.ProcessBase 30 * 31 * @param {ccs.Bone} The bone to be animated 32 * 33 * @property {ccs.ArmatureAnimation} animation - The animation 34 */ 35 ccs.Tween = ccs.ProcessBase.extend(/** @lends ccs.Tween# */{ 36 _tweenData:null, 37 _to:null, 38 _from:null, 39 _between:null, 40 _movementBoneData:null, 41 _bone:null, 42 _frameTweenEasing:0, 43 _betweenDuration:0, 44 _totalDuration:0, 45 _toIndex:0, 46 _fromIndex:0, 47 _animation:null, 48 _passLastFrame:false, 49 50 ctor:function (bone) { 51 ccs.ProcessBase.prototype.ctor.call(this); 52 this._frameTweenEasing = ccs.TweenType.LINEAR; 53 54 ccs.Tween.prototype.init.call(this, bone); 55 }, 56 57 /** 58 * initializes a ccs.Tween with a CCBone 59 * @param {ccs.Bone} bone 60 * @return {Boolean} 61 */ 62 init:function (bone) { 63 this._from = new ccs.FrameData(); 64 this._between = new ccs.FrameData(); 65 66 this._bone = bone; 67 this._tweenData = this._bone.getTweenData(); 68 this._tweenData.displayIndex = -1; 69 70 this._animation = (this._bone !== null && this._bone.getArmature() !== null) ? 71 this._bone.getArmature().getAnimation() : 72 null; 73 return true; 74 }, 75 76 /** 77 * Plays the tween. 78 * @param {ccs.MovementBoneData} movementBoneData 79 * @param {Number} durationTo 80 * @param {Number} durationTween 81 * @param {Boolean} loop 82 * @param {ccs.TweenType} tweenEasing 83 */ 84 play:function (movementBoneData, durationTo, durationTween, loop, tweenEasing) { 85 ccs.ProcessBase.prototype.play.call(this, durationTo, durationTween, loop, tweenEasing); 86 this._loopType = (loop)?ccs.ANIMATION_TYPE_TO_LOOP_FRONT:ccs.ANIMATION_TYPE_NO_LOOP; 87 88 this._totalDuration = 0; 89 this._betweenDuration = 0; 90 this._fromIndex = this._toIndex = 0; 91 92 var difMovement = movementBoneData !== this._movementBoneData; 93 94 this.setMovementBoneData(movementBoneData); 95 this._rawDuration = this._movementBoneData.duration; 96 97 var nextKeyFrame = this._movementBoneData.getFrameData(0); 98 this._tweenData.displayIndex = nextKeyFrame.displayIndex; 99 100 if (this._bone.getArmature().getArmatureData().dataVersion >= ccs.CONST_VERSION_COMBINED) { 101 ccs.TransformHelp.nodeSub(this._tweenData, this._bone.getBoneData()); 102 this._tweenData.scaleX += 1; 103 this._tweenData.scaleY += 1; 104 } 105 106 if (this._rawDuration === 0) { 107 this._loopType = ccs.ANIMATION_TYPE_SINGLE_FRAME; 108 if (durationTo === 0) 109 this.setBetween(nextKeyFrame, nextKeyFrame); 110 else 111 this.setBetween(this._tweenData, nextKeyFrame); 112 this._frameTweenEasing = ccs.TweenType.LINEAR; 113 } 114 else if (this._movementBoneData.frameList.length > 1) { 115 this._durationTween = durationTween * this._movementBoneData.scale; 116 if (loop && this._movementBoneData.delay !== 0) 117 this.setBetween(this._tweenData, this.tweenNodeTo(this.updateFrameData(1 - this._movementBoneData.delay), this._between)); 118 else { 119 if (!difMovement || durationTo === 0) 120 this.setBetween(nextKeyFrame, nextKeyFrame); 121 else 122 this.setBetween(this._tweenData, nextKeyFrame); 123 } 124 } 125 this.tweenNodeTo(0); 126 }, 127 128 /** 129 * Goes to specified frame and plays frame. 130 * @param {Number} frameIndex 131 */ 132 gotoAndPlay: function (frameIndex) { 133 ccs.ProcessBase.prototype.gotoFrame.call(this, frameIndex); 134 135 this._totalDuration = 0; 136 this._betweenDuration = 0; 137 this._fromIndex = this._toIndex = 0; 138 139 this._isPlaying = true; 140 this._isComplete = this._isPause = false; 141 142 this._currentPercent = this._curFrameIndex / (this._rawDuration-1); 143 this._currentFrame = this._nextFrameIndex * this._currentPercent; 144 }, 145 146 /** 147 * Goes to specified frame and pauses frame. 148 * @param {Number} frameIndex 149 */ 150 gotoAndPause: function (frameIndex) { 151 this.gotoAndPlay(frameIndex); 152 this.pause(); 153 }, 154 155 /** 156 * update will call this handler, you can handle your logic here 157 */ 158 updateHandler:function () { 159 var locCurrentPercent = this._currentPercent == null ? 1 : this._currentPercent; 160 var locLoopType = this._loopType; 161 if (locCurrentPercent >= 1) { 162 switch (locLoopType) { 163 case ccs.ANIMATION_TYPE_SINGLE_FRAME: 164 locCurrentPercent = 1; 165 this._isComplete = true; 166 this._isPlaying = false; 167 break; 168 case ccs.ANIMATION_TYPE_NO_LOOP: 169 locLoopType = ccs.ANIMATION_TYPE_MAX; 170 if (this._durationTween <= 0) 171 locCurrentPercent = 1; 172 else 173 locCurrentPercent = (locCurrentPercent - 1) * this._nextFrameIndex / this._durationTween; 174 if (locCurrentPercent >= 1) { 175 locCurrentPercent = 1; 176 this._isComplete = true; 177 this._isPlaying = false; 178 break; 179 } else { 180 this._nextFrameIndex = this._durationTween; 181 this._currentFrame = locCurrentPercent * this._nextFrameIndex; 182 this._totalDuration = 0; 183 this._betweenDuration = 0; 184 this._fromIndex = this._toIndex = 0; 185 break; 186 } 187 case ccs.ANIMATION_TYPE_TO_LOOP_FRONT: 188 locLoopType = ccs.ANIMATION_TYPE_LOOP_FRONT; 189 this._nextFrameIndex = this._durationTween > 0 ? this._durationTween : 1; 190 191 if (this._movementBoneData.delay !== 0) { 192 this._currentFrame = (1 - this._movementBoneData.delay) * this._nextFrameIndex; 193 locCurrentPercent = this._currentFrame / this._nextFrameIndex; 194 } else { 195 locCurrentPercent = 0; 196 this._currentFrame = 0; 197 } 198 199 this._totalDuration = 0; 200 this._betweenDuration = 0; 201 this._fromIndex = this._toIndex = 0; 202 break; 203 case ccs.ANIMATION_TYPE_MAX: 204 locCurrentPercent = 1; 205 this._isComplete = true; 206 this._isPlaying = false; 207 break; 208 default: 209 this._currentFrame = ccs.fmodf(this._currentFrame, this._nextFrameIndex); 210 break; 211 } 212 } 213 214 if (locCurrentPercent < 1 && locLoopType < ccs.ANIMATION_TYPE_TO_LOOP_BACK) 215 locCurrentPercent = Math.sin(locCurrentPercent * cc.PI / 2); 216 217 this._currentPercent = locCurrentPercent; 218 this._loopType = locLoopType; 219 220 if (locLoopType > ccs.ANIMATION_TYPE_TO_LOOP_BACK) 221 locCurrentPercent = this.updateFrameData(locCurrentPercent); 222 if (this._frameTweenEasing !== ccs.TweenType.TWEEN_EASING_MAX) 223 this.tweenNodeTo(locCurrentPercent); 224 }, 225 226 /** 227 * Calculate the between value of _from and _to, and give it to between frame data 228 * @param {ccs.FrameData} from 229 * @param {ccs.FrameData} to 230 * @param {Boolean} [limit=true] 231 */ 232 setBetween:function (from, to, limit) { //TODO set tweenColorTo to protected in v3.1 233 if(limit === undefined) 234 limit = true; 235 do { 236 if (from.displayIndex < 0 && to.displayIndex >= 0) { 237 this._from.copy(to); 238 this._between.subtract(to, to, limit); 239 break; 240 } 241 if (to.displayIndex < 0 && from.displayIndex >= 0) { 242 this._from.copy(from); 243 this._between.subtract(to, to, limit); 244 break; 245 } 246 this._from.copy(from); 247 this._between.subtract(from, to, limit); 248 } while (0); 249 if (!from.isTween){ 250 this._tweenData.copy(from); 251 this._tweenData.isTween = true; 252 } 253 this.arriveKeyFrame(from); 254 }, 255 256 /** 257 * Update display index and process the key frame event when arrived a key frame 258 * @param {ccs.FrameData} keyFrameData 259 */ 260 arriveKeyFrame:function (keyFrameData) { //TODO set tweenColorTo to protected in v3.1 261 if (keyFrameData) { 262 var locBone = this._bone; 263 var displayManager = locBone.getDisplayManager(); 264 265 //! Change bone's display 266 var displayIndex = keyFrameData.displayIndex; 267 268 if (!displayManager.getForceChangeDisplay()) 269 displayManager.changeDisplayWithIndex(displayIndex, false); 270 271 //! Update bone zorder, bone's zorder is determined by frame zorder and bone zorder 272 this._tweenData.zOrder = keyFrameData.zOrder; 273 locBone.updateZOrder(); 274 275 //! Update blend type 276 this._bone.setBlendFunc(keyFrameData.blendFunc); 277 278 var childAramture = locBone.getChildArmature(); 279 if (childAramture) { 280 if (keyFrameData.movement !== "") 281 childAramture.getAnimation().play(keyFrameData.movement); 282 } 283 } 284 }, 285 286 /** 287 * According to the percent to calculate current CCFrameData with tween effect 288 * @param {Number} percent 289 * @param {ccs.FrameData} [node] 290 * @return {ccs.FrameData} 291 */ 292 tweenNodeTo:function (percent, node) { //TODO set tweenColorTo to protected in v3.1 293 if (!node) 294 node = this._tweenData; 295 296 var locFrom = this._from; 297 var locBetween = this._between; 298 if (!locFrom.isTween) 299 percent = 0; 300 node.x = locFrom.x + percent * locBetween.x; 301 node.y = locFrom.y + percent * locBetween.y; 302 node.scaleX = locFrom.scaleX + percent * locBetween.scaleX; 303 node.scaleY = locFrom.scaleY + percent * locBetween.scaleY; 304 node.skewX = locFrom.skewX + percent * locBetween.skewX; 305 node.skewY = locFrom.skewY + percent * locBetween.skewY; 306 307 this._bone.setTransformDirty(true); 308 if (node && locBetween.isUseColorInfo) 309 this.tweenColorTo(percent, node); 310 311 return node; 312 }, 313 314 /** 315 * According to the percent to calculate current color with tween effect 316 * @param {Number} percent 317 * @param {ccs.FrameData} node 318 */ 319 tweenColorTo:function(percent,node){ //TODO set tweenColorTo to protected in v3.1 320 var locFrom = this._from; 321 var locBetween = this._between; 322 node.a = locFrom.a + percent * locBetween.a; 323 node.r = locFrom.r + percent * locBetween.r; 324 node.g = locFrom.g + percent * locBetween.g; 325 node.b = locFrom.b + percent * locBetween.b; 326 this._bone.updateColor(); 327 }, 328 329 /** 330 * Calculate which frame arrived, and if current frame have event, then call the event listener 331 * @param {Number} currentPercent 332 * @return {Number} 333 */ 334 updateFrameData:function (currentPercent) { //TODO set tweenColorTo to protected in v3.1 335 if (currentPercent > 1 && this._movementBoneData.delay !== 0) 336 currentPercent = ccs.fmodf(currentPercent,1); 337 338 var playedTime = (this._rawDuration-1) * currentPercent; 339 340 var from, to; 341 var locTotalDuration = this._totalDuration,locBetweenDuration = this._betweenDuration, locToIndex = this._toIndex; 342 // if play to current frame's front or back, then find current frame again 343 if (playedTime < locTotalDuration || playedTime >= locTotalDuration + locBetweenDuration) { 344 /* 345 * get frame length, if this._toIndex >= _length, then set this._toIndex to 0, start anew. 346 * this._toIndex is next index will play 347 */ 348 var frames = this._movementBoneData.frameList; 349 var length = frames.length; 350 351 if (playedTime < frames[0].frameID){ 352 from = to = frames[0]; 353 this.setBetween(from, to); 354 return this._currentPercent; 355 } 356 357 if (playedTime >= frames[length - 1].frameID) { 358 // If _passLastFrame is true and playedTime >= frames[length - 1]->frameID, then do not need to go on. 359 if (this._passLastFrame) { 360 from = to = frames[length - 1]; 361 this.setBetween(from, to); 362 return this._currentPercent; 363 } 364 this._passLastFrame = true; 365 } else 366 this._passLastFrame = false; 367 368 do { 369 this._fromIndex = locToIndex; 370 from = frames[this._fromIndex]; 371 locTotalDuration = from.frameID; 372 373 locToIndex = this._fromIndex + 1; 374 if (locToIndex >= length) 375 locToIndex = 0; 376 to = frames[locToIndex]; 377 378 //! Guaranteed to trigger frame event 379 if(from.strEvent && !this._animation.isIgnoreFrameEvent()) 380 this._animation.frameEvent(this._bone, from.strEvent,from.frameID, playedTime); 381 382 if (playedTime === from.frameID|| (this._passLastFrame && this._fromIndex === length-1)) 383 break; 384 } while (playedTime < from.frameID || playedTime >= to.frameID); 385 386 locBetweenDuration = to.frameID - from.frameID; 387 this._frameTweenEasing = from.tweenEasing; 388 this.setBetween(from, to, false); 389 390 this._totalDuration = locTotalDuration; 391 this._betweenDuration = locBetweenDuration; 392 this._toIndex = locToIndex; 393 } 394 currentPercent = locBetweenDuration === 0 ? 0 : (playedTime - this._totalDuration) / this._betweenDuration; 395 396 /* 397 * if frame tween easing equal to TWEEN_EASING_MAX, then it will not do tween. 398 */ 399 var tweenType = (this._frameTweenEasing !== ccs.TweenType.LINEAR) ? this._frameTweenEasing : this._tweenEasing; 400 if (tweenType !== ccs.TweenType.TWEEN_EASING_MAX && tweenType !== ccs.TweenType.LINEAR && !this._passLastFrame) { 401 currentPercent = ccs.TweenFunction.tweenTo(currentPercent, tweenType, this._from.easingParams); 402 } 403 return currentPercent; 404 }, 405 406 /** 407 * Sets Armature animation to ccs.Tween. 408 * @param {ccs.ArmatureAnimation} animation 409 */ 410 setAnimation:function (animation) { 411 this._animation = animation; 412 }, 413 414 /** 415 * Returns Armature animation of ccs.Tween. 416 * @return {ccs.ArmatureAnimation} 417 */ 418 getAnimation:function () { 419 return this._animation; 420 }, 421 422 /** 423 * Sets movement bone data to ccs.Tween. 424 * @param data 425 */ 426 setMovementBoneData: function(data){ 427 this._movementBoneData = data; 428 } 429 }); 430 431 var _p = ccs.Tween.prototype; 432 433 // Extended properties 434 /** @expose */ 435 _p.animation; 436 cc.defineGetterSetter(_p, "animation", _p.getAnimation, _p.setAnimation); 437 438 _p = null; 439 440 /** 441 * Allocates and initializes a ArmatureAnimation. 442 * @param {ccs.Bone} bone 443 * @return {ccs.Tween} 444 * @deprecated since v3.1, please use new construction instead 445 */ 446 ccs.Tween.create = function (bone) { 447 return new ccs.Tween(bone); 448 }; 449