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 break; 210 } 211 } 212 213 if (locCurrentPercent < 1 && locLoopType < CC_ANIMATION_TYPE_TO_LOOP_BACK) { 214 locCurrentPercent = Math.sin(locCurrentPercent * cc.PI / 2); 215 } 216 217 this._currentPercent = locCurrentPercent; 218 this._loopType = locLoopType; 219 220 if (locLoopType > CC_ANIMATION_TYPE_TO_LOOP_BACK) { 221 locCurrentPercent = this.updateFrameData(locCurrentPercent); 222 } 223 if (this._frameTweenEasing != ccs.TweenType.tweenEasingMax) { 224 this.tweenNodeTo(locCurrentPercent); 225 } 226 }, 227 228 /** 229 * Calculate the between value of _from and _to, and give it to between frame data 230 * @param {ccs.FrameData} from 231 * @param {ccs.FrameData} to 232 * @param {Boolean} limit 233 */ 234 setBetween:function (from, to, limit) { 235 if (typeof limit == "undefined") { 236 limit = true; 237 } 238 do 239 { 240 if (from.displayIndex < 0 && to.displayIndex >= 0) { 241 this._from.copy(to); 242 this._between.subtract(to, to, limit); 243 break; 244 } 245 if (to.displayIndex < 0 && from.displayIndex >= 0) { 246 this._from.copy(from); 247 this._between.subtract(to, to, limit); 248 break; 249 } 250 this._from.copy(from); 251 this._between.subtract(from, to, limit); 252 } while (0); 253 if (!from.isTween){ 254 this._tweenData.copy(from); 255 this._tweenData.isTween = true; 256 } 257 this.arriveKeyFrame(from); 258 }, 259 260 /** 261 * Update display index and process the key frame event when arrived a key frame 262 * @param {ccs.FrameData} keyFrameData 263 */ 264 arriveKeyFrame:function (keyFrameData) { 265 if (keyFrameData) { 266 var locBone = this._bone; 267 var displayIndex = keyFrameData.displayIndex; 268 var displayManager = locBone.getDisplayManager(); 269 if (!displayManager.getForceChangeDisplay()) { 270 displayManager.changeDisplayWithIndex(displayIndex, false); 271 var locRenderNode = displayManager.getDisplayRenderNode(); 272 if(locRenderNode) 273 locRenderNode.setBlendFunc(keyFrameData.blendFunc); 274 } 275 this._tweenData.zOrder = keyFrameData.zOrder; 276 locBone.updateZOrder(); 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