/**************************************************************************** Copyright (c) 2008-2010 Ricardo Quesada Copyright (c) 2011-2012 cocos2d-x.org Copyright (c) 2013-2014 Chukong Technologies Inc. http://www.cocos2d-x.org Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ (function () { var _pos = cc.p(); cc.BuilderAnimationManagerDelegate = cc.Class.extend({ completedAnimationSequenceNamed: function (name) { } }); cc.BuilderAnimationManager = cc.Class.extend({ _sequences: null, _nodeSequences: null, _baseValues: null, _autoPlaySequenceId: 0, _rootNode: null, _owner: null, _rootContainerSize: null, _delegate: null, _runningSequence: null, _documentOutletNames: null, _documentOutletNodes: null, _documentCallbackNames: null, _documentCallbackNodes: null, _documentCallbackControlEvents: null, _documentControllerName: "", _lastCompletedSequenceName: "", _keyframeCallbacks: null, _keyframeCallFuncs: null, _animationCompleteCallbackFunc: null, _target: null, _jsControlled: false, ctor: function () { this._rootContainerSize = cc.size(0, 0); this.init(); }, init: function () { this._sequences = []; this._nodeSequences = new cc._Dictionary(); this._baseValues = new cc._Dictionary(); this._documentOutletNames = []; this._documentOutletNodes = []; this._documentCallbackNames = []; this._documentCallbackNodes = []; this._documentCallbackControlEvents = []; this._keyframeCallbacks = []; this._keyframeCallFuncs = {}; return true; }, getSequences: function () { return this._sequences; }, setSequences: function (seqs) { this._sequences = seqs; }, getAutoPlaySequenceId: function () { return this._autoPlaySequenceId; }, setAutoPlaySequenceId: function (autoPlaySequenceId) { this._autoPlaySequenceId = autoPlaySequenceId; }, getRootNode: function () { return this._rootNode; }, setRootNode: function (rootNode) { this._rootNode = rootNode; }, getOwner: function () { return this._owner; }, setOwner: function (owner) { this._owner = owner; }, addDocumentCallbackNode: function (node) { this._documentCallbackNodes.push(node); }, addDocumentCallbackName: function (name) { this._documentCallbackNames.push(name); }, addDocumentCallbackControlEvents: function (controlEvents) { this._documentCallbackControlEvents.push(controlEvents); }, addDocumentOutletNode: function (node) { this._documentOutletNodes.push(node); }, addDocumentOutletName: function (name) { this._documentOutletNames.push(name); }, setDocumentControllerName: function (name) { this._documentControllerName = name; }, getDocumentControllerName: function () { return this._documentControllerName; }, getDocumentCallbackNames: function () { return this._documentCallbackNames; }, getDocumentCallbackNodes: function () { return this._documentCallbackNodes; }, getDocumentCallbackControlEvents: function () { return this._documentCallbackControlEvents; }, getDocumentOutletNames: function () { return this._documentOutletNames; }, getDocumentOutletNodes: function () { return this._documentOutletNodes; }, getLastCompletedSequenceName: function () { return this._lastCompletedSequenceName; }, getKeyframeCallbacks: function () { return this._keyframeCallbacks; }, getRootContainerSize: function () { return this._rootContainerSize; }, setRootContainerSize: function (rootContainerSize) { this._rootContainerSize = cc.size(rootContainerSize.width, rootContainerSize.height); }, getDelegate: function () { return this._delegate; }, setDelegate: function (delegate) { this._delegate = delegate; }, getRunningSequenceName: function () { if (this._runningSequence) return this._runningSequence.getName(); return null; }, getContainerSize: function (node) { if (node) return node.getContentSize(); else return this._rootContainerSize; }, addNode: function (node, seq) { this._nodeSequences.setObject(seq, node); }, setBaseValue: function (value, node, propName) { var props = this._baseValues.objectForKey(node); if (!props) { props = new cc._Dictionary(); this._baseValues.setObject(props, node); } props.setObject(value, propName); }, moveAnimationsFromNode: function (fromNode, toNode) { // Move base values var locBaseValues = this._baseValues; var baseValue = locBaseValues.objectForKey(fromNode); if (baseValue !== null) { locBaseValues.setObject(baseValue, toNode); locBaseValues.removeObjectForKey(fromNode); } // Move seqs var locNodeSequences = this._nodeSequences; var seqs = locNodeSequences.objectForKey(fromNode); if (seqs != null) { locNodeSequences.setObject(seqs, toNode); locNodeSequences.removeObjectForKey(fromNode); } }, getActionForCallbackChannel: function (channel) { var lastKeyframeTime = 0; var actions = []; var keyframes = channel.getKeyframes(); var numKeyframes = keyframes.length; for (var i = 0; i < numKeyframes; ++i) { var keyframe = keyframes[i]; var timeSinceLastKeyframe = keyframe.getTime() - lastKeyframeTime; lastKeyframeTime = keyframe.getTime(); if (timeSinceLastKeyframe > 0) { actions.push(cc.delayTime(timeSinceLastKeyframe)); } var keyVal = keyframe.getValue(); var selectorName = keyVal[0]; var selectorTarget = keyVal[1]; if (this._jsControlled) { var callbackName = selectorTarget + ":" + selectorName; //add number to the stream var callback = this._keyframeCallFuncs[callbackName]; if (callback != null) actions.push(callback); } else { var target; if (selectorTarget === CCB_TARGETTYPE_DOCUMENTROOT) target = this._rootNode; else if (selectorTarget === CCB_TARGETTYPE_OWNER) target = this._owner; if (target != null) { if (selectorName.length > 0) { var selCallFunc = 0; if (target.onResolveCCBCCCallFuncSelector != null) selCallFunc = target.onResolveCCBCCCallFuncSelector(target, selectorName); if (selCallFunc === 0) cc.log("Skipping selector '" + selectorName + "' since no CCBSelectorResolver is present."); else actions.push(cc.callFunc(selCallFunc, target)); } else { cc.log("Unexpected empty selector."); } } } } if (actions.length < 1) return null; return cc.sequence(actions); }, getActionForSoundChannel: function (channel) { var lastKeyframeTime = 0; var actions = []; var keyframes = channel.getKeyframes(); var numKeyframes = keyframes.length; for (var i = 0; i < numKeyframes; ++i) { var keyframe = keyframes[i]; var timeSinceLastKeyframe = keyframe.getTime() - lastKeyframeTime; lastKeyframeTime = keyframe.getTime(); if (timeSinceLastKeyframe > 0) { actions.push(cc.delayTime(timeSinceLastKeyframe)); } var keyVal = keyframe.getValue(); var soundFile = cc.BuilderReader.getResourcePath() + keyVal[0]; var pitch = parseFloat(keyVal[1]), pan = parseFloat(keyVal[2]), gain = parseFloat(keyVal[3]); actions.push(cc.BuilderSoundEffect.create(soundFile, pitch, pan, gain)); } if (actions.length < 1) return null; return cc.sequence(actions); }, runAnimationsForSequenceNamed: function (name) { this.runAnimationsForSequenceIdTweenDuration(this._getSequenceId(name), 0); }, runAnimationsForSequenceNamedTweenDuration: function (name, tweenDuration) { this.runAnimationsForSequenceIdTweenDuration(this._getSequenceId(name), tweenDuration); }, runAnimationsForSequenceIdTweenDuration: function (nSeqId, tweenDuration) { if (nSeqId === -1) throw new Error("cc.BuilderAnimationManager.runAnimationsForSequenceIdTweenDuration(): Sequence id should not be -1"); tweenDuration = tweenDuration || 0; this._rootNode.stopAllActions(); var allKeys = this._nodeSequences.allKeys(); for (var i = 0, len = allKeys.length; i < len; i++) { var node = allKeys[i]; node.stopAllActions(); var seqs = this._nodeSequences.objectForKey(node); var seqNodeProps = seqs.objectForKey(nSeqId); var j; var seqNodePropNames = []; if (seqNodeProps) { var propKeys = seqNodeProps.allKeys(); for (j = 0; j < propKeys.length; j++) { var propName = propKeys[j]; var seqProp = seqNodeProps.objectForKey(propName); seqNodePropNames.push(propName); this._setFirstFrame(node, seqProp, tweenDuration); this._runAction(node, seqProp, tweenDuration); } } var nodeBaseValues = this._baseValues.objectForKey(node); if (nodeBaseValues) { var baseKeys = nodeBaseValues.allKeys(); for (j = 0; j < baseKeys.length; j++) { var selBaseKey = baseKeys[j]; if (seqNodePropNames.indexOf(selBaseKey) === -1) { var value = nodeBaseValues.objectForKey(selBaseKey); if (value != null) this._setAnimatedProperty(selBaseKey, node, value, tweenDuration); } } } } // Make callback at end of sequence var seq = this._getSequence(nSeqId); var completeAction = cc.sequence(cc.delayTime(seq.getDuration() + tweenDuration), cc.callFunc(this._sequenceCompleted, this)); this._rootNode.runAction(completeAction); // Playback callbacks and sounds var action; if (seq.getCallbackChannel()) { // Build sound actions for channel action = this.getActionForCallbackChannel(seq.getCallbackChannel()); if (action) { this._rootNode.runAction(action); } } if (seq.getSoundChannel()) { // Build sound actions for channel action = this.getActionForSoundChannel(seq.getSoundChannel()); if (action) { this._rootNode.runAction(action); } } // Set the running scene this._runningSequence = this._getSequence(nSeqId); }, runAnimations: function (name, tweenDuration) { tweenDuration = tweenDuration || 0; var nSeqId; if (cc.isString(name)) nSeqId = this._getSequenceId(name); else nSeqId = name; this.runAnimationsForSequenceIdTweenDuration(nSeqId, tweenDuration); }, setAnimationCompletedCallback: function (target, callbackFunc) { this._target = target; this._animationCompleteCallbackFunc = callbackFunc; }, setCompletedAnimationCallback: function (target, callbackFunc) { this.setAnimationCompletedCallback(target, callbackFunc); }, setCallFunc: function (callFunc, callbackNamed) { this._keyframeCallFuncs[callbackNamed] = callFunc; }, debug: function () { }, _getBaseValue: function (node, propName) { var props = this._baseValues.objectForKey(node); if (props) return props.objectForKey(propName); return null; }, _getSequenceId: function (sequenceName) { var element = null; var locSequences = this._sequences; for (var i = 0, len = locSequences.length; i < len; i++) { element = locSequences[i]; if (element && element.getName() === sequenceName) return element.getSequenceId(); } return -1; }, _getSequence: function (sequenceId) { var element = null; var locSequences = this._sequences; for (var i = 0, len = locSequences.length; i < len; i++) { element = locSequences[i]; if (element && element.getSequenceId() === sequenceId) return element; } return null; }, _getAction: function (keyframe0, keyframe1, propName, node) { var duration = keyframe1.getTime() - (keyframe0 ? keyframe0.getTime() : 0); var getArr, type, getValueArr, x, y; if (propName === "rotation") { return cc.BuilderRotateTo.create(duration, keyframe1.getValue()); } else if (propName === "rotationX") { return cc.BuilderRotateXTo.create(duration, keyframe1.getValue()); } else if (propName === "rotationY") { return cc.BuilderRotateYTo.create(duration, keyframe1.getValue()); } else if (propName === "opacity") { return cc.fadeTo(duration, keyframe1.getValue()); } else if (propName === "color") { var selColor = keyframe1.getValue(); return cc.tintTo(duration, selColor.r, selColor.g, selColor.b); } else if (propName === "visible") { var isVisible = keyframe1.getValue(); if (isVisible) { return cc.sequence(cc.delayTime(duration), cc.show()); } else { return cc.sequence(cc.delayTime(duration), cc.hide()); } } else if (propName === "displayFrame") { return cc.sequence(cc.delayTime(duration), cc.BuilderSetSpriteFrame.create(keyframe1.getValue())); } else if (propName === "position") { getArr = this._getBaseValue(node, propName); type = getArr[2]; //get relative position getValueArr = keyframe1.getValue(); x = getValueArr[0]; y = getValueArr[1]; var containerSize = this.getContainerSize(node.getParent()); var absPos = cc.getAbsolutePosition(x, y, type, containerSize, propName); return cc.moveTo(duration, absPos); } else if (propName === "scale") { getArr = this._getBaseValue(node, propName); type = getArr[2]; //get relative position getValueArr = keyframe1.getValue(); x = getValueArr[0]; y = getValueArr[1]; if (type === CCB_SCALETYPE_MULTIPLY_RESOLUTION) { //TODO need to test var resolutionScale = cc.BuilderReader.getResolutionScale(); x *= resolutionScale; y *= resolutionScale; } return cc.scaleTo(duration, x, y); } else if (propName === "skew") { //get relative position getValueArr = keyframe1.getValue(); x = getValueArr[0]; y = getValueArr[1]; return cc.skewTo(duration, x, y); } else { cc.log("BuilderReader: Failed to create animation for property: " + propName); } return null; }, _setAnimatedProperty: function (propName, node, value, tweenDuration) { if (tweenDuration > 0) { // Create a fake keyframe to generate the action from var kf1 = new cc.BuilderKeyframe(); kf1.setValue(value); kf1.setTime(tweenDuration); kf1.setEasingType(CCB_KEYFRAME_EASING_LINEAR); // Animate var tweenAction = this._getAction(null, kf1, propName, node); node.runAction(tweenAction); } else { // Just set the value var getArr, nType, x, y; if (propName === "position") { getArr = this._getBaseValue(node, propName); nType = getArr[2]; x = value[0]; y = value[1]; cc.getAbsolutePosition(x, y, nType, this.getContainerSize(node.getParent()), propName, _pos); node._position.x = _pos.x; node._position.y = _pos.y; } else if (propName === "scale") { getArr = this._getBaseValue(node, propName); nType = getArr[2]; x = value[0]; y = value[1]; cc.setRelativeScale(node, x, y, nType, propName); } else if (propName === "skew") { x = value[0]; y = value[1]; node._skewX = x; node._skewY = y; } else { // [node setValue:value forKey:name]; // TODO only handle rotation, opacity, displayFrame, color if (propName === "rotation") { node.setRotation(value); } else if (propName === "rotationX") { node._rotationX = value; } else if (propName === "rotationY") { node._rotationY = value; } else if (propName === "opacity") { node._realOpacity = value; } else if (propName === "displayFrame") { node.setSpriteFrame(value); } else if (propName === "color") { if (value.r !== 255 || value.g !== 255 || value.b !== 255) { node.setColor(value); } } else if (propName === "visible") { value = value || false; node.setVisible(value); } else { cc.log("unsupported property name is " + propName); return; } } node.setNodeDirty(); } }, _setFirstFrame: function (node, seqProp, tweenDuration) { var keyframes = seqProp.getKeyframes(); if (keyframes.length === 0) { // Use base value (no animation) var baseValue = this._getBaseValue(node, seqProp.getName()); if (!baseValue) cc.log("cc.BuilderAnimationManager._setFirstFrame(): No baseValue found for property"); this._setAnimatedProperty(seqProp.getName(), node, baseValue, tweenDuration); } else { // Use first keyframe var keyframe = keyframes[0]; this._setAnimatedProperty(seqProp.getName(), node, keyframe.getValue(), tweenDuration); } }, _getEaseAction: function (action, easingType, easingOpt) { if (easingType === CCB_KEYFRAME_EASING_LINEAR || easingType === CCB_KEYFRAME_EASING_INSTANT) { return action; } else if (easingType === CCB_KEYFRAME_EASING_CUBIC_IN) { return action.easing(cc.easeIn(easingOpt)); } else if (easingType === CCB_KEYFRAME_EASING_CUBIC_OUT) { return action.easing(cc.easeOut(easingOpt)); } else if (easingType === CCB_KEYFRAME_EASING_CUBIC_INOUT) { return action.easing(cc.easeInOut(easingOpt)); } else if (easingType === CCB_KEYFRAME_EASING_BACK_IN) { return action.easing(cc.easeBackIn()); } else if (easingType === CCB_KEYFRAME_EASING_BACK_OUT) { return action.easing(cc.easeBackOut()); } else if (easingType === CCB_KEYFRAME_EASING_BACK_INOUT) { return action.easing(cc.easeBackInOut()); } else if (easingType === CCB_KEYFRAME_EASING_BOUNCE_IN) { return action.easing(cc.easeBounceIn()); } else if (easingType === CCB_KEYFRAME_EASING_BOUNCE_OUT) { return action.easing(cc.easeBounceOut()); } else if (easingType === CCB_KEYFRAME_EASING_BOUNCE_INOUT) { return action.easing(cc.easeBounceInOut()); } else if (easingType === CCB_KEYFRAME_EASING_ELASTIC_IN) { return action.easing(cc.easeElasticIn(easingOpt)); } else if (easingType === CCB_KEYFRAME_EASING_ELASTIC_OUT) { return action.easing(cc.easeElasticOut(easingOpt)); } else if (easingType === CCB_KEYFRAME_EASING_ELASTIC_INOUT) { return action.easing(cc.easeElasticInOut(easingOpt)); } else { cc.log("BuilderReader: Unknown easing type " + easingType); return action; } }, _runAction: function (node, seqProp, tweenDuration) { var keyframes = seqProp.getKeyframes(); var numKeyframes = keyframes.length; if (numKeyframes > 1) { // Make an animation! var actions = []; var keyframeFirst = keyframes[0]; var timeFirst = keyframeFirst.getTime() + tweenDuration; if (timeFirst > 0) { actions.push(cc.delayTime(timeFirst)); } for (var i = 0; i < numKeyframes - 1; ++i) { var kf0 = keyframes[i]; var kf1 = keyframes[(i + 1)]; var action = this._getAction(kf0, kf1, seqProp.getName(), node); if (action) { // Apply easing action = this._getEaseAction(action, kf0.getEasingType(), kf0.getEasingOpt()); actions.push(action); } } node.runAction(cc.sequence(actions)); } }, _sequenceCompleted: function () { var locRunningSequence = this._runningSequence; var locRunningName = locRunningSequence.getName(); if (this._lastCompletedSequenceName != locRunningSequence.getName()) { this._lastCompletedSequenceName = locRunningSequence.getName(); } var nextSeqId = locRunningSequence.getChainedSequenceId(); this._runningSequence = null; if (nextSeqId !== -1) this.runAnimations(nextSeqId, 0); if (this._delegate) this._delegate.completedAnimationSequenceNamed(locRunningName); if (this._target && this._animationCompleteCallbackFunc) { this._animationCompleteCallbackFunc.call(this._target); } } }); cc.BuilderSetSpriteFrame = cc.ActionInstant.extend({ _spriteFrame: null, initWithSpriteFrame: function (spriteFrame) { this._spriteFrame = spriteFrame; return true; }, update: function (time) { this.target.setSpriteFrame(this._spriteFrame); } }); cc.BuilderSetSpriteFrame.create = function (spriteFrame) { var ret = new cc.BuilderSetSpriteFrame(); if (ret) { if (ret.initWithSpriteFrame(spriteFrame)) return ret; } return null; }; // // cc.BuilderRotateTo // cc.BuilderRotateTo = cc.ActionInterval.extend({ _startAngle: 0, _dstAngle: 0, _diffAngle: 0, initWithDuration: function (duration, angle) { if (cc.ActionInterval.prototype.initWithDuration.call(this, duration)) { this._dstAngle = angle; return true; } else { return false; } }, update: function (time) { this.target.setRotation(this._startAngle + (this._diffAngle * time)); }, startWithTarget: function (node) { cc.ActionInterval.prototype.startWithTarget.call(this, node); this._startAngle = this.target.getRotation(); this._diffAngle = this._dstAngle - this._startAngle; } }); cc.BuilderRotateTo.create = function (duration, angle) { var ret = new cc.BuilderRotateTo(); if (ret) { if (ret.initWithDuration(duration, angle)) return ret; } return null; }; // // cc.BuilderRotateXTo // cc.BuilderRotateXTo = cc.ActionInterval.extend({ // TODO: rotationX is not implemented in HTML5 }); cc.BuilderRotateXTo.create = function (duration, angle) { throw new Error("rotationX has not been implemented in cocos2d-html5"); }; // // cc.BuilderRotateYTo // cc.BuilderRotateYTo = cc.ActionInterval.extend({ // TODO: rotationX is not implemented in HTML5 }); cc.BuilderRotateYTo.create = function (duration, angle) { throw new Error("rotationY has not been implemented in cocos2d-html5"); }; // // cc.BuilderSoundEffect // cc.BuilderSoundEffect = cc.ActionInstant.extend({ init: function (file) { this._file = file; return true; }, update: function (dt) { cc.audioEngine.playEffect(this._file); } }); cc.BuilderSoundEffect.create = function (file, pitch, pan, gain) { var ret = new cc.BuilderSoundEffect(); if (ret && ret.init(file)) { return ret; } return null; }; })();