Source: timer/AnimationTimeline.js

/**
 * 타임라인 기반으로 애니메이션을 실행시킴
 * [튜토리얼 보기](../tutorial/timer_timeline.html)
 *  
 * @see collie.Timer
 * @class
 * @extends collie.Animation
 * @param {Array} aTimeline 타임라인 배열
 * @param {Object} [htOption]
 * @param {Object} [htOption.loop=1] 반복 횟수, 0일 경우 무한 반복 한다
 * @example
 * // 생성과 동시에 타임라인을 정의
 * collie.Timer.timeline([
 *      [0, "delay", function () {}, 1000],
 *      [10, "transition", function () {}, 1000, { from:1, to:1 }],
 * ]);
 * 
 * @example
 * // 생성 후 타임라인을 정의
 * var timeline = collie.Timer.timeline();
 * timeline.add(0, "delay", function () {}, 1000);
 * timeline.add(10, "transition", function () {}, 1000, { from:1, to:1 });
 * 
 * @example
 * // 타임라인 액션을 삭제
 * var timeline = collie.Timer.timeline();
 * var action = timeline.add(0, "delay", function () {}, 1000);
 * timeline.remove(10); // 10ms에 실행되는 모든 액션을 삭제
 * timeline.remove(10, action); // action만 삭제 
 */
collie.AnimationTimeline = collie.Class(/** @lends collie.AnimationTimeline.prototype */{
    $init : function (aTimeline, htOption) {
        this.option("loop", 1);
        this.option(htOption || {});
        this.setOptionEvent(htOption);
        this._htAnimations = {};
        this._aTimeline = null;
        this._aRunningAnimation = null;
        this._nRunningTime = null;
        this._nCountCycle = 0;
        
        if (aTimeline) {
            for (var i = 0, l = aTimeline.length; i < l; i++) {
                this.addTimeline.apply(this, aTimeline[i]);
            }
        }
                
        this.reset();
    },
    
    /**
     * 타임라인에 애니메이션을 추가
     * 
     * @param {Number} nStartTime 시작 시간(ms) 
     * @param {String|collie.Animation} 애니메이션 이름이나 애니메이션 객체를 지정한다.
     * @param {Function|Object} fCallback 각 애니메이션에 쓰이는 인자, queue 애니메이션일 경우 첫 번째 안자가 htOption이 된다
     * @param {Number} nDuration 각 애니메이션에 쓰이는 인자
     * @param {Object} htOption 각 애니메이션에 쓰이는 인자
     * @return {collie.Animation} 만들어진 애니메이션
     * @example
     * var timeline = collie.Timer.timeline();
     * 
     * // queue를 사용하는 방법
     * var queue = timeline.add(0, "queue");
     * queue.cycle(item, 1000, { from:0, to:9 });
     * 
     * // 직접 Animation 객체를 생성
     * timeline.add(100, new collie.AnimationCycle(item, 1000, { from:0, to:9 }));
     */
    add : function (nStartTime, vType, fCallback, nDuration, htOption) {
        var oAnimation;
        
        // 애니메이션 인스턴스 생성
        switch (vType) {
            case "delay" :
                oAnimation = new collie.AnimationDelay(fCallback, nDuration, htOption);                 
                break;
                
            case "repeat" :
                oAnimation = new collie.AnimationRepeat(fCallback, nDuration, htOption);
                break;
                
            case "transition" :
                oAnimation = new collie.AnimationTransition(fCallback, nDuration, htOption);
                break;
                
            case "cycle" :
                oAnimation = new collie.AnimationCycle(fCallback, nDuration, htOption);
                break;
                
            case "queue" :
                oAnimation = new collie.AnimationQueue(fCallback /* htOption임 */);
                break;
                
            default :
                if (vType instanceof collie.Animation) {
                    oAnimation = vType;
                } else {
                    throw new Error(vType + ' timer is not defined');
                }
        }
        
        this._addTimeline(nStartTime, oAnimation);
        return oAnimation;
    },
    
    /**
     * 애니메이션 인스턴스를 추가
     * 
     * @private
     * @param {Number} nStartTime 시작 시간(ms) 
     * @param {collie.Animation} oAnimation 추가될 애니메이션
     */
    _addTimeline : function (nStartTime, oAnimation) {
        nStartTime = parseInt(nStartTime, 10); // 정수로 변환
        this._htAnimations[nStartTime] = this._htAnimations[nStartTime] || []; 
        this._htAnimations[nStartTime].push(oAnimation);
        
        // 이미 초기화 됐다면 다시 초기화
        if (this._aTimeline !== null) {
            this.reset();
        }
    },
    
    /**
     * 등록된 타임라인을 제거한다
     * 
     * @param {Number} nStartTime 시작 시간(ms)
     * @param {collie.Animation} oTimer 지울 타이머, 값이 없으면 해당 시간대 전부를 지움
     */
    remove : function (nStartTime, oTimer) {
        nStartTime = parseInt(nStartTime, 10); // 정수로 변환
        
        if (this._htAnimations && this._htAnimations[nStartTime]) {
            for (var i = 0; i < this._htAnimations[nStartTime].length; i++) {
                if (typeof oTimer === "undefined" || oTimer === this._htAnimations[nStartTime][i]) {
                    this._htAnimations[nStartTime][i].stop();
                    this._htAnimations[nStartTime].splice(i, 1);
                    i--;
                    
                    if (typeof oTimer !== "undefined") {
                        break;
                    }
                }
            }
            
            // 지웠는데 더 이상 그 시간대에 타이머가 없을 경우 생성된 Timeline도 지움
            if (this._htAnimations[nStartTime].length < 1) {
                delete this._htAnimations[nStartTime];
                this._removeTimelineStartTime(nStartTime);
            }
        }
    },
    
    _removeTimelineStartTime : function (nStartTime) {
        if (this._aTimeline) {
            for (var i = 0, l = this._aTimeline.length; i < l; i++) {
                if (this._aTimeline[i] === nStartTime) {
                    this._aTimeline.splice(i, 1);
                    break;
                }
            }
        }
    },
    
    /**
     * 타임라인을 초기화
     * @private
     */
    _initTimeline : function () {
        this._aTimeline = [];
        this._aRunningAnimation = [];
        
        // 시작 시간을 넣음
        for (var i in this._htAnimations) {
            this._aTimeline.push(parseInt(i, 10));
        } 
        
        // 정렬
        this._aTimeline.sort(function (a, b) {
            return a - b;
        });
    },
    
    /**
     * 등록된 애니메이션 인스턴스를 반환한다
     * 
     * @param {Number} nStartTime 시작 시간(ms) 
     * @return {Array|Boolean} 등록된 애니메이션이 없으면 false를 반환, 반환 형식은 항상 배열임
     */
    getAnimation : function (nStartTime) {
        nStartTime = parseInt(nStartTime, 10); // 정수로 변환
        return (this._htAnimations && this._htAnimations[nStartTime]) ? this._htAnimations[nStartTime] : false;
    },
    
    /**
     * 현재까지 진행된 시간을 반환
     * @return {Number} ms 진행이 안된 상태면 0을 반환
     */
    getRunningTime : function () {
        return this._nRunningTime || 0;
    },
    
    /**
     * 현재까지 반복된 횟수
     * @return {Number}
     */
    getCycle : function () {
        return this._nCountCycle || 0;
    },
    
    /**
     * 값을 초기화
     */
    reset : function () {
        this._nFrameAtRunLastest = null;
        this._nRunningTime = null;      
        this._aTimeline = null;
        this._aRunningAnimation = null;
        this._nCountCycle = 0;
        this._initTimeline();
    },
    
    /**
     * 애니메이션을 실행
     * 
     * @param {Number} [nCurrentFrame] 현재 렌더러 프레임, 값이 없으면 자동으로 현재 렌더러 프레임을 가져 온다
     * @param {Number} [nFrameDuration] 진행된 프레임 시간(ms)
     */
    run : function (nCurrentFrame, nFrameDuration) {
        if (nCurrentFrame === undefined) {
            nCurrentFrame = collie.Renderer.getInfo().frame;
        }
        
        // 렌더러가 stop 된 경우
        if (this._nFrameAtRunLastest > nCurrentFrame) {
            this.reset();
            return;
        }
        
        // 시작 프레임 저장
        if (this._nFrameAtRunLastest === null) {
            this._nFrameAtRunLastest = nCurrentFrame;
            this._nRunningTime = 0;
            nFrameDuration = 0;
        }
        
        this._nRunningTime += nFrameDuration;
        
        // 진행될 액션이 있을 경우 추가
        if (this._aTimeline.length > 0) {
            while (this._aTimeline[0] <= this._nRunningTime) {
                var nStartTime = this._aTimeline.shift();
                
                for (var i = 0, l = this._htAnimations[nStartTime].length; i < l; i++) {
                    this._aRunningAnimation.push(this._htAnimations[nStartTime][i]);
                    this._htAnimations[nStartTime][i].start();
                }
            }
        }
        
        // 진행중인 액션이 있을 경우 run 전달
        if (this._aRunningAnimation.length > 0) {
            for (var i = 0; i < this._aRunningAnimation.length; i++) {
                if (this._aRunningAnimation[i]) {
                    this._aRunningAnimation[i].run(nCurrentFrame, nFrameDuration);
                }
                
                if (!this._aRunningAnimation[i] || !this._aRunningAnimation[i].isPlaying()) {
                    if (this._aRunningAnimation[i]) {
                        this._aRunningAnimation[i].reset();
                    }
                    
                    this._aRunningAnimation.splice(i, 1);
                    i--;
                    this._checkComplete();
                }
            }
        }
    },
    
    _checkComplete : function () {
        // 끝났으면
        if (this._aRunningAnimation.length < 1 && this._aTimeline.length < 1) {
            this._nCountCycle++;
            
            if (this._htOption.loop && this._htOption.loop <= this._nCountCycle) {
                /**
                 * 계획된 모든 애니메이션과 반복 횟수가 끝나면 발생. loop=0으로 설정하면 발생하지 않는다.
                 * @name collie.AnimationTimeline#complete
                 * @event
                 * @param {Object} oEvent 기본 컴포넌트 이벤트 객체
                 */
                this.complete();
            } else {
                /**
                 * loop가 있을 경우 모든 타임라인 액션이 한 번 끝났을 때 발생
                 * @name collie.AnimationTimeline#end
                 * @event
                 * @param {Object} oEvent 기본 컴포넌트 이벤트 객체
                 */
                this.fireEvent("end");
                this._nFrameAtRunLastest = null;
                this._nRunningTime = null;      
                this._aTimeline = null;
                this._aRunningAnimation = null;
                this._initTimeline();
            }
        }
    }
}, collie.Animation);
comments powered by Disqus