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 * Base class for ccs.Tween objects. 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 ctor:function () { 48 ccs.ProcessBase.prototype.ctor.call(this); 49 this._tweenData = null; 50 this._to = null; 51 this._from = null; 52 this._between = null; 53 this._bone = null; 54 this._movementBoneData = null; 55 this._frameTweenEasing = ccs.TweenType.linear; 56 this._toIndex = 0; 57 this._fromIndex = 0; 58 this.animation = null; 59 this._passLastFrame = false; 60 }, 61 62 /** 63 * init with a CCBone 64 * @param {ccs.Bone} bone 65 * @return {Boolean} 66 */ 67 init:function (bone) { 68 this._from = new ccs.FrameData(); 69 this._between = new ccs.FrameData(); 70 71 this._bone = bone; 72 this._tweenData = this._bone.getTweenData(); 73 this._tweenData.displayIndex = -1; 74 var armature = bone.getArmature(); 75 if (armature) this.animation = armature.getAnimation(); 76 return true; 77 }, 78 79 /** 80 * Start the Process 81 * @param {ccs.MovementBoneData} movementBoneData 82 * @param {Number} durationTo 83 * @param {Number} durationTween 84 * @param {Boolean} loop 85 * @param {ccs.TweenType} tweenEasing 86 */ 87 play:function (movementBoneData, durationTo, durationTween, loop, tweenEasing) { 88 ccs.ProcessBase.prototype.play.call(this, durationTo, tweenEasing); 89 90 if(loop){ 91 this._loopType = ccs.ANIMATION_TYPE_TO_LOOP_FRONT; 92 }else{ 93 this._loopType = ccs.ANIMATION_TYPE_NO_LOOP; 94 } 95 96 this._totalDuration = 0; 97 this._betweenDuration = 0; 98 this._fromIndex = this._toIndex = 0; 99 100 var difMovement = movementBoneData != this._movementBoneData; 101 this._movementBoneData = movementBoneData; 102 this._rawDuration = this._movementBoneData.duration; 103 var nextKeyFrame = this._movementBoneData.getFrameData(0); 104 this._tweenData.displayIndex = nextKeyFrame.displayIndex; 105 106 if (this._bone.getArmature().getArmatureData().dataVersion >= ccs.CONST_VERSION_COMBINED) { 107 ccs.TransformHelp.nodeSub(this._tweenData, this._bone.getBoneData()); 108 this._tweenData.scaleX += 1; 109 this._tweenData.scaleY += 1; 110 } 111 112 if (this._rawDuration == 0 || this._movementBoneData.frameList.length == 1) { 113 this._loopType = ccs.ANIMATION_TYPE_SINGLE_FRAME; 114 if (durationTo == 0) { 115 this.setBetween(nextKeyFrame, nextKeyFrame); 116 } else { 117 this.setBetween(this._tweenData, nextKeyFrame); 118 } 119 this._frameTweenEasing = ccs.TweenType.linear; 120 } 121 else if (this._movementBoneData.frameList.length > 1) { 122 this._durationTween = durationTween * this._movementBoneData.scale; 123 if (loop && this._movementBoneData.delay != 0) { 124 this.setBetween(this._tweenData, this.tweenNodeTo(this.updateFrameData(1 - this._movementBoneData.delay), this._between)); 125 } 126 else { 127 if (!difMovement || durationTo == 0) 128 this.setBetween(nextKeyFrame, nextKeyFrame); 129 else 130 this.setBetween(this._tweenData, nextKeyFrame); 131 } 132 } 133 this.tweenNodeTo(0); 134 }, 135 136 gotoAndPlay: function (frameIndex) { 137 ccs.ProcessBase.prototype.gotoFrame.call(this, frameIndex); 138 this._totalDuration = 0; 139 this._betweenDuration = 0; 140 this._fromIndex = this._toIndex = 0; 141 this._isPlaying = true; 142 this._isComplete = this._isPause = false; 143 this._currentPercent = this._curFrameIndex / (this._rawDuration-1); 144 this._currentFrame = this._nextFrameIndex * this._currentPercent; 145 }, 146 147 gotoAndPause: function (frameIndex) { 148 this.gotoAndPlay(frameIndex); 149 this.pause(); 150 }, 151 152 /** 153 * update will call this handler, you can handle your logic here 154 */ 155 updateHandler:function () { 156 var locCurrentPercent = this._currentPercent; 157 var locLoopType = this._loopType; 158 if (locCurrentPercent >= 1) { 159 switch (locLoopType) { 160 case ccs.ANIMATION_TYPE_SINGLE_FRAME: 161 locCurrentPercent = 1; 162 this._isComplete = true; 163 this._isPlaying = false; 164 break; 165 case ccs.ANIMATION_TYPE_NO_LOOP: 166 locLoopType = ccs.ANIMATION_TYPE_MAX; 167 if (this._durationTween <= 0) { 168 locCurrentPercent = 1; 169 } 170 else { 171 locCurrentPercent = (locCurrentPercent - 1) * this._nextFrameIndex / this._durationTween; 172 } 173 if (locCurrentPercent >= 1) { 174 locCurrentPercent = 1; 175 this._isComplete = true; 176 this._isPlaying = false; 177 break; 178 } 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 if (this._movementBoneData.delay != 0) { 191 this._currentFrame = (1 - this._movementBoneData.delay) * this._nextFrameIndex; 192 locCurrentPercent = this._currentFrame / this._nextFrameIndex; 193 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 this._totalDuration = 0; 211 this._betweenDuration = 0; 212 break; 213 } 214 } 215 216 if (locCurrentPercent < 1 && locLoopType < ccs.ANIMATION_TYPE_TO_LOOP_BACK) { 217 locCurrentPercent = Math.sin(locCurrentPercent * cc.PI / 2); 218 } 219 220 this._currentPercent = locCurrentPercent; 221 this._loopType = locLoopType; 222 223 if (locLoopType > ccs.ANIMATION_TYPE_TO_LOOP_BACK) { 224 locCurrentPercent = this.updateFrameData(locCurrentPercent); 225 } 226 if (this._frameTweenEasing != ccs.TweenType.tweenEasingMax) { 227 this.tweenNodeTo(locCurrentPercent); 228 } 229 }, 230 231 /** 232 * Calculate the between value of _from and _to, and give it to between frame data 233 * @param {ccs.FrameData} from 234 * @param {ccs.FrameData} to 235 * @param {Boolean} limit 236 */ 237 setBetween:function (from, to, limit) { 238 if (typeof limit == "undefined") { 239 limit = true; 240 } 241 do 242 { 243 if (from.displayIndex < 0 && to.displayIndex >= 0) { 244 this._from.copy(to); 245 this._between.subtract(to, to, limit); 246 break; 247 } 248 if (to.displayIndex < 0 && from.displayIndex >= 0) { 249 this._from.copy(from); 250 this._between.subtract(to, to, limit); 251 break; 252 } 253 this._from.copy(from); 254 this._between.subtract(from, to, limit); 255 } while (0); 256 if (!from.isTween){ 257 this._tweenData.copy(from); 258 this._tweenData.isTween = true; 259 } 260 this.arriveKeyFrame(from); 261 }, 262 263 /** 264 * Update display index and process the key frame event when arrived a key frame 265 * @param {ccs.FrameData} keyFrameData 266 */ 267 arriveKeyFrame:function (keyFrameData) { 268 if (keyFrameData) { 269 var locBone = this._bone; 270 var displayIndex = keyFrameData.displayIndex; 271 var displayManager = locBone.getDisplayManager(); 272 if (!displayManager.getForceChangeDisplay()) { 273 displayManager.changeDisplayWithIndex(displayIndex, false); 274 var locRenderNode = displayManager.getDisplayRenderNode(); 275 if(locRenderNode) 276 locRenderNode.setBlendFunc(keyFrameData.blendFunc); 277 } 278 this._tweenData.zOrder = keyFrameData.zOrder; 279 locBone.updateZOrder(); 280 var childAramture = locBone.getChildArmature(); 281 if (childAramture) { 282 if (keyFrameData.movement != "") { 283 childAramture.getAnimation().play(keyFrameData.movement); 284 } 285 } 286 } 287 }, 288 289 /** 290 * According to the percent to calculate current CCFrameData with tween effect 291 * @param {Number} percent 292 * @param {ccs.FrameData} node 293 * @return {ccs.FrameData} 294 */ 295 tweenNodeTo:function (percent, node) { 296 if (!node) { 297 node = this._tweenData; 298 } 299 var locFrom = this._from; 300 var locBetween = this._between; 301 if (!locFrom.isTween){ 302 percent = 0; 303 } 304 node.x = locFrom.x + percent * locBetween.x; 305 node.y = locFrom.y + percent * locBetween.y; 306 node.scaleX = locFrom.scaleX + percent * locBetween.scaleX; 307 node.scaleY = locFrom.scaleY + percent * locBetween.scaleY; 308 node.skewX = locFrom.skewX + percent * locBetween.skewX; 309 node.skewY = locFrom.skewY + percent * locBetween.skewY; 310 311 this._bone.setTransformDirty(true); 312 if (node && locBetween.isUseColorInfo) 313 this.tweenColorTo(percent, node); 314 315 return node; 316 }, 317 318 tweenColorTo:function(percent,node){ 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) { 334 if (currentPercent > 1 && this._movementBoneData.delay != 0) { 335 currentPercent = ccs.fmodf(currentPercent,1); 336 } 337 var playedTime = (this._rawDuration-1) * currentPercent; 338 var from, to; 339 var locTotalDuration = this._totalDuration,locBetweenDuration = this._betweenDuration, locToIndex = this._toIndex; 340 // if play to current frame's front or back, then find current frame again 341 if (playedTime < locTotalDuration || playedTime >= locTotalDuration + locBetweenDuration) { 342 /* 343 * get frame length, if this._toIndex >= _length, then set this._toIndex to 0, start anew. 344 * this._toIndex is next index will play 345 */ 346 var length = this._movementBoneData.frameList.length; 347 var frames = this._movementBoneData.frameList; 348 if (playedTime < frames[0].frameID){ 349 from = to = frames[0]; 350 this.setBetween(from, to); 351 return currentPercent; 352 } 353 else if (playedTime >= frames[length - 1].frameID) { 354 if (this._passLastFrame) { 355 from = to = frames[length - 1]; 356 this.setBetween(from, to); 357 return currentPercent; 358 } 359 this._passLastFrame = true; 360 } else { 361 this._passLastFrame = false; 362 } 363 364 do { 365 this._fromIndex = locToIndex; 366 from = frames[this._fromIndex]; 367 locTotalDuration = from.frameID; 368 locToIndex = this._fromIndex + 1; 369 if (locToIndex >= length) { 370 locToIndex = 0; 371 } 372 to = frames[locToIndex]; 373 374 //! Guaranteed to trigger frame event 375 if(from.event&& !this.animation.isIgnoreFrameEvent()){ 376 this.animation.frameEvent(this._bone, from.event,from.frameID, playedTime); 377 } 378 379 if (playedTime == from.frameID|| (this._passLastFrame && this._fromIndex == length-1)){ 380 break; 381 } 382 } 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 this._totalDuration = locTotalDuration; 389 this._betweenDuration = locBetweenDuration; 390 this._toIndex = locToIndex; 391 } 392 393 currentPercent = locBetweenDuration == 0 ? 0 : (playedTime - locTotalDuration) / locBetweenDuration; 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 * animation setter 407 * @param {ccs.ArmatureAnimation} animation 408 */ 409 setAnimation:function (animation) { 410 this.animation = animation; 411 }, 412 413 /** 414 * animation getter 415 * @return {ccs.ArmatureAnimation} 416 */ 417 getAnimation:function () { 418 return this.animation; 419 }, 420 421 release:function () { 422 this._from = null; 423 this._between = null; 424 } 425 }); 426 427 /** 428 * allocates and initializes a ArmatureAnimation. 429 * @constructs 430 * @param {ccs.Bone} bone 431 * @return {ccs.ArmatureAnimation} 432 * @example 433 * // example 434 * var animation = ccs.ArmatureAnimation.create(); 435 */ 436 ccs.Tween.create = function (bone) { 437 var tween = new ccs.Tween(); 438 if (tween && tween.init(bone)) { 439 return tween; 440 } 441 return null; 442 }; 443