1 /**************************************************************************** 2 Copyright (c) 2010-2012 cocos2d-x.org 3 4 http://www.cocos2d-x.org 5 6 Permission is hereby granted, free of charge, to any person obtaining a copy 7 of this software and associated documentation files (the "Software"), to deal 8 in the Software without restriction, including without limitation the rights 9 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 copies of the Software, and to permit persons to whom the Software is 11 furnished to do so, subject to the following conditions: 12 13 The above copyright notice and this permission notice shall be included in 14 all copies or substantial portions of the Software. 15 16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 THE SOFTWARE. 23 ****************************************************************************/ 24 25 /** 26 * Base class for ccs.Tween objects. 27 * @class 28 * @extends ccs.ProcessBase 29 */ 30 ccs.Tween = ccs.ProcessBase.extend(/** @lends ccs.Tween# */{ 31 _tweenData:null, 32 _to:null, 33 _from:null, 34 _between:null, 35 _movementBoneData:null, 36 _bone:null, 37 _frameTweenEasing:0, 38 _betweenDuration:0, 39 _totalDuration:0, 40 _toIndex:0, 41 _fromIndex:0, 42 _animation:null, 43 _passLastFrame:false, 44 ctor:function () { 45 ccs.ProcessBase.prototype.ctor.call(this); 46 this._tweenData = null; 47 this._to = null; 48 this._from = null; 49 this._between = null; 50 this._bone = null; 51 this._movementBoneData = null; 52 this._frameTweenEasing = ccs.TweenType.linear; 53 this._toIndex = 0; 54 this._fromIndex = 0; 55 this._animation = null; 56 this._passLastFrame = false; 57 }, 58 59 /** 60 * init with a CCBone 61 * @param {ccs.Bone} bone 62 * @return {Boolean} 63 */ 64 init:function (bone) { 65 this._from = new ccs.FrameData(); 66 this._between = new ccs.FrameData(); 67 68 this._bone = bone; 69 this._tweenData = this._bone.getTweenData(); 70 this._tweenData.displayIndex = -1; 71 var armature = bone.getArmature(); 72 if (armature) this._animation = armature.getAnimation(); 73 return true; 74 }, 75 76 /** 77 * Start the Process 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, tweenEasing); 86 87 if(loop){ 88 this._loopType = CC_ANIMATION_TYPE_TO_LOOP_FRONT; 89 }else{ 90 this._loopType = CC_ANIMATION_TYPE_NO_LOOP; 91 } 92 93 this._totalDuration = 0; 94 this._betweenDuration = 0; 95 this._fromIndex = this._toIndex = 0; 96 97 var difMovement = movementBoneData != this._movementBoneData; 98 this._movementBoneData = movementBoneData; 99 this._rawDuration = this._movementBoneData.duration; 100 var nextKeyFrame = this._movementBoneData.getFrameData(0); 101 this._tweenData.displayIndex = nextKeyFrame.displayIndex; 102 103 if (this._bone.getArmature().getArmatureData().dataVersion >= ccs.CONST_VERSION_COMBINED) { 104 ccs.TransformHelp.nodeSub(this._tweenData, this._bone.getBoneData()); 105 this._tweenData.scaleX += 1; 106 this._tweenData.scaleY += 1; 107 } 108 109 if (this._rawDuration == 0 || this._movementBoneData.frameList.length == 1) { 110 this._loopType = CC_ANIMATION_TYPE_SINGLE_FRAME; 111 if (durationTo == 0) { 112 this.setBetween(nextKeyFrame, nextKeyFrame); 113 } else { 114 this.setBetween(this._tweenData, nextKeyFrame); 115 } 116 this._frameTweenEasing = ccs.TweenType.linear; 117 } 118 else if (this._movementBoneData.frameList.length > 1) { 119 this._durationTween = durationTween * this._movementBoneData.scale; 120 if (loop && this._movementBoneData.delay != 0) { 121 this.setBetween(this._tweenData, this.tweenNodeTo(this.updateFrameData(1 - this._movementBoneData.delay), this._between)); 122 } 123 else { 124 if (!difMovement || durationTo == 0) 125 this.setBetween(nextKeyFrame, nextKeyFrame); 126 else 127 this.setBetween(this._tweenData, nextKeyFrame); 128 } 129 } 130 this.tweenNodeTo(0); 131 }, 132 133 gotoAndPlay: function (frameIndex) { 134 ccs.ProcessBase.prototype.gotoFrame.call(this, frameIndex); 135 this._totalDuration = 0; 136 this._betweenDuration = 0; 137 this._fromIndex = this._toIndex = 0; 138 this._isPlaying = true; 139 this._isComplete = this._isPause = false; 140 this._currentPercent = this._curFrameIndex / (this._rawDuration-1); 141 this._currentFrame = this._nextFrameIndex * this._currentPercent; 142 }, 143 144 gotoAndPause: function (frameIndex) { 145 this.gotoAndPlay(frameIndex); 146 this.pause(); 147 }, 148 149 /** 150 * update will call this handler, you can handle your logic here 151 */ 152 updateHandler:function () { 153 var locCurrentPercent = this._currentPercent; 154 var locLoopType = this._loopType; 155 if (locCurrentPercent >= 1) { 156 switch (locLoopType) { 157 case CC_ANIMATION_TYPE_SINGLE_FRAME: 158 locCurrentPercent = 1; 159 this._isComplete = true; 160 this._isPlaying = false; 161 break; 162 case CC_ANIMATION_TYPE_NO_LOOP: 163 locLoopType = CC_ANIMATION_TYPE_MAX; 164 if (this._durationTween <= 0) { 165 locCurrentPercent = 1; 166 } 167 else { 168 locCurrentPercent = (locCurrentPercent - 1) * this._nextFrameIndex / this._durationTween; 169 } 170 if (locCurrentPercent >= 1) { 171 locCurrentPercent = 1; 172 this._isComplete = true; 173 this._isPlaying = false; 174 break; 175 } 176 else { 177 this._nextFrameIndex = this._durationTween; 178 this._currentFrame = locCurrentPercent * this._nextFrameIndex; 179 this._totalDuration = 0; 180 this._betweenDuration = 0; 181 this._fromIndex = this._toIndex = 0; 182 break; 183 } 184 case CC_ANIMATION_TYPE_TO_LOOP_FRONT: 185 locLoopType = CC_ANIMATION_TYPE_LOOP_FRONT; 186 this._nextFrameIndex = this._durationTween > 0 ? this._durationTween : 1; 187 if (this._movementBoneData.delay != 0) { 188 this._currentFrame = (1 - this._movementBoneData.delay) * this._nextFrameIndex; 189 locCurrentPercent = this._currentFrame / this._nextFrameIndex; 190 191 } else { 192 locCurrentPercent = 0; 193 this._currentFrame = 0; 194 } 195 196 this._totalDuration = 0; 197 this._betweenDuration = 0; 198 this._fromIndex = this._toIndex = 0; 199 break; 200 case CC_ANIMATION_TYPE_MAX: 201 locCurrentPercent = 1; 202 this._isComplete = true; 203 this._isPlaying = false; 204 break; 205 default: 206 this._currentFrame = ccs.fmodf(this._currentFrame, this._nextFrameIndex); 207 this._totalDuration = 0; 208 this._betweenDuration = 0; 209 this._fromIndex = this._toIndex = 0; 210 break; 211 } 212 } 213 214 if (locCurrentPercent < 1 && locLoopType < CC_ANIMATION_TYPE_TO_LOOP_BACK) { 215 locCurrentPercent = Math.sin(locCurrentPercent * cc.PI / 2); 216 } 217 218 this._currentPercent = locCurrentPercent; 219 this._loopType = locLoopType; 220 221 if (locLoopType > CC_ANIMATION_TYPE_TO_LOOP_BACK) { 222 locCurrentPercent = this.updateFrameData(locCurrentPercent); 223 } 224 if (this._frameTweenEasing != ccs.TweenType.tweenEasingMax) { 225 this.tweenNodeTo(locCurrentPercent); 226 } 227 }, 228 229 /** 230 * Calculate the between value of _from and _to, and give it to between frame data 231 * @param {ccs.FrameData} from 232 * @param {ccs.FrameData} to 233 * @param {Boolean} limit 234 */ 235 setBetween:function (from, to, limit) { 236 if (typeof limit == "undefined") { 237 limit = true; 238 } 239 do 240 { 241 if (from.displayIndex < 0 && to.displayIndex >= 0) { 242 this._from.copy(to); 243 this._between.subtract(to, to, limit); 244 break; 245 } 246 if (to.displayIndex < 0 && from.displayIndex >= 0) { 247 this._from.copy(from); 248 this._between.subtract(to, to, limit); 249 break; 250 } 251 this._from.copy(from); 252 this._between.subtract(from, to, limit); 253 } while (0); 254 if (!from.isTween){ 255 this._tweenData.copy(from); 256 this._tweenData.isTween = true; 257 } 258 this.arriveKeyFrame(from); 259 }, 260 261 /** 262 * Update display index and process the key frame event when arrived a key frame 263 * @param {ccs.FrameData} keyFrameData 264 */ 265 arriveKeyFrame:function (keyFrameData) { 266 if (keyFrameData) { 267 var locBone = this._bone; 268 var displayIndex = keyFrameData.displayIndex; 269 var displayManager = locBone.getDisplayManager(); 270 if (!displayManager.getForceChangeDisplay()) { 271 displayManager.changeDisplayWithIndex(displayIndex, false); 272 273 } 274 this._tweenData.zOrder = keyFrameData.zOrder; 275 locBone.updateZOrder(); 276 locBone.setBlendFunc(keyFrameData.blendFunc); 277 var childAramture = locBone.getChildArmature(); 278 if (childAramture) { 279 if (keyFrameData.movement != "") { 280 childAramture.getAnimation().play(keyFrameData.movement); 281 } 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) { 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 } 301 node.x = locFrom.x + percent * locBetween.x; 302 node.y = locFrom.y + percent * locBetween.y; 303 node.scaleX = locFrom.scaleX + percent * locBetween.scaleX; 304 node.scaleY = locFrom.scaleY + percent * locBetween.scaleY; 305 node.skewX = locFrom.skewX + percent * locBetween.skewX; 306 node.skewY = locFrom.skewY + percent * locBetween.skewY; 307 308 this._bone.setTransformDirty(true); 309 if (node && locBetween.isUseColorInfo) 310 this.tweenColorTo(percent, node); 311 312 return node; 313 }, 314 315 tweenColorTo:function(percent,node){ 316 var locFrom = this._from; 317 var locBetween = this._between; 318 node.a = locFrom.a + percent * locBetween.a; 319 node.r = locFrom.r + percent * locBetween.r; 320 node.g = locFrom.g + percent * locBetween.g; 321 node.b = locFrom.b + percent * locBetween.b; 322 this._bone.updateColor(); 323 }, 324 325 /** 326 * Calculate which frame arrived, and if current frame have event, then call the event listener 327 * @param {Number} currentPercent 328 * @return {Number} 329 */ 330 updateFrameData:function (currentPercent) { 331 if (currentPercent > 1 && this._movementBoneData.delay != 0) { 332 currentPercent = ccs.fmodf(currentPercent,1); 333 } 334 var playedTime = (this._rawDuration-1) * currentPercent; 335 var from, to; 336 var locTotalDuration = this._totalDuration,locBetweenDuration = this._betweenDuration, locToIndex = this._toIndex; 337 // if play to current frame's front or back, then find current frame again 338 if (playedTime < locTotalDuration || playedTime >= locTotalDuration + locBetweenDuration) { 339 /* 340 * get frame length, if this._toIndex >= _length, then set this._toIndex to 0, start anew. 341 * this._toIndex is next index will play 342 */ 343 var length = this._movementBoneData.frameList.length; 344 var frames = this._movementBoneData.frameList; 345 if (playedTime < frames[0].frameID){ 346 from = to = frames[0]; 347 this.setBetween(from, to); 348 return currentPercent; 349 } 350 else if (playedTime >= frames[length - 1].frameID) { 351 if (this._passLastFrame) { 352 from = to = frames[length - 1]; 353 this.setBetween(from, to); 354 return currentPercent; 355 } 356 this._passLastFrame = true; 357 } else { 358 this._passLastFrame = false; 359 } 360 361 do { 362 this._fromIndex = locToIndex; 363 from = frames[this._fromIndex]; 364 locTotalDuration = from.frameID; 365 locToIndex = this._fromIndex + 1; 366 if (locToIndex >= length) { 367 locToIndex = 0; 368 } 369 to = frames[locToIndex]; 370 371 //! Guaranteed to trigger frame event 372 if(from.event&& !this._animation.isIgnoreFrameEvent()){ 373 this._animation.frameEvent(this._bone, from.event,from.frameID, playedTime); 374 } 375 376 if (playedTime == from.frameID|| (this._passLastFrame && this._fromIndex == length-1)){ 377 break; 378 } 379 } 380 while (playedTime < from.frameID || playedTime >= to.frameID); 381 382 locBetweenDuration = to.frameID - from.frameID; 383 this._frameTweenEasing = from.tweenEasing; 384 this.setBetween(from, to, false); 385 this._totalDuration = locTotalDuration; 386 this._betweenDuration = locBetweenDuration; 387 this._toIndex = locToIndex; 388 } 389 390 currentPercent = locBetweenDuration == 0 ? 0 : (playedTime - locTotalDuration) / locBetweenDuration; 391 392 /* 393 * if frame tween easing equal to TWEEN_EASING_MAX, then it will not do tween. 394 */ 395 var tweenType = (this._frameTweenEasing != ccs.TweenType.linear) ? this._frameTweenEasing : this._tweenEasing; 396 if (tweenType != ccs.TweenType.tweenEasingMax&&tweenType != ccs.TweenType.linear&& !this._passLastFrame) { 397 currentPercent = ccs.TweenFunction.tweenTo(currentPercent, tweenType, this._from.easingParams); 398 } 399 return currentPercent; 400 }, 401 402 /** 403 * animation setter 404 * @param {ccs.ArmatureAnimation} animation 405 */ 406 setAnimation:function (animation) { 407 this._animation = animation; 408 }, 409 410 /** 411 * animation getter 412 * @return {ccs.ArmatureAnimation} 413 */ 414 getAnimation:function () { 415 return this._animation; 416 }, 417 418 release:function () { 419 this._from = null; 420 this._between = null; 421 } 422 }); 423 424 /** 425 * allocates and initializes a ArmatureAnimation. 426 * @constructs 427 * @param {ccs.Bone} bone 428 * @return {ccs.ArmatureAnimation} 429 * @example 430 * // example 431 * var animation = ccs.ArmatureAnimation.create(); 432 */ 433 ccs.Tween.create = function (bone) { 434 var tween = new ccs.Tween(); 435 if (tween && tween.init(bone)) { 436 return tween; 437 } 438 return null; 439 }; 440