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