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