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